67 lines
3.5 KiB
JavaScript
67 lines
3.5 KiB
JavaScript
import { listZipFiles } from '../zip/list_zip_files.js';
|
|
import { addFileWithDedup } from './add_file_with_dedup.js';
|
|
import { render } from './render.js';
|
|
|
|
/**
|
|
* Обрабатывает список файлов из обычного file input.
|
|
*
|
|
* @param {{fileKeys: Set<string>, nodes: Array, busy: boolean}} state Состояние picker.
|
|
* @param {{allowedExt: string[]}} cfg Конфигурация разрешённых расширений.
|
|
* @param {FileList|File[]} files Выбранные browser File objects.
|
|
* @param {{tableBodyEl: HTMLElement, countEl: HTMLElement}} elements DOM-элементы вывода.
|
|
* @returns {Promise<void>} Завершается после разбора всех ZIP и перерисовки таблицы.
|
|
*
|
|
* Обычные документы добавляются сразу. ZIP читаются асинхронно в браузере;
|
|
* ошибка одного архива не отменяет обработку остальных выбранных файлов.
|
|
*/
|
|
export async function addFiles(state, cfg, files, elements) {
|
|
for (const file of Array.from(files)) {
|
|
if (!file.name.toLowerCase().endsWith('.zip')) {
|
|
// Фильтр повторяет accept в HTML, потому что accept не является защитой API.
|
|
if (!cfg.allowedExt.some((extension) => file.name.toLowerCase().endsWith(extension.toLowerCase()))) {
|
|
continue;
|
|
}
|
|
// Обычный файл получает имя как путь: у file input нет относительного пути.
|
|
addFileWithDedup(state, {
|
|
id: crypto.randomUUID(), kind: 'file', name: file.name, path: file.name,
|
|
file, children: [], expanded: true,
|
|
});
|
|
continue;
|
|
}
|
|
try {
|
|
// listZipFiles возвращает дерево только с разрешёнными leaf-файлами.
|
|
const zipTree = await listZipFiles(file, cfg.allowedExt, cfg.limits);
|
|
if (zipTree) addFileWithDedup(state, zipTree);
|
|
} catch (error) {
|
|
// Битый или небезопасный ZIP пропускается, чтобы не блокировать picker.
|
|
if (typeof cfg.onError === 'function') cfg.onError(error, file);
|
|
continue;
|
|
}
|
|
}
|
|
// Единая перерисовка после всей пачки предотвращает промежуточные состояния UI.
|
|
render(state, elements, cfg);
|
|
}
|
|
|
|
/**
|
|
* Создаёт обработчик change для обычного выбора файлов.
|
|
*
|
|
* @param {object} state Состояние picker, включая флаг busy.
|
|
* @param {object} cfg Конфигурация picker.
|
|
* @param {object} elements DOM-элементы picker.
|
|
* @returns {Function} Async-обработчик для addEventListener('change', ...).
|
|
*
|
|
* busy устанавливается синхронно до первого await. Поэтому второе быстрое
|
|
* событие выбора не запускает параллельный разбор и не меняет дерево конкурирующе.
|
|
*/
|
|
export function onFilesChange(state, cfg, elements) {
|
|
return async () => {
|
|
if (state.busy) return;
|
|
state.busy = true;
|
|
try {
|
|
await addFiles(state, cfg, elements.fileInputEl.files, elements);
|
|
} finally {
|
|
// Даже исключение вне внутреннего ZIP-catch не оставляет picker заблокированным.
|
|
state.busy = false;
|
|
}
|
|
};
|
|
} |