mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-11 16:24:11 +01:00
10 KiB
10 KiB
PhotoPrism — Frontend CODEMAP
Last Updated: November 21, 2025
Purpose
- Help agents and contributors navigate the Vue 3 + Vuetify 3 app quickly and make safe changes.
- Use Makefile targets and scripts in
frontend/package.jsonas sources of truth.
Quick Start
- Build once:
make -C frontend build - Watch for changes (inside dev container is fine):
make watch-jsfrom repo root, orcd frontend && npm run watch
- Unit tests (Vitest):
make vitest-watch/make vitest-coverageorcd frontend && npm run test
Directory Map (src)
src/app.vue— root component; UI shellsrc/app.js— app bootstrap: creates Vue app, installs Vuetify + plugins, configures router, mounts to#appsrc/app/routes.js— all route definitions (guards, titles, meta)src/app/session.js—$configand$sessionsingletons wired from server-providedwindow.__CONFIG__and storagesrc/common/*— framework-agnostic helpers:$api(Axios),$notify,$view,$event(PubSub), i18n (gettext), util, fullscreen, map utils, websocketsrc/component/*— Vue components;src/component/components.jsregisters global componentssrc/page/*— route views (Albums, Photos, Places, Settings, Admin, Discover, Help, Login, etc.)src/model/*— REST models; baseRestclass (model/rest.js) wraps Axios CRUD for collections and entitiessrc/options/*— UI/theme options, formats, auth optionssrc/css/*— styles loaded by Webpacksrc/locales/*— gettext catalogs; extraction/compile scripts inpackage.json
Startup Templates & Splash Screen
- The HTML shell is rendered from
assets/templates/index.gohtml(andpro/assets/templates/index.gohtml/plus/...). Each template includesapp.gohtmlfor the splash markup andapp.js.gohtmlto inject the bundle. - The browser check logic resides in
assets/static/js/browser-check.jsand is included viaapp.js.gohtml; it performs capability checks (Promise, fetch, AbortController,script.noModule, etc.) before the main bundle executes. Update the same files in private repos whenever the loader logic changes, and keep the script order so the check runs first. - Splash styles, including the
.splash-warningfallback banner, live infrontend/src/css/splash.css. Keep styling changes there so public and private editions stay aligned. - Baseline support: Safari 13 / iOS 13 or current Chrome, Edge, or Firefox. If the support matrix changes, revise the warning text in
app.js.gohtmland the CSS message accordingly. - Lightbox videos:
createVideoElementwires listeners through anAbortControllerstored incontent.data.events;contentDestroyaborts it so video and RemotePlayback handlers vanish with the slide.
Runtime & Plugins
- Vue 3 + Vuetify 3 (
createVuetify) with MDI icons; themes fromsrc/options/themes.js - Router: Vue Router 4, history base at
$config.baseUri + "/library/" - I18n:
vue3-gettextviacommon/gettext.js; extraction withnpm run gettext-extract, compile withnpm run gettext-compile - HTML sanitization:
vue-3-sanitize+vue-sanitize-directive - Tooltips:
floating-vue - Video: HLS.js assigned to
window.Hls - PWA: Workbox registers a service worker after config load (see
src/app.js); scope and registration URL derive from$config.baseUriso non-root deployments work. Workbox precache rules live infrontend/webpack.config.js(see theGenerateSWplugin); locale chunks and non-woff2 font variants are excluded there so we don’t force every user to download those assets on first visit. - WebSocket:
src/common/websocket.jspublisheswebsocket.*events, used by$sessionfor client info
Lightbox Integration
- Shared entry points live in
src/common/lightbox.js;$lightbox.open(options)fires alightbox.openevent consumed bycomponent/lightbox.vue. - Prefer
$lightbox.openView(this, index)when a component or dialog already has the photos in memory. ImplementgetLightboxContext(index)on the view and return{ models, index, context, allowEdit?, allowSelect? }so the lightbox can build slides without requerying. - Set
allowEdit: falsewhen the caller shouldn’t expose inline editing (the edit button andKeyEshortcut are disabled automatically). SetallowSelect: falseto hide the selection toggle and block the.shortcut so batch-edit dialogs don’t mutate the global clipboard. - Legacy
$lightbox.openModels(models, index, collection)still accepts raw thumb arrays, but it cannot express the context flags—only use it when you truly don’t have a backing view.
HTTP Client
- Axios instance:
src/common/api.js- Base URL:
window.__CONFIG__.apiUri(or/api/v1in tests) - Adds
X-Auth-Token,X-Client-Uri,X-Client-Version - Interceptors drive global progress notifications and token refresh via headers
X-Preview-Token/X-Download-Token
- Base URL:
Auth, Session, and Config
$session:src/common/session.js— storesX-Auth-Tokenandsession.idin storage; provides guards and default routes$config:src/common/config.js— reactive view of server config and user settings; sets theme, language, limits; exposesdeny()for feature flags- Route guards live in
src/app.js(routerbeforeEach/afterEach) and use$session+$config $view:src/common/view.js— manages focus/scroll helpers; usesaveWindowScrollPos()/restoreWindowScrollPos()when navigating so infinite-scroll pages land back where users left them; behaviour is covered bytests/vitest/common/view.test.js
Models (REST)
- Base class:
src/model/rest.jsprovidessearch,find,save,update,removefor concrete models (photo,album,label,subject, etc.) - Collection helpers:
src/model/collection.jsadds shared behaviors (for examplesetCover) used by collection-types such as albums and labels. - Pagination headers used:
X-Count,X-Limit,X-Offset
Routing Conventions
- Add pages under
src/page/<area>/...and import them insrc/app/routes.js - Set
meta.requiresAuth,meta.admin, andmeta.settingsas needed - Use
meta.titlefor translated titles;router.afterEachupdatesdocument.title
Theming & UI
- Themes:
src/options/themes.jsregistered in Vuetify; default comes from$config.values.settings.ui.theme - Global components: register in
src/component/components.jswhen they are broadly reused
Testing
- Vitest config:
frontend/vitest.config.js(Vue plugin, alias map tosrc/*),tests/vitest/**/* - Run:
cd frontend && npm run test(ormake test-jsfrom repo root) - Acceptance: TestCafe configs in
frontend/tests/acceptance; run against a live server
Build & Tooling
- Webpack is used for bundling; scripts in
frontend/package.json:npm run build(prod),npm run build-dev(dev),npm run watch- Lint/format:
npm run lintormake lint-js; repo rootmake lintruns both backend (golangci-lint via.golangci.yml) and frontend linters - Security scan:
npm run security:scan(checks--ignore-scriptsand forbidsv-html)
- Licensing: run
make noticefrom the repo root to regenerateNOTICEfiles after dependency changes—never edit them manually. - Make targets (from repo root):
make build-js,make watch-js,make test-js - Browser automation (Playwright MCP): workflows are documented in
AGENTS.mdunder “Playwright MCP Usage”; use those directions when agents need to script UI checks or capture screenshots.
Common How‑Tos
-
Add a page
- Create
src/page/<name>.vue(or nested directory) - Add route in
src/app/routes.jswithname,path,component, andmeta - Use
$apifor data,$notifyfor UX,$sessionfor guards updateQuery(props)helpers should return a boolean indicating whether a navigation was scheduled (recently standardised across pages); callers can bail early whenfalse
- Create
-
Add a REST model
- Create
src/model/<thing>.jsextendingRestand implementstatic getCollectionResource()+static getModelName() - Use in pages/components for CRUD
- Create
-
Call a backend endpoint
- Use
$api.get/post/put/deletefromsrc/common/api.js - For auth:
$session.setAuthToken(token)sets header; router guards redirect tologinwhen needed
- Use
-
Add translations
- Wrap strings with
$gettext(...)/$pgettext(...) - Extract:
npm run gettext-extract; compile:npm run gettext-compile
- Wrap strings with
-
Restore scroll state on back navigation
- Use
$view.saveRestoreState(key, { count, offset, scrollTop })when unloads happen and$view.consumeRestoreState(key)on popstate to preload prior batches (Albums, Labels already supply examples). - Compute
keyfrom route + filter params and cap eager loads withRest.restoreCap(Model.batchSize())(defaults to 10× the batch size). - Check
$view.wasBackwardNavigation()when deciding whether to reuse stored state;src/app.jswires the router guards that keep the history direction in sync so no globals likewindow.backwardsNavigationDetectedare needed.
- Use
-
Handle dialog shortcuts
- Persistent dialogs (
persistentprop) must listen for Escape on@keydown.esc.exactto override Vuetify’s rejection animation; keep Enter and other actions on@keyupso child inputs can intercept them first. - Global shortcuts go through
onShortCut(ev)incommon/view.js. It only forwards Escape andctrl/metacombinations, so do not depend on it for plain character keys.
- Persistent dialogs (
Conventions & Safety
- Avoid
v-html; usev-sanitizeor$util.sanitizeHtml()(build enforces this) - Keep big components lazy if needed; split views logically under
src/page - Respect aliases in
vitest.config.jswhen importing (app,common,component,model,options,page)
Frequently Touched Files
- Bootstrap:
src/app.js,src/app.vue - Router:
src/app/routes.js - HTTP:
src/common/api.js - Session/Config:
src/common/session.js,src/common/config.js - Models:
src/model/rest.jsand concrete models (photo.js,album.js, ...) - Global components:
src/component/components.js
See Also
- Backend CODEMAP at repo root (
CODEMAP.md) for API and server internals - AGENTS.md for repo-wide rules and test tips