этап 2: переиспользуемый модуль upload (sink) вместо рукописного транспорта
- копирую модуль upload/ из drhider (слои 1-2) - blueprint: параметр sink (drhider-сессия по умолчанию, сверка — DB+парсинг) - upload_bp: contracts_upload_sink = _store_and_parse - routes: регистрирую create_upload_refs_blueprint(cfg, sink=...) - app.py: корень репо в sys.path (для import upload) - History: план переиспользования + ревью Соннета
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
// initUploadTable — сборка слоя 1 (выбор файлов/папки/архива).
|
||||
// Состояние (files/fileMeta/overNames) живёт внутри модуля.
|
||||
|
||||
import { addFiles as addFilesToState, makeFilesChangeHandler } from './on_files_change.js';
|
||||
import { makeFolderChangeHandler } from './on_folder_change.js';
|
||||
import { render } from './render.js';
|
||||
import { setStatus } from './set_status.js';
|
||||
import { rmFile } from './rm_file.js';
|
||||
|
||||
// cfg: {allowedExt, maxFileBytes, maxSessionBytes, estMbSec}
|
||||
// els: {fileInput, folderInput, tableBody, countEl, uploadBtnEl, onStatus(cls, text)}
|
||||
// returns api (см. README.md)
|
||||
export function initUploadTable(cfg, els) {
|
||||
const state = {
|
||||
files: [], // File[]
|
||||
fileMeta: new Map(), // имя -> {size, mtime} для дедупа/суффиксов
|
||||
overNames: new Set(), // имена файлов сверх лимита (не участвуют)
|
||||
busy: false, // идёт загрузка/обработка — список заблокирован
|
||||
proc: null, // {phase, procState, procExtractRate} — задаёт слой 3
|
||||
};
|
||||
|
||||
const onFilesChange = makeFilesChangeHandler({ state, cfg, els, onStatus: els.onStatus });
|
||||
const onFolderChange = makeFolderChangeHandler({ state, cfg, els, onStatus: els.onStatus });
|
||||
|
||||
els.fileInput.addEventListener('change', onFilesChange);
|
||||
els.folderInput.addEventListener('change', onFolderChange);
|
||||
// Делегирование кликов по кнопкам «✕» (удалить строку)
|
||||
els.tableBody.addEventListener('click', e => {
|
||||
const btn = e.target.closest('.remove-btn');
|
||||
if (btn) rmFile(state, els, cfg, Number(btn.dataset.idx));
|
||||
});
|
||||
|
||||
return {
|
||||
state, // доступ для слоя 3 (установить state.proc для render)
|
||||
addFiles(files) { return addFilesToState(state, cfg, files, els, els.onStatus); },
|
||||
getFiles() { // ТОЛЬКО учитываемые (без over): [{name, size, file}]
|
||||
return state.files.filter(f => !state.overNames.has(f.name))
|
||||
.map(f => ({ name: f.name, size: f.size, file: f }));
|
||||
},
|
||||
getOverNames() { return state.overNames; },
|
||||
setStatus(idx, html) { setStatus(idx, html); },
|
||||
render() { render(state, els, cfg); },
|
||||
clear() {
|
||||
state.files = [];
|
||||
state.fileMeta = new Map();
|
||||
state.overNames = new Set();
|
||||
if (els.fileInput) els.fileInput.value = '';
|
||||
render(state, els, cfg);
|
||||
},
|
||||
setBusy(b) {
|
||||
state.busy = b;
|
||||
if (els.fileInput) els.fileInput.disabled = b;
|
||||
},
|
||||
_rm(i) { rmFile(state, els, cfg, i); },
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user