diff options
| -rw-r--r-- | .gitignore | 38 | ||||
| -rw-r--r-- | public/app.js | 38 | ||||
| -rw-r--r-- | public/index.html | 4 | ||||
| -rw-r--r-- | server.mjs | 62 |
4 files changed, 29 insertions, 113 deletions
@@ -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> @@ -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 === "") { |
