aboutsummaryrefslogtreecommitdiff
path: root/public/app.js
diff options
context:
space:
mode:
authordax <me@dax.ist>2026-08-05 17:15:39 +0100
committerdax <me@dax.ist>2026-08-05 17:15:39 +0100
commit70ce625487b5e0b7540b70de2ae59f0086134a9e (patch)
tree50db2fba097b2965046e965587bde5143d23785b /public/app.js
parent03dcc91c96ac45ff005a1c3e14a71a5733037447 (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.js138
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)
-})