feat: make file picker embeddable

This commit is contained in:
“Naeel”
2026-09-05 14:42:50 +03:00
parent aec8ad4fbe
commit 3d227f1daa
18 changed files with 3005 additions and 88 deletions
+27 -12
View File
@@ -8,7 +8,8 @@
```text
site/app.py
-> site/templates/index.html
-> upload/frontend/table/init_upload_table.js
-> 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
@@ -33,7 +34,7 @@ Flask отдаёт страницу и ES-модули. Все выбранны
| `children` | `Array` | Дочерние узлы группы. |
| `expanded` | `boolean` | Видимость дочерних строк в таблице. |
Внутреннее состояние `initUploadTable`:
Внутреннее состояние picker-а:
| Поле | Назначение |
|---|---|
@@ -82,7 +83,8 @@ Flask отдаёт страницу и ES-модули. Все выбранны
| `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 пропускается. `finally` обязательно освобождает `busy`.
Ошибка отдельного ZIP пропускается и передаётся в `cfg.onError(error, file)`, если
callback задан. `finally` обязательно освобождает `busy`.
### `on_folder_change.js`
@@ -105,7 +107,7 @@ Flask отдаёт страницу и ES-модули. Все выбранны
`render` считает все leaf-файлы, включая свернутые группы. `renderNode` показывает
дочерние строки только при `expanded === true`.
### `init_upload_table.js`
### `init_upload_table.js` (legacy)
| Функция | Вход | Выход | Назначение |
|---|---|---|---|
@@ -113,7 +115,7 @@ Flask отдаёт страницу и ES-модули. Все выбранны
| `removeNode(state, id)` | state и id | `void` | Удаляет узел из родительского массива. Внутренняя функция. |
| `initUploadTable(cfg)` | `allowedExt` и четыре DOM-элемента | API-объект | Создаёт state, события, render и публичные операции. |
Публичный API:
Публичный API legacy-совместимости:
| Метод | Вход | Результат |
|---|---|---|
@@ -137,11 +139,24 @@ Flask отдаёт страницу и ES-модули. Все выбранны
| `node(kind, name, path, children, file)` | Метаданные узла | node | Создаёт узел. Внутренняя. |
| `addPath(root, parts, fileNode)` | Дерево, сегменты, узел | `void` | Создаёт папки и вставляет узел. Внутренняя. |
| `listEntries(data, zipName, allowedExt, depth)` | ZIP bytes и контекст | `Promise<node|null>` | Рекурсивно обрабатывает ZIP. Внутренняя. |
| `listZipFiles(file, allowedExt)` | browser File и расширения | `Promise<node|null>` | Публичная точка входа ZIP-парсера. |
| `listZipFiles(file, allowedExt, limits)` | browser File, расширения и limits | `Promise<node|null>` | Публичная точка входа ZIP-парсера. |
Безопасность: отклоняются абсолютные пути, backslash, пустые сегменты, `.` и
`..`; глубина nested ZIP ограничена 20. ZIP без разрешённых leaf-файлов возвращает
`null` и не создаёт пустую группу.
`..`; применяются лимиты `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
@@ -153,13 +168,13 @@ Flask отдаёт страницу и ES-модули. Все выбранны
| `VERSION` | константа | Версия страницы |
| `index()` / `/` | HTTP GET | HTML с version и config |
| `health()` / `/health` | HTTP GET | `ok`, HTTP 200 |
| `upload_frontend(filename)` | HTTP GET и относительный путь | ES/CSS-файл из `upload/frontend` |
| `upload_frontend(filename)` | HTTP GET и относительный путь | Legacy-файл из `upload/frontend` |
| `file_picker_dist(filename)` | HTTP GET и имя bundle | Bundle из `dist` |
### `site/templates/index.html`
Шаблон создаёт два скрытых input: обычный file input и `webkitdirectory`,
передаёт четыре DOM-элемента в `initUploadTable`, подключает локальный `fflate`
до ES-модуля и связывает три кнопки с `pickFiles`, `pickFolder` и `clear`.
Шаблон создаёт mount, подключает IIFE bundle с query-параметром версии для
сброса browser cache и вызывает `FilePicker.initFilePicker`.
## Legacy