feat(booth): image viewer page with Fit/1:1, download, Esc-back (v0.1.2)

Clicking a gallery image now opens a dedicated viewer instead of dumping you on
the raw file.

- GET /b/<name>/view?f=<img> — full-viewport viewer (registered before the file
  catch-all so /view wins; non-image f 307-redirects to the raw file, traversal
  and missing f 404).
- Fit (downscale-only) / 1:1 (natural pixels, scroll-to-pan) toggle that only
  appears when the image is larger than the viewport — when it already fits,
  Fit ≡ 1:1 so the toggle is hidden. Re-evaluates on resize.
- Download button + ✕/Esc back to the gallery. Australis-themed, progressive
  JS (degrades to fit-only, no-JS still shows the image + download + back).
- 5 new tests (34 total, all green); verified Fit/1:1/hidden-toggle states in a
  real browser.
This commit is contained in:
vh
2026-07-20 22:10:46 -07:00
parent f328848bd7
commit 4e6c7951ff
7 changed files with 152 additions and 3 deletions
+25
View File
@@ -349,6 +349,31 @@ def create_app(
},
)
@app.get("/b/{name}/view", response_class=HTMLResponse)
def booth_image_view(request: Request, name: str, f: str):
booth = resolve_booth(name)
try:
target = (booth / f).resolve()
except OSError:
raise HTTPException(status_code=404, detail="no such file")
if not str(target).startswith(str(booth) + os.sep) or not target.is_file():
raise HTTPException(status_code=404, detail="no such file")
file_url = quote(f, safe="/")
if classify(target.name) != "image":
# nothing to zoom on a non-image — hand back the raw file
return RedirectResponse(url=f"/b/{quote(name, safe='')}/{file_url}", status_code=307)
return templates.TemplateResponse(
request,
"view.html",
{
**base_ctx,
"name": name,
"name_url": quote(name, safe=""),
"file": f,
"file_url": file_url,
},
)
@app.get("/b/{name}/{filepath:path}")
def booth_file(name: str, filepath: str):
booth = resolve_booth(name)