1export 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
19export interface WorkspaceFileDropHost {
20 setDragActive(active: boolean): void;
21 addFiles(files: File[]): void;
22 addFolders(folders: File[]): void;
23}
24
25function isFileDrag(event: WorkspaceFileDragEvent): boolean {
26 return event.dataTransfer.types.includes("Files");
27}
28
29function movedWithinDropTarget(event: WorkspaceFileDragEvent): boolean {
30 return event.relatedTarget !== null && event.currentTarget.contains(event.relatedTarget as Node);
31}
32
33function 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
55export 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}