54 lines
2.7 KiB
JavaScript
54 lines
2.7 KiB
JavaScript
/**
|
|
* Добавляет узел файла или группы в состояние picker с рекурсивной дедупликацией.
|
|
*
|
|
* @param {{nodes: Array, fileKeys: Set<string>}} 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;
|
|
if (accepted.kind !== 'file') {
|
|
const existing = state.nodes.find((node) => node.kind !== 'file' && node.path === accepted.path);
|
|
if (existing) {
|
|
mergeChildren(existing, accepted);
|
|
return true;
|
|
}
|
|
}
|
|
state.nodes.push(accepted);
|
|
return true;
|
|
}
|
|
|
|
function mergeChildren(target, incoming) {
|
|
incoming.children.forEach((child) => {
|
|
if (child.kind === 'file') {
|
|
const duplicate = target.children.some((existing) => existing.kind === 'file'
|
|
&& existing.path === child.path && existing.file.size === child.file.size);
|
|
if (!duplicate) target.children.push(child);
|
|
return;
|
|
}
|
|
const existing = target.children.find((candidate) => candidate.kind !== 'file'
|
|
&& candidate.path === child.path);
|
|
if (existing) mergeChildren(existing, child);
|
|
else target.children.push(child);
|
|
});
|
|
} |