frontend: fix uploads
This commit is contained in:
+101
-101
@@ -3050,122 +3050,122 @@ const AppLayout = () => {
|
||||
}
|
||||
|
||||
const items = Array.from(dataTransfer.items || []);
|
||||
const results = [];
|
||||
console.info('[Uploads] drop start', { items: items.length, files: (dataTransfer.files || []).length });
|
||||
|
||||
const pushFile = (file, ancestors) => {
|
||||
if (file) {
|
||||
results.push({
|
||||
file,
|
||||
segments: (ancestors || []).filter(Boolean),
|
||||
const results = [];
|
||||
const seenKeys = new Set();
|
||||
|
||||
const pushFile = (file, ancestors = []) => {
|
||||
if (!file) return;
|
||||
const segments = (ancestors || []).filter(Boolean);
|
||||
const key = `${segments.join('/')}/${file.name}:${file.size}`;
|
||||
if (seenKeys.has(key)) {
|
||||
// skipped duplicate
|
||||
return;
|
||||
}
|
||||
seenKeys.add(key);
|
||||
results.push({ file, segments });
|
||||
// queued file
|
||||
};
|
||||
|
||||
const readAllEntries = async (reader) => {
|
||||
const entries = [];
|
||||
while (true) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const batch = await new Promise((resolve, reject) => reader.readEntries(resolve, reject));
|
||||
if (!batch.length) {
|
||||
break;
|
||||
}
|
||||
entries.push(...batch);
|
||||
}
|
||||
return entries;
|
||||
};
|
||||
|
||||
const walkEntry = async (entry, ancestors = []) => {
|
||||
if (!entry) return;
|
||||
if (entry.isFile) {
|
||||
const file = await new Promise((resolve, reject) => {
|
||||
try {
|
||||
entry.file(resolve, reject);
|
||||
} catch (error) {
|
||||
console.warn('[Uploads] entry.file failed', error);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
pushFile(file, ancestors);
|
||||
return;
|
||||
}
|
||||
if (entry.isDirectory) {
|
||||
const nextAncestors = entry.name ? [...ancestors, entry.name] : [...ancestors];
|
||||
const reader = entry.createReader();
|
||||
const entries = await readAllEntries(reader);
|
||||
for (const child of entries) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await walkEntry(child, nextAncestors);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const supportsFileSystemAccess =
|
||||
window.isSecureContext &&
|
||||
items.some((item) => typeof item.getAsFileSystemHandle === 'function');
|
||||
const supportsWebkitEntries = items.some(
|
||||
(item) => typeof item.webkitGetAsEntry === 'function',
|
||||
await Promise.all(
|
||||
items.map(async (item, index) => {
|
||||
if (item.kind !== 'file') return;
|
||||
|
||||
const fileFromItem = typeof item.getAsFile === 'function' ? item.getAsFile() : null;
|
||||
if (fileFromItem) {
|
||||
const relativePath =
|
||||
typeof fileFromItem.webkitRelativePath === 'string' ? fileFromItem.webkitRelativePath : '';
|
||||
const segments = relativePath
|
||||
? relativePath
|
||||
.split('/')
|
||||
.slice(0, -1)
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
pushFile(fileFromItem, segments);
|
||||
}
|
||||
|
||||
if (typeof item.webkitGetAsEntry === 'function') {
|
||||
try {
|
||||
const entry = item.webkitGetAsEntry();
|
||||
if (entry) {
|
||||
// processing entry
|
||||
await walkEntry(entry, []);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[Uploads] webkitGetAsEntry failed', error);
|
||||
}
|
||||
}
|
||||
|
||||
if (!fileFromItem) {
|
||||
console.info('[Uploads] item missing file handle', index);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
if (supportsFileSystemAccess) {
|
||||
const walkDirectoryHandle = async (handle, ancestors) => {
|
||||
const nextAncestors = handle.name ? [...ancestors, handle.name] : [...ancestors];
|
||||
for await (const child of handle.values()) {
|
||||
if (child.kind === 'file') {
|
||||
const file = await child.getFile();
|
||||
pushFile(file, nextAncestors);
|
||||
} else if (child.kind === 'directory') {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await walkDirectoryHandle(child, nextAncestors);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (const item of items) {
|
||||
if (item.kind !== 'file') continue;
|
||||
const getHandle = item.getAsFileSystemHandle?.bind(item);
|
||||
if (!getHandle) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const handle = await getHandle();
|
||||
if (!handle) continue;
|
||||
|
||||
if (handle.kind === 'file') {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const file = await handle.getFile();
|
||||
pushFile(file, []);
|
||||
} else if (handle.kind === 'directory') {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await walkDirectoryHandle(handle, []);
|
||||
}
|
||||
}
|
||||
} else if (supportsWebkitEntries) {
|
||||
const walkWebkitEntry = async (entry, ancestors) => {
|
||||
if (!entry) return;
|
||||
if (entry.isFile) {
|
||||
const file = await new Promise((resolve, reject) => {
|
||||
entry.file(resolve, reject);
|
||||
});
|
||||
pushFile(file, ancestors);
|
||||
return;
|
||||
}
|
||||
if (entry.isDirectory) {
|
||||
const nextAncestors = entry.name ? [...ancestors, entry.name] : [...ancestors];
|
||||
const reader = entry.createReader();
|
||||
const readEntries = () =>
|
||||
new Promise((resolve, reject) => {
|
||||
reader.readEntries(resolve, reject);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const batch = await readEntries();
|
||||
if (!batch.length) {
|
||||
break;
|
||||
}
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
for (const child of batch) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await walkWebkitEntry(child, nextAncestors);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (const item of items) {
|
||||
const entry =
|
||||
typeof item.webkitGetAsEntry === 'function' ? item.webkitGetAsEntry() : null;
|
||||
if (!entry) continue;
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await walkWebkitEntry(entry, []);
|
||||
}
|
||||
} else {
|
||||
const files = Array.from(dataTransfer.files || []);
|
||||
files.forEach((file) => {
|
||||
if (!file) return;
|
||||
const relativePath =
|
||||
typeof file.webkitRelativePath === 'string' ? file.webkitRelativePath : '';
|
||||
const segments = relativePath
|
||||
? relativePath
|
||||
.split('/')
|
||||
.slice(0, -1)
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
pushFile(file, segments);
|
||||
});
|
||||
}
|
||||
Array.from(dataTransfer.files || []).forEach((file, index) => {
|
||||
if (!file) return;
|
||||
// FileList entry suppressed
|
||||
const relativePath =
|
||||
typeof file.webkitRelativePath === 'string' ? file.webkitRelativePath : '';
|
||||
const segments = relativePath
|
||||
? relativePath
|
||||
.split('/')
|
||||
.slice(0, -1)
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
pushFile(file, segments);
|
||||
});
|
||||
|
||||
if (!results.length) {
|
||||
throw new Error('No files detected in drop payload.');
|
||||
}
|
||||
|
||||
console.info('[Uploads] prepared files', results.length);
|
||||
|
||||
return results;
|
||||
}, []);
|
||||
|
||||
|
||||
const handleFileDrop = useCallback(
|
||||
async (dataTransfer, targetFolderId) => {
|
||||
if (!token) {
|
||||
|
||||
Reference in New Issue
Block a user