10 KiB
Справочник кода Upload Platform
Документ описывает актуальную picker-only реализацию. VM upload, backend sessions и API загрузки являются legacy и в текущем коде не используются.
Архитектура
site/app.py
-> site/templates/index.html
-> dist/file-picker.iife.js
-> upload/frontend/index.js
-> table/on_files_change.js
-> table/on_folder_change.js
-> table/add_file_with_dedup.js
-> table/render.js
-> zip/list_zip_files.js
Flask отдаёт страницу и ES-модули. Все выбранные файлы остаются в памяти браузера; сервер не принимает содержимое файлов.
Модель данных
Каждый узел дерева имеет поля:
| Поле | Тип | Назначение |
|---|---|---|
id |
string |
Уникальный идентификатор для DOM-кнопок и поиска. |
kind |
file, folder, zip |
Тип узла. file является leaf. |
name |
string |
Имя отображения в дереве. |
path |
string |
Полный логический путь внутри выбора/архива. |
file |
File | null |
Исходный или созданный из ZIP browser File для leaf. |
children |
Array |
Дочерние узлы группы. |
expanded |
boolean |
Видимость дочерних строк в таблице. |
Внутреннее состояние picker-а:
| Поле | Назначение |
|---|---|
nodes |
Корневые узлы дерева. |
fileKeys |
Set ключей path + NUL + size для дедупликации. |
busy |
Защита от параллельной обработки async change-событий. |
upload/frontend/table
add_file_with_dedup.js
| Функция | Вход | Выход | Побочные эффекты |
|---|---|---|---|
addFileWithDedup(state, fileNode) |
state и корневой file/folder/zip-узел | boolean |
Фильтрует дубли и пустые группы, добавляет узел и ключи в state. |
Дедупликация выполняется рекурсивно. Один и тот же путь с другим размером не считается дублем.
esc.js
| Функция | Вход | Выход |
|---|---|---|
esc(value) |
Любое значение | HTML-экранированная строка |
Используется перед innerHTML, data-path, aria-label и текстом имени.
fs.js
| Функция | Вход | Выход |
|---|---|---|
fs(bytes) |
Размер в байтах | Строка в B, KB или MB |
rebase_tree.js
| Функция | Вход | Выход | Побочные эффекты |
|---|---|---|---|
rebaseTree(root, prefix) |
Дерево и префикс папки | То же дерево | Рекурсивно меняет path; для leaf создаёт File с новым именем. |
Модуль является единственной реализацией rebasing для ZIP внутри выбранной папки и для nested ZIP.
on_files_change.js
| Функция | Вход | Выход | Назначение |
|---|---|---|---|
addFiles(state, cfg, files, elements) |
state, allowedExt, FileList/Array, DOM |
Promise<void> |
Фильтрует обычные файлы, раскрывает ZIP, вызывает render. |
onFilesChange(state, cfg, elements) |
state, config, DOM | async callback | Блокирует повторные события через busy и вызывает addFiles. |
Ошибка отдельного ZIP пропускается и передаётся в cfg.onError(error, file), если
callback задан. finally обязательно освобождает busy.
on_folder_change.js
| Функция | Вход | Выход | Назначение |
|---|---|---|---|
onFolderChange(state, cfg, elements) |
state, allowedExt, folder input и DOM |
async callback | Строит дерево по webkitRelativePath, раскрывает ZIP и рендерит. |
Корень пути становится folder-узлом. Вложенные папки создаются по сегментам пути, затем корень проходит общую дедупликацию.
render.js
| Функция | Вход | Выход | Назначение |
|---|---|---|---|
renderNode(node, depth) |
Узел и глубина | HTML | Рекурсивно создаёт строки дерева. Внутренняя функция. |
render(state, elements) |
state и DOM | void |
Перерисовывает таблицу и счётчик. |
findNode(nodes, id) |
Массив узлов и id | {node, nodes} или null |
Ищет узел и его родительский массив. |
flattenFiles(nodes, result) |
Дерево и аккумулятор | Массив leaf-файлов | Преобразует дерево для API приложения. |
render считает все leaf-файлы, включая свернутые группы. renderNode показывает
дочерние строки только при expanded === true.
init_upload_table.js (legacy)
| Функция | Вход | Выход | Назначение |
|---|---|---|---|
removeFileMeta(state, node) |
state и удаляемое поддерево | void |
Рекурсивно удаляет dedup-ключи. Внутренняя функция. |
removeNode(state, id) |
state и id | void |
Удаляет узел из родительского массива. Внутренняя функция. |
initUploadTable(cfg) |
allowedExt и четыре DOM-элемента |
API-объект | Создаёт state, события, render и публичные операции. |
Публичный API legacy-совместимости:
| Метод | Вход | Результат |
|---|---|---|
pickFiles() |
нет | Открывает обычный file input. |
pickFolder() |
нет | Открывает folder input. |
addFiles(files) |
FileList/Array | Добавляет файлы с фильтрацией и ZIP-разбором. |
getFiles() |
нет | {path, name, size, file}[] только для leaf. |
remove(id) |
id узла | Удаляет узел и его dedup-ключи. |
render() |
нет | Перерисовывает текущий state. |
clear() |
нет | Очищает дерево и dedup Set. |
upload/frontend/zip
list_zip_files.js
| Функция | Вход | Выход | Назначение |
|---|---|---|---|
extensionAllowed(name, allowedExt) |
Имя и расширения | boolean | Проверяет расширение. Внутренняя. |
makeFile(data, name) |
Байты и путь | File |
Создаёт browser File. Внутренняя. |
safeEntryParts(entryName) |
Сырой ZIP-путь | сегменты или null | Отбрасывает traversal и опасные пути. Внутренняя. |
node(kind, name, path, children, file) |
Метаданные узла | node | Создаёт узел. Внутренняя. |
addPath(root, parts, fileNode) |
Дерево, сегменты, узел | void |
Создаёт папки и вставляет узел. Внутренняя. |
listEntries(data, zipName, allowedExt, depth) |
ZIP bytes и контекст | `Promise<node | null>` |
listZipFiles(file, allowedExt, limits) |
browser File, расширения и limits | `Promise<node | null>` |
Безопасность: отклоняются абсолютные пути, backslash, пустые сегменты, . и
..; применяются лимиты maxEntries, maxTotalBytes, maxEntryBytes и
maxDepth с безопасными значениями по умолчанию. ZIP без разрешённых leaf-файлов
возвращает null и не создаёт пустую группу.
upload/frontend/index.js
initFilePicker({ mount, allowedExt, labels, layout, limits, onChange, onError })
строит весь DOM внутри HTMLElement или CSS-селектора mount, подключает inputs,
таблицу и обработчики. Возвращаемый API: pickFiles, pickFolder, addFiles,
getFiles, remove, clear, render, destroy. getFiles и onChange
возвращают копии массива leaf-файлов. destroy снимает слушатели и очищает mount.
IIFE-бандл экспортирует этот API как FilePicker.initFilePicker; ESM-бандл
экспортирует функцию напрямую. fflate включён в оба bundle, поэтому глобальный
vendor script не требуется.
Flask и HTML
site/app.py
| Маршрут/объект | Вход | Выход |
|---|---|---|
CONFIG |
config.json |
JSON-конфигурация picker |
VERSION |
константа | Версия страницы |
index() / / |
HTTP GET | HTML с version и config |
health() / /health |
HTTP GET | ok, HTTP 200 |
upload_frontend(filename) |
HTTP GET и относительный путь | Legacy-файл из upload/frontend |
file_picker_dist(filename) |
HTTP GET и имя bundle | Bundle из dist |
site/templates/index.html
Шаблон создаёт mount, подключает IIFE bundle с query-параметром версии для
сброса browser cache и вызывает FilePicker.initFilePicker.
Legacy
upload/frontend/table/set_status.js не импортируется актуальной страницей и
сохранён как legacy-заготовка. Vendor site/static/vendor/fflate.min.js не
документируется построчно: это внешняя библиотека, используемая как готовый
runtime dependency.