Initial commit
Photo-based book cataloger with AI identification. Room → Cabinet → Shelf → Book hierarchy; FastAPI + SQLite backend; vanilla JS SPA; OpenAI-compatible plugin system for boundary detection, text recognition, and archive search.
This commit is contained in:
82
static/js/init.js
Normal file
82
static/js/init.js
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* init.js
|
||||
* Application bootstrap: full render, partial detail re-render, config and
|
||||
* tree loading, batch-status polling, and the initial Promise.all boot call.
|
||||
*
|
||||
* render() is the single source of truth for full repaints — it replaces
|
||||
* #app innerHTML, re-attaches editables, reinitialises Sortable instances,
|
||||
* and (on desktop) schedules the boundary canvas setup.
|
||||
*
|
||||
* renderDetail() does a cheaper in-place update of the right panel only,
|
||||
* used during plugin runs and field edits to avoid re-rendering the sidebar.
|
||||
*
|
||||
* Depends on: S, _plugins, _batchState, _batchPollTimer (state.js);
|
||||
* req, toast (api.js / helpers.js); isDesktop (helpers.js);
|
||||
* vApp, vDetailBody, mainTitle, mainHeaderBtns, vBatchBtn
|
||||
* (tree-render.js / detail-render.js);
|
||||
* attachEditables, initSortables (editing.js);
|
||||
* setupDetailCanvas (canvas-boundary.js)
|
||||
* Provides: render(), renderDetail(), loadConfig(), startBatchPolling(),
|
||||
* loadTree()
|
||||
*/
|
||||
|
||||
// ── Full re-render ────────────────────────────────────────────────────────────
|
||||
function render() {
|
||||
if (document.activeElement?.contentEditable === 'true') return;
|
||||
const sy = window.scrollY;
|
||||
document.getElementById('app').innerHTML = vApp();
|
||||
window.scrollTo(0, sy);
|
||||
attachEditables();
|
||||
initSortables();
|
||||
if (isDesktop()) requestAnimationFrame(setupDetailCanvas);
|
||||
}
|
||||
|
||||
// ── Right-panel partial re-render ─────────────────────────────────────────────
|
||||
// Used during plugin runs and field edits to avoid re-rendering the sidebar.
|
||||
function renderDetail() {
|
||||
const body = document.getElementById('main-body');
|
||||
if (body) body.innerHTML = vDetailBody();
|
||||
const t = document.getElementById('main-title');
|
||||
if (t) t.innerHTML = mainTitle(); // innerHTML: mainTitle() returns an HTML span
|
||||
const hb = document.getElementById('main-hdr-btns');
|
||||
if (hb) hb.innerHTML = mainHeaderBtns();
|
||||
const bb = document.getElementById('main-hdr-batch');
|
||||
if (bb) bb.innerHTML = vBatchBtn();
|
||||
attachEditables(); // pick up the new editable span in the header
|
||||
requestAnimationFrame(setupDetailCanvas);
|
||||
}
|
||||
|
||||
// ── Data loading ──────────────────────────────────────────────────────────────
|
||||
async function loadConfig() {
|
||||
try {
|
||||
const cfg = await req('GET','/api/config');
|
||||
window._grabPx = cfg.boundary_grab_px ?? 14;
|
||||
window._confidenceThreshold = cfg.confidence_threshold ?? 0.8;
|
||||
_plugins = cfg.plugins || [];
|
||||
} catch { window._grabPx = 14; window._confidenceThreshold = 0.8; }
|
||||
}
|
||||
|
||||
function startBatchPolling() {
|
||||
if (_batchPollTimer) clearInterval(_batchPollTimer);
|
||||
_batchPollTimer = setInterval(async () => {
|
||||
try {
|
||||
const st = await req('GET', '/api/batch/status');
|
||||
_batchState = st;
|
||||
const bb = document.getElementById('main-hdr-batch');
|
||||
if (bb) bb.innerHTML = vBatchBtn();
|
||||
if (!st.running) {
|
||||
clearInterval(_batchPollTimer); _batchPollTimer = null;
|
||||
toast(`Batch: ${st.done} done, ${st.errors} errors`);
|
||||
await loadTree();
|
||||
}
|
||||
} catch { /* ignore poll errors */ }
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
async function loadTree() {
|
||||
S.tree = await req('GET','/api/tree');
|
||||
render();
|
||||
}
|
||||
|
||||
// ── Init ──────────────────────────────────────────────────────────────────────
|
||||
Promise.all([loadConfig(), loadTree()]);
|
||||
Reference in New Issue
Block a user