diff options
| author | dax <me@dax.ist> | 2026-08-05 17:15:39 +0100 |
|---|---|---|
| committer | dax <me@dax.ist> | 2026-08-05 17:15:39 +0100 |
| commit | 70ce625487b5e0b7540b70de2ae59f0086134a9e (patch) | |
| tree | 50db2fba097b2965046e965587bde5143d23785b /public/app.js | |
| parent | 03dcc91c96ac45ff005a1c3e14a71a5733037447 (diff) | |
admin import panel with upload progress + recent photos; app empty-state links to /admin
Diffstat (limited to 'public/app.js')
| -rw-r--r-- | public/app.js | 138 |
1 files changed, 0 insertions, 138 deletions
diff --git a/public/app.js b/public/app.js index 311faa9..746a9fa 100644 --- a/public/app.js +++ b/public/app.js @@ -4,26 +4,10 @@ const $ = (s) => document.querySelector(s) const stage = $("#stage") const world = $("#world") const importState = $("#import-state") -const importEl = $("#import") -const fileInput = $("#file") -const dropOverlay = $("#drop") const toastEl = $("#toast") -const progressEl = $("#progress") -const progressFill = $("#progress-fill") -const progressText = $("#progress-text") const splash = $("#splash") const startBtn = $("#start") -function adminToken() { - for (const store of [localStorage, sessionStorage]) { - try { - const raw = store.getItem("pathways_admin") - if (raw) { const t = JSON.parse(raw).token; if (t) return t } - } catch { /* ignore */ } - } - return null -} - addEventListener("error", (e) => { toast(`error: ${e.message || "script failed"}`, true) }) @@ -734,21 +718,6 @@ function hideImportState() { stage.classList.remove("blocked") } -async function initDirectHint() { - try { - const r = await fetch("/api/health") - const data = await r.json() - const d = data.direct || {} - const urls = [d.magic, d.lan, d.tailscale].filter(Boolean) - if (!urls.length) return - if (urls.some((u) => { try { return new URL(u).host === location.host } catch { return false } })) return - const link = $("#direct-link") - link.textContent = d.magic - link.href = d.magic - $("#direct-hint").hidden = false - } catch { /* ignore */ } -} - async function beginWalk() { if (started) return started = true @@ -786,7 +755,6 @@ let hasPhotos = false splash.hidden = true showImportState() } - initDirectHint() })() const MIN_READ = 4000 @@ -827,109 +795,3 @@ function toast(msg, isError = false) { clearTimeout(toastTimer) toastTimer = setTimeout(() => toastEl.classList.remove("show"), isError ? 7000 : 3500) } - -function plural(n) { - return n === 1 ? "photo" : "photos" -} - -function setProgress(mode, pct, text) { - progressEl.hidden = false - progressText.textContent = text - if (mode === "indeterminate") { - progressEl.classList.add("indeterminate") - } else { - progressEl.classList.remove("indeterminate") - progressFill.style.width = pct + "%" - } -} - -function hideProgress() { - progressEl.hidden = true - progressEl.classList.remove("indeterminate") - progressFill.style.width = "0%" -} - -function uploadWithProgress(form, onPct, onUploaded) { - return new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest() - xhr.open("POST", "/api/import") - const tok = adminToken() - if (tok) xhr.setRequestHeader("Authorization", "Bearer " + tok) - xhr.upload.addEventListener("progress", (e) => { - if (e.lengthComputable) onPct(Math.min(100, Math.round((e.loaded / e.total) * 100))) - }) - xhr.upload.addEventListener("load", () => onUploaded()) - xhr.addEventListener("load", () => { - let data = {} - try { data = JSON.parse(xhr.responseText) } catch { /* ignore */ } - if (xhr.status >= 200 && xhr.status < 300) resolve(data) - else reject(new Error(data.error || `import failed (${xhr.status})`)) - }) - xhr.addEventListener("error", () => reject(new Error("upload failed (network error)"))) - xhr.send(form) - }) -} - -async function importFiles(list) { - const files = Array.from(list || []).filter((f) => f.size > 0) - if (!files.length) return - const form = new FormData() - for (const f of files) form.append("photos", f, f.name) - importEl.classList.add("busy") - setProgress("determinate", 0, `uploading ${files.length} ${plural(files.length)}… 0%`) - try { - const data = await uploadWithProgress( - form, - (pct) => setProgress("determinate", pct, `uploading ${files.length} ${plural(files.length)}… ${pct}%`), - () => setProgress("indeterminate", 100, "indexing…") - ) - if (!data.ok) throw new Error(data.error || "import failed") - exitsCache.clear() - exitPending.clear() - hideProgress() - hideImportState() - await beginWalk() - toast(`imported ${data.imported} ${plural(data.imported)} - keep walking to find them`) - } catch (err) { - hideProgress() - toast(err.message || "import failed", true) - } finally { - importEl.classList.remove("busy") - fileInput.value = "" - } -} - -function hasFiles(e) { - return Array.from(e.dataTransfer?.types || []).includes("Files") -} - - -fileInput.addEventListener("change", () => importFiles(fileInput.files)) - -let dragDepth = 0 - -addEventListener("dragenter", (e) => { - if (!hasFiles(e)) return - e.preventDefault() - dragDepth++ - dropOverlay.classList.add("show") -}) - -addEventListener("dragover", (e) => { - if (!hasFiles(e)) return - e.preventDefault() - e.dataTransfer.dropEffect = "copy" -}) - -addEventListener("dragleave", () => { - dragDepth = Math.max(0, dragDepth - 1) - if (dragDepth === 0) dropOverlay.classList.remove("show") -}) - -addEventListener("drop", (e) => { - if (!e.dataTransfer) return - e.preventDefault() - dragDepth = 0 - dropOverlay.classList.remove("show") - importFiles(e.dataTransfer.files) -}) |
