| 1 | export interface WorkspaceFileDragEvent { |
| 2 | readonly dataTransfer: { |
| 3 | readonly types: ReadonlyArray<string>; |
| 4 | readonly files: Iterable<File>; |
| 5 | readonly items?: Iterable<{ |
| 6 | readonly kind: string; |
| 7 | getAsFile(): File | null; |
| 8 | webkitGetAsEntry(): { readonly isDirectory: boolean } | null; |
| 9 | }>; |
| 10 | dropEffect: string; |
| 11 | }; |
| 12 | readonly relatedTarget: EventTarget | null; |
| 13 | readonly currentTarget: { |
| 14 | contains(target: Node | null): boolean; |
| 15 | }; |
| 16 | preventDefault(): void; |
| 17 | } |
| 18 | |
| 19 | export interface WorkspaceFileDropHost { |
| 20 | setDragActive(active: boolean): void; |
| 21 | addFiles(files: File[]): void; |
| 22 | addFolders(folders: File[]): void; |
| 23 | } |
| 24 | |
| 25 | function isFileDrag(event: WorkspaceFileDragEvent): boolean { |
| 26 | return event.dataTransfer.types.includes("Files"); |
| 27 | } |
| 28 | |
| 29 | function movedWithinDropTarget(event: WorkspaceFileDragEvent): boolean { |
| 30 | return event.relatedTarget !== null && event.currentTarget.contains(event.relatedTarget as Node); |
| 31 | } |
| 32 | |
| 33 | function splitDroppedItems(dataTransfer: WorkspaceFileDragEvent["dataTransfer"]): { |
| 34 | files: File[]; |
| 35 | folders: File[]; |
| 36 | } { |
| 37 | if (dataTransfer.items === undefined) |
| 38 | return { files: Array.from(dataTransfer.files), folders: [] }; |
| 39 | |
| 40 | const files: File[] = []; |
| 41 | const folders: File[] = []; |
| 42 | for (const item of dataTransfer.items) { |
| 43 | if (item.kind !== "file") continue; |
| 44 | const file = item.getAsFile(); |
| 45 | if (file === null) continue; |
| 46 | if (item.webkitGetAsEntry()?.isDirectory === true) { |
| 47 | folders.push(file); |
| 48 | } else { |
| 49 | files.push(file); |
| 50 | } |
| 51 | } |
| 52 | return { files, folders }; |
| 53 | } |
| 54 | |
| 55 | export function makeWorkspaceFileDropHandlers(host: WorkspaceFileDropHost) { |
| 56 | return { |
| 57 | onDragEnter(event: WorkspaceFileDragEvent) { |
| 58 | if (!isFileDrag(event)) return; |
| 59 | event.preventDefault(); |
| 60 | if (movedWithinDropTarget(event)) return; |
| 61 | host.setDragActive(true); |
| 62 | }, |
| 63 | onDragOver(event: WorkspaceFileDragEvent) { |
| 64 | if (!isFileDrag(event)) return; |
| 65 | event.preventDefault(); |
| 66 | event.dataTransfer.dropEffect = "copy"; |
| 67 | host.setDragActive(true); |
| 68 | }, |
| 69 | onDragLeave(event: WorkspaceFileDragEvent) { |
| 70 | if (!isFileDrag(event)) return; |
| 71 | event.preventDefault(); |
| 72 | if (movedWithinDropTarget(event)) return; |
| 73 | host.setDragActive(false); |
| 74 | }, |
| 75 | onDrop(event: WorkspaceFileDragEvent) { |
| 76 | if (!isFileDrag(event)) return; |
| 77 | event.preventDefault(); |
| 78 | host.setDragActive(false); |
| 79 | const { files, folders } = splitDroppedItems(event.dataTransfer); |
| 80 | if (files.length > 0) host.addFiles(files); |
| 81 | if (folders.length > 0) host.addFolders(folders); |
| 82 | }, |
| 83 | }; |
| 84 | } |