diff --git a/shared/photoflow/ui/__init__.py b/shared/photoflow/ui/__init__.py index e69de29..f0f2bae 100644 --- a/shared/photoflow/ui/__init__.py +++ b/shared/photoflow/ui/__init__.py @@ -0,0 +1,16 @@ +import os +from flask import Blueprint, Flask +from jinja2 import ChoiceLoader, FileSystemLoader + +TEMPLATE_DIR = os.path.join(os.path.dirname(__file__), "templates") +STATIC_DIR = os.path.join(os.path.dirname(__file__), "static") + + +def register_shared_ui(app: Flask) -> None: + app.jinja_loader = ChoiceLoader([app.jinja_loader, FileSystemLoader(TEMPLATE_DIR)]) + bp = Blueprint("shared_ui", __name__, static_folder=STATIC_DIR, + static_url_path="/shared-static") + app.register_blueprint(bp) + + +__all__ = ["TEMPLATE_DIR", "STATIC_DIR", "register_shared_ui"] diff --git a/shared/photoflow/ui/static/shared.js b/shared/photoflow/ui/static/shared.js new file mode 100644 index 0000000..a5d77a1 --- /dev/null +++ b/shared/photoflow/ui/static/shared.js @@ -0,0 +1,57 @@ +// Generic grid + lightbox behavior shared across photoflow apps. +// Apps spread this into their own Alpine component: { ...photoGrid(), ...appLogic } +function photoGrid() { + return { + focused: null, + lightboxOpen: false, + + cards() { + return [...document.querySelectorAll('.photo-card')] + .filter(c => c.style.display !== 'none'); + }, + + select(el) { + if (this.focused) this.focused.classList.remove('ring-4', 'ring-white', 'z-10'); + this.focused = el; + if (el) { + el.classList.add('ring-4', 'ring-white', 'z-10'); + el.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + if (this.lightboxOpen) this.updateLightbox(); + } + }, + + selectFirst() { + const cards = this.cards(); + if (cards.length) this.select(cards[0]); + }, + + navigate(dir) { + const cards = this.cards(); + if (!cards.length) return; + const idx = this.focused ? cards.indexOf(this.focused) : -1; + const next = cards[Math.max(0, Math.min(cards.length - 1, idx + dir))]; + if (next) this.select(next); + }, + + openLightbox(el) { + this.select(el); + this.lightboxOpen = true; + document.getElementById('lb').style.display = ''; + this.updateLightbox(); + }, + + closeLightbox() { + this.lightboxOpen = false; + const lb = document.getElementById('lb'); + if (lb) lb.style.display = 'none'; + }, + + updateLightbox() { + const el = this.focused; + if (!el) return; + const img = el.querySelector('img'); + const lbImg = document.getElementById('lb-img'); + if (img && lbImg) lbImg.src = img.src; + }, + }; +} diff --git a/shared/photoflow/ui/templates/base.html b/shared/photoflow/ui/templates/base.html new file mode 100644 index 0000000..249c29f --- /dev/null +++ b/shared/photoflow/ui/templates/base.html @@ -0,0 +1,24 @@ + + +
+ + +