Document picker code and architecture
This commit is contained in:
@@ -2,6 +2,13 @@ import { addFiles, onFilesChange } from './on_files_change.js';
|
||||
import { onFolderChange } from './on_folder_change.js';
|
||||
import { findNode, flattenFiles, render } from './render.js';
|
||||
|
||||
/**
|
||||
* Удаляет ключи дедупликации для leaf-файлов внутри удаляемого поддерева.
|
||||
*
|
||||
* @param {{fileKeys: Set<string>}} state Состояние дедупликации.
|
||||
* @param {{kind: string, path: string, file?: File, children?: Array}} node Удаляемый узел.
|
||||
* @returns {void}
|
||||
*/
|
||||
function removeFileMeta(state, node) {
|
||||
if (node.kind === 'file') {
|
||||
state.fileKeys.delete(`${node.path}\u0000${node.file.size}`);
|
||||
@@ -10,6 +17,13 @@ function removeFileMeta(state, node) {
|
||||
node.children.forEach((child) => removeFileMeta(state, child));
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаляет узел из дерева и синхронно освобождает его dedup-ключи.
|
||||
*
|
||||
* @param {{nodes: Array, fileKeys: Set<string>}} state Состояние таблицы.
|
||||
* @param {string} id Идентификатор узла.
|
||||
* @returns {void} Ничего не делает, если id не найден.
|
||||
*/
|
||||
function removeNode(state, id) {
|
||||
const found = findNode(state.nodes, id);
|
||||
if (!found) return;
|
||||
@@ -17,17 +31,34 @@ function removeNode(state, id) {
|
||||
found.nodes.splice(found.nodes.indexOf(found.node), 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Создаёт picker и возвращает его публичный API.
|
||||
*
|
||||
* @param {{allowedExt: string[], fileInputEl: HTMLInputElement,
|
||||
* folderInputEl: HTMLInputElement, tableBodyEl: HTMLElement, countEl: HTMLElement}} cfg
|
||||
* Конфигурация и обязательные DOM-элементы интегратора.
|
||||
* @returns {{pickFiles: Function, pickFolder: Function, addFiles: Function,
|
||||
* getFiles: Function, remove: Function, render: Function, clear: Function}}
|
||||
* Управляющий API без прямого доступа к внутреннему state.
|
||||
*
|
||||
* Функция регистрирует DOM-события один раз, хранит дерево и dedup Set внутри
|
||||
* замыкания и сразу рисует пустое состояние. Внешнее приложение получает только
|
||||
* операции выбора, добавления, чтения и удаления.
|
||||
*/
|
||||
export function initUploadTable(cfg) {
|
||||
// Нормализованный набор DOM-ссылок передаётся во все функции рендера.
|
||||
const elements = {
|
||||
fileInputEl: cfg.fileInputEl,
|
||||
folderInputEl: cfg.folderInputEl,
|
||||
tableBodyEl: cfg.tableBodyEl,
|
||||
countEl: cfg.countEl,
|
||||
};
|
||||
// nodes — дерево; fileKeys — ключи path+NUL+size; busy защищает async change handlers.
|
||||
const state = { nodes: [], fileKeys: new Set(), busy: false };
|
||||
elements.fileInputEl.addEventListener('change', onFilesChange(state, cfg, elements));
|
||||
elements.folderInputEl.addEventListener('change', onFolderChange(state, cfg, elements));
|
||||
elements.tableBodyEl.addEventListener('click', (event) => {
|
||||
// Делегирование событий позволяет обслуживать динамически созданные кнопки.
|
||||
const toggle = event.target.closest('[data-toggle]');
|
||||
if (toggle) {
|
||||
const found = findNode(state.nodes, toggle.dataset.toggle);
|
||||
@@ -43,9 +74,12 @@ export function initUploadTable(cfg) {
|
||||
});
|
||||
|
||||
const api = {
|
||||
// Открывают системные диалоги, не обходя браузерные ограничения File API.
|
||||
pickFiles: () => elements.fileInputEl.click(),
|
||||
pickFolder: () => elements.folderInputEl.click(),
|
||||
// Программное добавление использует тот же фильтр и ZIP-парсер, что и input.
|
||||
addFiles: (files) => addFiles(state, cfg, files, elements),
|
||||
// Возвращает только leaf-файлы; группы и внутренний state наружу не выдаются.
|
||||
getFiles: () => flattenFiles(state.nodes),
|
||||
remove: (id) => {
|
||||
removeNode(state, id);
|
||||
@@ -53,6 +87,7 @@ export function initUploadTable(cfg) {
|
||||
},
|
||||
render: () => render(state, elements),
|
||||
clear: () => {
|
||||
// Очистка сбрасывает и дерево, и dedup Set, чтобы повторный выбор был возможен.
|
||||
state.nodes = [];
|
||||
state.fileKeys.clear();
|
||||
render(state, elements);
|
||||
|
||||
Reference in New Issue
Block a user