aboutsummaryrefslogtreecommitdiff
path: root/public/admin.js
diff options
context:
space:
mode:
Diffstat (limited to 'public/admin.js')
-rw-r--r--public/admin.js87
1 files changed, 87 insertions, 0 deletions
diff --git a/public/admin.js b/public/admin.js
index fa74635..0540236 100644
--- a/public/admin.js
+++ b/public/admin.js
@@ -293,9 +293,82 @@ async function enterDash() {
show("dash")
try {
await loadStatus()
+ await loadRecent()
} catch (err) { if (views.dash.hidden) return; }
}
+async function loadRecent() {
+ const data = await api("/api/admin/photos?recent=1&limit=12")
+ const grid = $("#im-new")
+ grid.replaceChildren()
+ for (const p of data.photos) {
+ const box = photoBox(p)
+ box.addEventListener("click", () => {
+ document.querySelectorAll(".tab").forEach((x) => x.classList.remove("active"))
+ document.querySelector('.tab[data-tab="graph"]').classList.add("active")
+ $("#tab-status").hidden = true
+ $("#tab-import").hidden = true
+ $("#tab-graph").hidden = false
+ loadGraph(p.id)
+ })
+ grid.appendChild(box)
+ }
+ $("#im-recent").hidden = data.photos.length === 0
+}
+
+function setImProgress(pct, text) {
+ $("#im-progress").hidden = false
+ $("#im-status").textContent = text
+ const fill = $("#im-fill")
+ if (pct === "indeterminate") fill.classList.add("indeterminate")
+ else {
+ fill.classList.remove("indeterminate")
+ fill.style.width = pct + "%"
+ }
+}
+
+function uploadImport(form) {
+ return new Promise((resolve, reject) => {
+ const xhr = new XMLHttpRequest()
+ xhr.open("POST", "/api/import")
+ xhr.setRequestHeader("Authorization", "Bearer " + getToken())
+ xhr.upload.addEventListener("progress", (e) => {
+ if (e.lengthComputable) setImProgress(Math.min(100, Math.round((e.loaded / e.total) * 100)))
+ })
+ xhr.upload.addEventListener("load", () => setImProgress("indeterminate", "indexing…"))
+ 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)
+ const dz = $("#dz")
+ dz.classList.add("busy")
+ $("#im-err").textContent = ""
+ try {
+ const data = await uploadImport(form)
+ toast(`imported ${data.imported} ${data.imported === 1 ? "photo" : "photos"}`)
+ await loadStatus()
+ await loadRecent()
+ } catch (err) {
+ $("#im-err").textContent = err.message
+ } finally {
+ dz.classList.remove("busy")
+ $("#im-progress").hidden = true
+ $("#im-files").value = ""
+ }
+}
+
async function init() {
document.querySelectorAll(".tab").forEach((b) => {
b.addEventListener("click", () => {
@@ -303,7 +376,9 @@ async function init() {
b.classList.add("active")
const tab = b.dataset.tab
$("#tab-status").hidden = tab !== "status"
+ $("#tab-import").hidden = tab !== "import"
$("#tab-graph").hidden = tab !== "graph"
+ if (tab === "import") loadRecent().catch((e2) => toast(e2.message))
})
})
@@ -365,6 +440,18 @@ async function init() {
$("#gq").addEventListener("input", () => debounce(runGraphSearch, 220))
+ const dz = $("#dz")
+ dz.addEventListener("click", () => $("#im-files").click())
+ dz.addEventListener("keydown", (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); $("#im-files").click() } })
+ $("#im-files").addEventListener("change", () => importFiles($("#im-files").files))
+ dz.addEventListener("dragover", (e) => { e.preventDefault(); dz.classList.add("drag") })
+ dz.addEventListener("dragleave", () => dz.classList.remove("drag"))
+ dz.addEventListener("drop", (e) => {
+ e.preventDefault()
+ dz.classList.remove("drag")
+ importFiles(e.dataTransfer.files)
+ })
+
const token = getToken()
if (token) {
try {