Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 157 additions & 3 deletions panel/models/file_dropper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,167 @@ export class DeleteEvent extends ModelEvent {
}
}

// Mapping of file extensions to browser-reported MIME types
// as it's not yet supported by filepond, see:
// https://github.com/pqina/filepond-plugin-file-validate-type/issues/13
const EXTENSION_TO_MIME_TYPE: Record<string, string> = {
// Images
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".gif": "image/gif",
".svg": "image/svg+xml",
".webp": "image/webp",
".bmp": "image/bmp",
".ico": "image/x-icon",
".tiff": "image/tiff",
".tif": "image/tiff",
".avif": "image/avif",

// Video
".mp4": "video/mp4",
".webm": "video/webm",
".mov": "video/quicktime",
".mkv": "video/x-matroska",
".avi": "video/x-msvideo",
".wmv": "video/x-ms-wmv",
".flv": "video/x-flv",

// Audio
".mp3": "audio/mpeg",
".wav": "audio/wav",
".ogg": "audio/ogg",
".oga": "audio/ogg",
".m4a": "audio/mp4",
".aac": "audio/aac",
".flac": "audio/flac",

// Text / documents
".txt": "text/plain",
".md": "text/markdown",
".rtf": "application/rtf",
".pdf": "application/pdf",

// Structured data (browser-compatible)
".json": "application/json",
".xml": "text/xml",
".csv": "text/csv",
".tsv": "text/tab-separated-values",
".yaml": "text/yaml",
".yml": "text/yaml",

// Web / code
".html": "text/html",
".htm": "text/html",
".css": "text/css",
".js": "text/javascript",
".mjs": "text/javascript",
".ts": "application/typescript",
".jsx": "text/jsx",
".tsx": "text/tsx",
".wasm": "application/wasm",

// Office documents
".doc": "application/msword",
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
".xls": "application/vnd.ms-excel",
".xlsb": "application/vnd.ms-excel.sheet.binary.macroenabled.12",
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
".ppt": "application/vnd.ms-powerpoint",
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
".odt": "application/vnd.oasis.opendocument.text",
".ods": "application/vnd.oasis.opendocument.spreadsheet",
".odp": "application/vnd.oasis.opendocument.presentation",

// Archives
".zip": "application/zip",
".tar": "application/x-tar",
".gz": "application/gzip",
".tgz": "application/gzip",
".bz2": "application/x-bzip2",
".xz": "application/x-xz",
".7z": "application/x-7z-compressed",
".rar": "application/vnd.rar",

// Fonts
".ttf": "font/ttf",
".otf": "font/otf",
".woff": "font/woff",
".woff2": "font/woff2",

// Executables / binaries
".exe": "application/vnd.microsoft.portable-executable",
".dll": "application/vnd.microsoft.portable-executable",
".bin": "application/octet-stream",
".dmg": "application/x-apple-diskimage",
".pkg": "application/octet-stream",
".deb": "application/vnd.debian.binary-package",
".rpm": "application/x-rpm",

// Columnar / big data
".parquet": "application/octet-stream", // browsers do NOT emit application/parquet
".arrow": "application/octet-stream",
".feather": "application/octet-stream",
".orc": "application/octet-stream",

// Scientific / numerical
".npy": "application/octet-stream",
".npz": "application/octet-stream",
".mat": "application/octet-stream",
".h5": "application/octet-stream",
".hdf5": "application/octet-stream",
".zarr": "application/octet-stream",

// R ecosystem
".rds": "application/octet-stream",
".rda": "application/octet-stream",
".rdata": "application/octet-stream",

// Notebooks
".ipynb": "application/json",

// ML models / artifacts
".pkl": "application/octet-stream",
".pickle": "application/octet-stream",
".joblib": "application/octet-stream",
".onnx": "application/octet-stream",
".pt": "application/octet-stream",
".pth": "application/octet-stream",
".h5model": "application/octet-stream",

// Vector
".shp": "application/octet-stream",
".shx": "application/octet-stream",
".dbf": "application/octet-stream",
".geojson": "application/geo+json",
".gpkg": "application/octet-stream",

// Raster
".geotiff": "image/tiff",
".asc": "text/plain",
}

export class FileDropperView extends InputWidgetView {
declare model: FileDropper
declare input_el: HTMLInputElement
_file_pond: any | null = null
_transfer_in_process: string | null = null

private extensionsToMimeTypes(types: string[]): string[] {
return types.map(type => {
// MIME types, including wildcards like "image/*"
if (type.includes("/")) {
return type
}
// Guessing it's an extension
if (type.startsWith(".")) {
const lowerType = type.toLowerCase()
return EXTENSION_TO_MIME_TYPE[lowerType] || type
}
return type
})
}

override initialize(): void {
super.initialize()
const {previews} = this.model
Expand All @@ -61,7 +216,7 @@ export class FileDropperView extends InputWidgetView {
const {disabled, layout, max_file_size, max_files, max_total_file_size, multiple} = this.model.properties
this.on_change([disabled, max_file_size, max_files, max_total_file_size, multiple, layout], () => {
this._file_pond?.setOptions({
acceptedFileTypes: this.model.accepted_filetypes,
acceptedFileTypes: this.extensionsToMimeTypes(this.model.accepted_filetypes),
allowMultiple: this.model.multiple,
disabled: this.model.disabled,
maxFiles: this.model.max_files,
Expand Down Expand Up @@ -91,9 +246,8 @@ export class FileDropperView extends InputWidgetView {

override render(): void {
super.render()

this._file_pond = (window as any).FilePond.create(this.input_el, {
acceptedFileTypes: this.model.accepted_filetypes,
acceptedFileTypes: this.extensionsToMimeTypes(this.model.accepted_filetypes),
allowMultiple: this.model.multiple,
credits: false,
disabled: this.model.disabled,
Expand Down
Loading