feat: make file picker embeddable
This commit is contained in:
+27
-12
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user