frontend: fix uploads
This commit is contained in:
+101
-101
@@ -3050,122 +3050,122 @@ const AppLayout = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const items = Array.from(dataTransfer.items || []);
|
const items = Array.from(dataTransfer.items || []);
|
||||||
const results = [];
|
console.info('[Uploads] drop start', { items: items.length, files: (dataTransfer.files || []).length });
|
||||||
|
|
||||||
const pushFile = (file, ancestors) => {
|
const results = [];
|
||||||
if (file) {
|
const seenKeys = new Set();
|
||||||
results.push({
|
|
||||||
file,
|
const pushFile = (file, ancestors = []) => {
|
||||||
segments: (ancestors || []).filter(Boolean),
|
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 =
|
await Promise.all(
|
||||||
window.isSecureContext &&
|
items.map(async (item, index) => {
|
||||||
items.some((item) => typeof item.getAsFileSystemHandle === 'function');
|
if (item.kind !== 'file') return;
|
||||||
const supportsWebkitEntries = items.some(
|
|
||||||
(item) => typeof item.webkitGetAsEntry === 'function',
|
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) {
|
Array.from(dataTransfer.files || []).forEach((file, index) => {
|
||||||
const walkDirectoryHandle = async (handle, ancestors) => {
|
if (!file) return;
|
||||||
const nextAncestors = handle.name ? [...ancestors, handle.name] : [...ancestors];
|
// FileList entry suppressed
|
||||||
for await (const child of handle.values()) {
|
const relativePath =
|
||||||
if (child.kind === 'file') {
|
typeof file.webkitRelativePath === 'string' ? file.webkitRelativePath : '';
|
||||||
const file = await child.getFile();
|
const segments = relativePath
|
||||||
pushFile(file, nextAncestors);
|
? relativePath
|
||||||
} else if (child.kind === 'directory') {
|
.split('/')
|
||||||
// eslint-disable-next-line no-await-in-loop
|
.slice(0, -1)
|
||||||
await walkDirectoryHandle(child, nextAncestors);
|
.filter(Boolean)
|
||||||
}
|
: [];
|
||||||
}
|
pushFile(file, segments);
|
||||||
};
|
});
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!results.length) {
|
if (!results.length) {
|
||||||
throw new Error('No files detected in drop payload.');
|
throw new Error('No files detected in drop payload.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.info('[Uploads] prepared files', results.length);
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
const handleFileDrop = useCallback(
|
const handleFileDrop = useCallback(
|
||||||
async (dataTransfer, targetFolderId) => {
|
async (dataTransfer, targetFolderId) => {
|
||||||
if (!token) {
|
if (!token) {
|
||||||
|
|||||||
Reference in New Issue
Block a user