/** * Добавляет узел файла или группы в состояние picker с рекурсивной дедупликацией. * * @param {{nodes: Array, fileKeys: Set}} state Внутреннее состояние таблицы. * @param {{kind: string, path: string, file?: File, children?: Array}} fileNode * Корневой узел файла, папки или ZIP-архива. * @returns {boolean} True, если в состояние добавлен хотя бы один узел. * * Ключ дедупликации состоит из полного пути и размера файла. Это позволяет * повторно выбрать файл после удаления и одновременно не скрывает файл с тем * же путём, но другим размером. */ export function addFileWithDedup(state, fileNode) { // Удаляет дубли и пустые группы снизу вверх, сохраняя исходные File objects. const accept = (node) => { if (node.kind === 'file') { // NUL-разделитель исключает неоднозначность при склейке пути и размера. const key = `${node.path}\u0000${node.file.size}`; if (state.fileKeys.has(key)) return null; state.fileKeys.add(key); return node; } // Группа нужна только пока после фильтрации в ней остался хотя бы один leaf. node.children = node.children.map(accept).filter(Boolean); return node.children.length ? node : null; }; const accepted = accept(fileNode); // Пустой ZIP/каталог не должен появляться в таблице как пустая строка. if (!accepted || (accepted.kind !== 'file' && !accepted.children.length)) return false; state.nodes.push(accepted); return true; }