frontend: fix uploads
ci / docker (backend, backend/Dockerfile, backend) (push) Successful in 19s
ci / docker (frontend, frontend/Dockerfile, frontend) (push) Successful in 33s

This commit is contained in:
2025-10-12 16:01:28 +02:00
parent 111d2cada3
commit 9cd69dc00c
+101 -101
View File
@@ -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) {