Support folder drops in Design Files (#1662)

Co-authored-by: mzl2233 <mzl2233@users.noreply.github.com>
This commit is contained in:
Yixuan Xu 2026-05-19 16:52:19 +08:00 committed by GitHub
parent db9752f5ae
commit b311885bee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -37,6 +37,18 @@ type DesignFilesGroupMode = 'kind' | 'modified';
type ModifiedSection = 'today' | 'yesterday' | 'previous7Days' | 'previous30Days' | 'older'; type ModifiedSection = 'today' | 'yesterday' | 'previous7Days' | 'previous30Days' | 'older';
type SortKey = 'name' | 'kind' | 'mtime'; type SortKey = 'name' | 'kind' | 'mtime';
type SortDir = 'asc' | 'desc'; type SortDir = 'asc' | 'desc';
type FileSystemEntryWithReader = FileSystemEntry & {
createReader?: () => FileSystemDirectoryReader;
};
type FileSystemFileEntryWithFile = FileSystemFileEntry & {
file: (
successCallback: (file: File) => void,
errorCallback?: (error: DOMException) => void,
) => void;
};
type DataTransferItemWithEntry = DataTransferItem & {
webkitGetAsEntry?: () => FileSystemEntry | null;
};
const MODIFIED_SECTION_ORDER: ModifiedSection[] = [ const MODIFIED_SECTION_ORDER: ModifiedSection[] = [
'today', 'today',
@ -671,11 +683,11 @@ export function DesignFilesPanel({
} }
} }
function handleDrop(ev: React.DragEvent<HTMLDivElement>) { async function handleDrop(ev: React.DragEvent<HTMLDivElement>) {
ev.preventDefault(); ev.preventDefault();
dragDepthRef.current = 0; dragDepthRef.current = 0;
setDraggingFiles(false); setDraggingFiles(false);
const dropped = Array.from(ev.dataTransfer.files ?? []); const dropped = await filesFromDataTransfer(ev.dataTransfer);
if (dropped.length > 0) onUploadFiles(dropped); if (dropped.length > 0) onUploadFiles(dropped);
} }
@ -1394,6 +1406,49 @@ function dateDaysBefore(date: Date, days: number): Date {
return result; return result;
} }
async function filesFromDataTransfer(dataTransfer: DataTransfer): Promise<File[]> {
const items = Array.from(dataTransfer.items ?? []);
if (items.length === 0) return Array.from(dataTransfer.files ?? []);
const files = await Promise.all(items.map(filesFromDataTransferItem));
const flattened = files.flat();
return flattened.length > 0 ? flattened : Array.from(dataTransfer.files ?? []);
}
async function filesFromDataTransferItem(item: DataTransferItem): Promise<File[]> {
const entry = (item as DataTransferItemWithEntry).webkitGetAsEntry?.();
if (!entry) {
const file = item.kind === 'file' ? item.getAsFile() : null;
return file ? [file] : [];
}
return filesFromFileSystemEntry(entry);
}
async function filesFromFileSystemEntry(entry: FileSystemEntry): Promise<File[]> {
if (entry.isFile) return [await fileFromEntry(entry as FileSystemFileEntryWithFile)];
if (!entry.isDirectory) return [];
const reader = (entry as FileSystemEntryWithReader).createReader?.();
if (!reader) return [];
const files: File[] = [];
for (;;) {
const entries = await readEntryBatch(reader);
if (entries.length === 0) break;
const nested = await Promise.all(entries.map(filesFromFileSystemEntry));
files.push(...nested.flat());
}
return files;
}
function fileFromEntry(entry: FileSystemFileEntryWithFile): Promise<File> {
return new Promise((resolve, reject) => entry.file(resolve, reject));
}
function readEntryBatch(reader: FileSystemDirectoryReader): Promise<FileSystemEntry[]> {
return new Promise((resolve, reject) => reader.readEntries(resolve, reject));
}
function kindGlyph(kind: ProjectFileKind): string { function kindGlyph(kind: ProjectFileKind): string {
if (kind === 'html') return '\u27E8\u27E9'; if (kind === 'html') return '\u27E8\u27E9';
if (kind === 'image') return '\u25A3'; if (kind === 'image') return '\u25A3';