Add hierarchical file picker

This commit is contained in:
“Naeel”
2026-09-05 09:29:03 +03:00
parent 85605fe8c6
commit a965f181d9
11 changed files with 181 additions and 97 deletions
+38 -8
View File
@@ -1,4 +1,4 @@
// Рекурсивно получить из ZIP только файлы с разрешёнными расширениями.
// Построить дерево ZIP с файлами только разрешённых расширений.
function extensionAllowed(name, allowedExt) {
const lowerName = name.toLowerCase();
@@ -9,24 +9,54 @@ function makeFile(data, name) {
return new File([data], name);
}
async function listEntries(data, prefix, allowedExt, depth) {
function node(kind, name, path, children = [], file = null) {
return { id: crypto.randomUUID(), kind, name, path, children, file, expanded: true };
}
function rebaseTree(root, prefix) {
root.path = `${prefix}/${root.path}`;
if (root.file) root.file = new File([root.file], root.path, { lastModified: root.file.lastModified });
root.children.forEach((child) => rebaseTree(child, prefix));
return root;
}
function addPath(root, parts, fileNode) {
let current = root;
parts.forEach((part, index) => {
const last = index === parts.length - 1;
let child = current.children.find((item) => item.name === part);
if (!child) {
child = last
? fileNode
: node('folder', part, `${current.path}/${part}`);
current.children.push(child);
}
current = child;
});
}
async function listEntries(data, zipName, allowedExt, depth) {
if (depth > 20) throw new Error('Слишком глубокая вложенность ZIP');
const entries = fflate.unzipSync(data);
const files = [];
const root = node('zip', zipName, zipName);
for (const [entryName, entryData] of Object.entries(entries)) {
if (entryName.endsWith('/')) continue;
const path = prefix ? `${prefix}/${entryName}` : entryName;
if (entryName.toLowerCase().endsWith('.zip')) {
files.push(...await listEntries(entryData, path, allowedExt, depth + 1));
const nested = await listEntries(entryData, entryName, allowedExt, depth + 1);
rebaseTree(nested, zipName);
nested.name = entryName.split('/').pop();
addPath(root, entryName.split('/'), nested);
} else if (extensionAllowed(entryName, allowedExt)) {
files.push(makeFile(entryData, path));
const path = `${zipName}/${entryName}`;
const file = makeFile(entryData, path);
addPath(root, entryName.split('/'), node('file', entryName.split('/').pop(), path, [], file));
}
}
return files;
return root;
}
export async function listZipFiles(file, allowedExt) {
const data = new Uint8Array(await file.arrayBuffer());
return listEntries(data, '', allowedExt, 0);
return listEntries(data, file.name, allowedExt, 0);
}