aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordax <me@dax.ist>2026-08-04 10:26:40 +0000
committerdax <me@dax.ist>2026-08-04 10:26:40 +0000
commit56b3a602c8e992a2bc197844e1ab3de8743b8012 (patch)
tree42e4f1f9ee46e6e8e7bfe93dbaf93608371e65b4
parent8f9b940b875ee854a293c862405dc38ea8b643ff (diff)
Code review cleanup: cache placement/adjacency, drop dead code
- Cache choosePlacement and adjacentTile results per (id,dir); invalidate on addTile. Avoids O(n^3) placement scoring on every pointermove. - setCurrent now re-renders only affected navs (prev + new) instead of refreshAllNav; remove refreshAllNav entirely. - Add pointercancel handler so interrupted touch/trackpad gestures don't leave a stuck drag. - Remove dead code: placeRect, photoConnections + /api/photo route and THUMBS const on server, dangling info var in pointerup. - Trim .gitignore, remove .DS_Store, bump cache-bust to ?v=20.
-rw-r--r--.gitignore38
-rw-r--r--public/app.js38
-rw-r--r--public/index.html4
-rw-r--r--server.mjs62
4 files changed, 29 insertions, 113 deletions
diff --git a/.gitignore b/.gitignore
index fd3dbb5..6659201 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,36 +1,4 @@
-# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
-
-# dependencies
-/node_modules
-/.pnp
-.pnp.js
-.yarn/install-state.gz
-
-# testing
-/coverage
-
-# next.js
-/.next/
-/out/
-
-# production
-/build
-
-# misc
+node_modules/
.DS_Store
-*.pem
-
-# debug
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-
-# local env files
-.env*.local
-
-# vercel
-.vercel
-
-# typescript
-*.tsbuildinfo
-next-env.d.ts
+*.log
+.env*
diff --git a/public/app.js b/public/app.js
index 800a822..95f64d4 100644
--- a/public/app.js
+++ b/public/app.js
@@ -155,11 +155,6 @@ function bestPlacement(w, h, from, dir) {
return best ? { rect: best, cost: bestCost } : null
}
-function placeRect(w, h, from, dir) {
- const p = bestPlacement(w, h, from, dir)
- return p ? p.rect : null
-}
-
function addTile(info, rect) {
const el = document.createElement("div")
el.className = "tile"
@@ -177,18 +172,22 @@ function addTile(info, rect) {
requestAnimationFrame(() => el.classList.add("lock"))
tiles.set(info.id, { el, info, rect })
rectOf.set(info.id, rect)
+ placementCache.clear()
+ adjacencyCache.clear()
renderNav(info.id)
return el
}
function setCurrent(id, rect) {
+ const prevId = cur && cur.id
for (const t of tiles.values()) t.el.classList.remove("current")
cur = { id, rect }
tiles.get(id).el.classList.add("current")
if (!userZoomed) scale = worldScale()
center()
getExits(id)
- refreshAllNav()
+ if (prevId && prevId !== id) renderNav(prevId)
+ renderNav(id)
}
function renderNav(id) {
@@ -223,10 +222,6 @@ function renderNav(id) {
t.el.appendChild(nav)
}
-function refreshAllNav() {
- for (const id of tiles.keys()) if (exitsCache.has(id)) renderNav(id)
-}
-
async function getExits(id) {
if (exitsCache.has(id)) return exitsCache.get(id)
if (exitPending.has(id)) return exitPending.get(id)
@@ -251,7 +246,12 @@ async function getExits(id) {
return p
}
+const placementCache = new Map()
+const adjacencyCache = new Map()
+
function choosePlacement(id, dir) {
+ const key = id + ":" + dir
+ if (placementCache.has(key)) return placementCache.get(key)
const exits = exitsCache.get(id)
const ex = exits && exits.find((e) => e.dir === dir)
if (!ex) return null
@@ -290,10 +290,14 @@ function choosePlacement(id, dir) {
const best = scored[0]
const next = ex.next && !rectOf.has(ex.next.id) ? scored.find((s) => s.id === ex.next.id) : null
const chosen = next && next.cost - best.cost <= FIT_TOLERANCE ? next : best
- return { id: chosen.id, info: chosen.info, rect: chosen.rect }
+ const out = { id: chosen.id, info: chosen.info, rect: chosen.rect }
+ placementCache.set(key, out)
+ return out
}
function adjacentTile(id, dir) {
+ const key = id + ":" + dir
+ if (adjacencyCache.has(key)) return adjacencyCache.get(key)
const r = tiles.get(id).rect
let best = null
let bestDist = Infinity
@@ -311,7 +315,9 @@ function adjacentTile(id, dir) {
: Math.abs(q.y + q.h / 2 - (r.y + r.h / 2))
if (d < bestDist) { bestDist = d; best = t }
}
- return best ? best.info.id : null
+ const out = best ? best.info.id : null
+ adjacencyCache.set(key, out)
+ return out
}
function traverseFrom(id, dir) {
@@ -326,7 +332,6 @@ function traverseFrom(id, dir) {
hideGhost()
addTile(chosen.info, chosen.rect)
setCurrent(chosen.id, chosen.rect)
- refreshAllNav()
refreshAllZones()
}
@@ -448,11 +453,16 @@ stage.addEventListener("pointerup", (e) => {
world.style.transition = ""
if (wasDrag || !tile) return
const id = Number(tile.dataset.id)
- const info = tiles.get(id).info
if (cur && cur.id === id) return
setCurrent(id, tiles.get(id).rect)
})
+stage.addEventListener("pointercancel", () => {
+ if (!drag) return
+ drag = null
+ world.style.transition = ""
+})
+
let zoomTimer = 0
stage.addEventListener("wheel", (e) => {
e.preventDefault()
diff --git a/public/index.html b/public/index.html
index ee844d7..4c13a5a 100644
--- a/public/index.html
+++ b/public/index.html
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="dark">
<title>pathways</title>
- <link rel="stylesheet" href="/app.css?v=19">
+ <link rel="stylesheet" href="/app.css?v=20">
</head>
<body>
<main id="stage">
@@ -16,6 +16,6 @@
<p>four paths, one per direction. walk them.</p>
<button id="start">start</button>
</div>
- <script src="/app.js?v=19"></script>
+ <script src="/app.js?v=20"></script>
</body>
</html>
diff --git a/server.mjs b/server.mjs
index f03eace..5ae51a0 100644
--- a/server.mjs
+++ b/server.mjs
@@ -14,7 +14,6 @@ const DB = {
}
const PORT = Number(process.env.PORT || 3100)
-const THUMBS = process.env.THUMBS_ROOT || "/srv/photoprism/storage/cache/thumbnails"
const PUBLIC = join(process.cwd(), "public")
const MIN_PATH = 3
@@ -182,61 +181,6 @@ function json(res, code, data) {
res.end(JSON.stringify(data))
}
-async function photoConnections(id) {
- const [ph] = await pool.query(
- `SELECT p.id, p.photo_title, p.photo_year, p.photo_month, p.photo_day,
- f.file_hash, f.file_width, f.file_height
- FROM photos p
- JOIN files f ON f.photo_id = p.id AND f.file_primary = 1 AND f.file_missing = 0
- WHERE p.id = ?`,
- [id]
- )
- if (ph.length === 0) return null
- const p = ph[0]
- const conns = []
-
- const [labels] = await pool.query(
- `SELECT l.label_name AS name, COUNT(*) AS n
- FROM photos_labels pl JOIN labels l ON l.id = pl.label_id
- WHERE pl.photo_id = ? GROUP BY pl.label_id`,
- [id]
- )
- const [keywords] = await pool.query(
- `SELECT kw.keyword AS name, COUNT(*) AS n
- FROM photos_keywords pk JOIN keywords kw ON kw.id = pk.keyword_id
- WHERE pk.photo_id = ? GROUP BY pk.keyword_id`,
- [id]
- )
- const [camera] = await pool.query(
- `SELECT CONCAT(c.camera_make, ' ', c.camera_model) AS name, COUNT(*) AS n
- FROM photos p JOIN cameras c ON c.id = p.camera_id WHERE p.id = ?`,
- [id]
- )
- const [lens] = await pool.query(
- `SELECT l.lens_model AS name, COUNT(*) AS n
- FROM photos p JOIN lenses l ON l.id = p.lens_id WHERE p.id = ?`,
- [id]
- )
-
- for (const r of labels) conns.push({ type: "label", name: r.name, n: r.n })
- for (const r of keywords) conns.push({ type: "keyword", name: r.name, n: r.n })
- for (const r of camera) conns.push({ type: "camera", name: r.name, n: r.n })
- for (const r of lens) conns.push({ type: "lens", name: r.name, n: r.n })
-
- return {
- photo: {
- id: p.id,
- thumb: thumb(p.file_hash),
- full: thumb(p.file_hash, BIG_SIZE),
- title: p.photo_title || "",
- date: p.photo_year ? `${p.photo_year}-${String(p.photo_month || 1).padStart(2, "0")}-${String(p.photo_day || 1).padStart(2, "0")}` : "",
- w: p.file_width || 0,
- h: p.file_height || 0,
- },
- connections: conns,
- }
-}
-
const server = http.createServer(async (req, res) => {
const url = new URL(req.url, `http://${req.headers.host || "localhost"}`)
const path = url.pathname
@@ -268,12 +212,6 @@ const server = http.createServer(async (req, res) => {
photos,
})
}
- const photoMatch = path.match(/^\/api\/photo\/(\d+)$/)
- if (photoMatch) {
- const data = await photoConnections(Number(photoMatch[1]))
- if (!data) return json(res, 404, { error: "not found" })
- return json(res, 200, data)
- }
if (path === "/api/health") return json(res, 200, { ok: true })
if (path === "/" || path === "") {