Commit graph

5711 commits

Author SHA1 Message Date
Michael Mayer
df21d77d30 Frontend: Skip lightbox-sidebar edit on link clicks & compact comments #4966 2026-05-17 09:41:51 +00:00
Michael Mayer
6c54bbc58d Frontend: Hide caption overlay when sidebar opens with lightbox #4966 2026-05-16 19:26:08 +00:00
Michael Mayer
735fa5b3db OIDC: Auto-redirect deep links and preserve return URL #5506
PHOTOPRISM_OIDC_REDIRECT only fired for the root path; deep links under
/library/* fell through to the SPA bootstrap, which served the local
login page with a manual "Continue with OIDC" button. The deep-link
URL was also dropped on the OIDC roundtrip because $session.loginRedirect
lived only in memory, which the IdP bounce wipes.

The login page now auto-triggers OIDC on mount when the flag is on, and
$session persists the redirect target in namespaced storage under a key
(login.next) that survives auth.gohtml's session.* clear. A one-shot
login.logout flag set on signOut suppresses one auto-trigger so an
explicit logout shows the login form once.

Also adds a /logout SPA route so direct visits behave like the navigation
menu action. The new $session.signOut() snapshots the auth token, fires
the server DELETE with it (so the response is 200 and no 401 echo
re-raises the logout flag), then resets client state synchronously.
2026-05-16 17:22:42 +00:00
Michael Mayer
ee89d73ca7 Settings: Save service password and API key on edit #5558
AccPass and AccKey are tagged json:"-" on the backend so they're absent
from GET responses. The base Model only tracked keys present in the
response, so getValues(true) silently dropped credential edits and the
PUT body never carried them.

Adds a generic getWriteOnly() hook on the base Model (parallel to
getDefaults()): subclasses declare write-only fields and the constructor
seeds them into __originalValues so the existing change-diff machinery
detects user edits. Service overrides it for AccPass and AccKey.
2026-05-16 16:20:53 +00:00
Michael Mayer
b0e76449b9 Weblate: Update frontend translations #4966 #5585
Signed-off-by: Michael Mayer <michael@photoprism.app>
2026-05-16 16:50:57 +02:00
Weblate
ff03aaac0b Weblate: Update frontend translations 2026-05-16 16:50:04 +02:00
Michael Mayer
01d19c24ae Frontend: Regenerate translation catalogs #4966
Refreshes `#:` source-path references after the sidebar relocation
from component/sidebar/ to component/lightbox/sidebar(.vue|/toolbar.vue).
No string changes.
2026-05-16 14:44:24 +00:00
Michael Mayer
c20363f1e9 Frontend: Relocate sidebar under component/lightbox/ #4966
Renames the sidebar component to reflect its only mount point: the
lightbox. Bundles file moves, name renames, template tags, imports,
CSS class renames, and acceptance + vitest selector sweeps so the
build never lands half-renamed.

- frontend/src/component/sidebar/info.vue -> lightbox/sidebar.vue
- frontend/src/component/sidebar/inline-toolbar.vue -> lightbox/sidebar/toolbar.vue
- PSidebarInfo -> PLightboxSidebar; PSidebarInlineToolbar -> PLightboxSidebarToolbar
- .p-sidebar-info -> .p-lightbox-sidebar (and the toolbar variant)
- vitest files relocated; gettext catalog regen deferred to next pass.
2026-05-16 14:39:23 +00:00
Michael Mayer
781c9147ba Frontend: Add class "action-save" to meta dialog confirm buttons #4966
Signed-off-by: Michael Mayer <michael@photoprism.app>
2026-05-16 16:23:21 +02:00
Michael Mayer
260e8b6bfb Frontend: Gate metadata-popover saves and fix Unknown-date render #5584
Camera popover (component/meta/camera/dialog.vue): wraps the inputs in
<v-form ref="form" v-model="valid">, seeds validate() in afterEnter, and
gates confirm() on $refs.form.validate() with a "Changes could not be
saved" toast on failure. Save button binds :disabled="!valid" so the
disabled state mirrors the datetime popover's :disabled="invalidDate"
pattern and the user sees the gate before clicking.

Datetime popover (component/meta/datetime/dialog.vue): setTime() now
flips invalidDate when the time field is malformed (previously
short-circuited and left the flag stale, so Confirm stayed enabled
against bad input). Time field gains :error="invalidDate" for visual
consistency with the Day/Month/Year siblings.

Both popovers rename the primary action from "Confirm" to "Save" with a
matching aria-label, aligning with the regular Edit Dialog. The
action-confirm CSS class stays for selector compatibility with the
acceptance tests.

Sidebar (component/sidebar/info.vue): formatTime() now delegates to
photo.getDateString(true) when the full Photo is loaded, so Day/Month/
Year=-1 (Unknown) values render via the model's existing fallback
(Unknown / year / month + year) instead of the padded "Jan 1, 2024"
shape. The Thumb model from viewer.Result doesn't carry Year/Month/
Day, so the fallback to the Thumb's plain TakenAtLocal format still
covers the brief pre-load window.
2026-05-16 14:10:51 +00:00
Michael Mayer
499ac5d009 Frontend: Tighten Photo numeric caps and reveal inline errors #5584
ISO, F Number, and Focal Length on the Edit Dialog and Camera popover
previously capped at 1048576, much wider than the backend's SetExposure
clamps (ISO and FocalLength <= 128000, FNumber <= 256). The looser caps
let the rule pass values the server then quietly rejects. Tightens each
to match the backend, adds Exposure: 64 to PhotoMaxLength, and routes
the Exposure rule through PhotoMaxLength.Exposure instead of a hardcoded
literal.

Drops the bare hide-details attribute from Country, Altitude, ISO,
Exposure, F Number, and Focal Length on both surfaces so the rule's
inline error renders below the input on overflow. Date/time inputs
(Day/Month/Year/Time autocompletes + the standalone DateTime dialog)
stay as-is: autocompletes can only emit valid items, the Time field
combines rules.time() with the :error="invalidDate" visual cue and the
form-validate save gate.
2026-05-16 13:26:39 +00:00
Michael Mayer
04f5dd922f Frontend: Validate inline-text input against backend VARCHAR caps #5584
Adds per-entity MaxLength constants on model/{photo,label,subject,album}.js
mirroring the backend VARCHAR widths, and routes every inline-text input
in the Edit Dialog, Batch Edit, sidebar inline editor, Photo Labels /
People tabs, New People page, and the Label / Album / People edit dialogs
through the shared rules.text(...) factory in common/form.js. Deletes the
$config.get('clip') = 160 anti-pattern that rejected valid 200-char titles
and 1024-char rights fields, and drops the bare `hide-details` attribute
that masked the inline error slot.

Adds a $refs.form.validate() gate to the Edit Dialog and Batch Edit save
handlers, mirroring page/settings/account.vue, so an overlength field
blocks the save with $notify.error("Changes could not be saved") instead
of firing photo.update() against invalid input. For the sidebar inline
editor (no parent v-form) confirmField() now checks the field's
fieldRegistry.maxLength imperatively and keeps the editor open on
overflow; navigation arrows then route through confirmDiscardPending,
which opens the existing discard dialog with a "Discard invalid changes?"
label so the user explicitly chooses to revert or stay.

Includes focused vitest cases for the new caps, the form-validate gate
on both dialogs, the sidebar imperative check, and the new discard-dialog
overflow branch.
2026-05-16 12:46:15 +00:00
Michael Mayer
38e7022765 Frontend: Gate batch-edit albums/labels on feature flags and reorder
Hides the Labels and Albums sections in the Batch Edit dialog when
the matching deployment feature is disabled or the session lacks
the `labels:search` / `albums:search` grant, mirroring the gate
pattern already in `sidebar/info.vue`. Skips the typeahead prefetch
for any disabled section so a deployment with both features off
spends no round-trip on data the dialog never reads. Also moves the
Albums row above Labels so the order matches the lightbox sidebar.
2026-05-16 10:44:09 +00:00
Michael Mayer
11f30f7edc Frontend: Restrict Prettier to CSS and JSON via .prettierignore
Signed-off-by: Michael Mayer <michael@photoprism.app>
2026-05-16 10:34:38 +02:00
Michael Mayer
39216b2bd6 Frontend: Update code formatting rules in eslint.config.mjs
Signed-off-by: Michael Mayer <michael@photoprism.app>
2026-05-16 10:09:35 +02:00
Michael Mayer
e7729425fb Frontend: Apply curly autofix sweep to frontend/tests #4966 2026-05-16 08:03:43 +00:00
Michael Mayer
700c308dc3 Frontend: Loosen indent/quotes to warn; add brace-style rule #4966 2026-05-16 08:03:39 +00:00
Michael Mayer
5a144af643 Frontend: Disable Prettier reflow; enforce indent/quotes via ESLint #4966 2026-05-16 08:00:43 +00:00
Michael Mayer
bdd33df25b Frontend: Enforce braced if-bodies via ESLint curly rule #4966 2026-05-16 07:40:10 +00:00
Michael Mayer
e92c4ee80b Frontend: Revert ineffective v-tooltip fix and reformat info.vue #4966 2026-05-16 07:21:23 +00:00
Michael Mayer
0cfdb05a10 Frontend: Update documentation link for v-tooltip in app.js #4966
Signed-off-by: Michael Mayer <michael@photoprism.app>
2026-05-16 09:00:33 +02:00
Michael Mayer
ce1dc46757 Frontend: Move albums above labels in lightbox sidebar #4966
Signed-off-by: Michael Mayer <michael@photoprism.app>
2026-05-16 00:11:20 +02:00
Michael Mayer
c79cb1f022 Frontend: Compact verbose CSS comments in lightbox.css 2026-05-15 18:03:26 +00:00
Michael Mayer
918a0c2259 Frontend: Compact verbose comments in lightbox and sidebar #4966 2026-05-15 17:35:37 +00:00
Michael Mayer
069b3cd87b Frontend: Compact verbose comments in model/ modules #4966 2026-05-15 17:35:33 +00:00
Michael Mayer
f89a0922a3 Frontend: Compact verbose comments in common/ modules #4966 2026-05-15 17:35:29 +00:00
Michael Mayer
ae2645dbc8 Frontend: Compact verbose code comments in lightbox and sidebar #4966 2026-05-15 17:17:52 +00:00
Michael Mayer
7616a6cdab Frontend: Polish lightbox sidebar with QA feedback #4966 2026-05-15 16:30:23 +00:00
Michael Mayer
4516ce0894 Frontend: Remove custom color from links in info sidebar #4966
Signed-off-by: Michael Mayer <michael@photoprism.app>
2026-05-15 17:39:36 +02:00
Michael Mayer
005c34dd5e Frontend: Fix People discard dialog races and stacked Add-name prompt #4966 2026-05-15 15:37:51 +00:00
Michael Mayer
a37882418f Frontend: Show real metadata for limited roles in lightbox sidebar #4966 2026-05-15 14:22:15 +00:00
Michael Mayer
6973feaed8 Frontend: Keep chip-toolbar Save/Undo visible and fix re-add #4966 2026-05-15 12:02:18 +00:00
Michael Mayer
ae4e330e2e Frontend: Update deps in package.json and package-lock.json #4966
Signed-off-by: Michael Mayer <michael@photoprism.app>
2026-05-15 08:24:55 +02:00
Michael Mayer
140d241b84 Frontend: Show lightbox controls after resizing and adjust CSS #4966
Signed-off-by: Michael Mayer <michael@photoprism.app>
2026-05-15 08:12:38 +02:00
Michael Mayer
f88e85e4f3 Frontend: Mount face-marker overlay in read-only display mode for viewers
Drops shouldShowEditButton() from the <p-face-marker-overlay> v-if so
non-editable users (Plus / Pro viewer role, anyone reaching the eye
toggle in the sidebar) actually see marker rectangles AND the
in-overlay Back button when they activate display mode. The previous
gate required edit permission, so viewers entered face-marker mode
(chrome hidden), saw nothing, and had no way to exit. Edit gestures
stay gated separately by the overlay's isEditMode flag and the
lightbox-side onCreateFaceMarker / onRemoveFaceMarker /
toggleFaceMarkerEdit handlers that re-check shouldShowEditButton().
Pre-existing bug; surfaced while testing the recent ACL-driven
sidebar refactor (#4966).
2026-05-14 17:34:38 +00:00
Michael Mayer
1d6698da04 Frontend: Gate sidebar visibility on ACL grants, not a static role list
Replaces the static SidebarRestrictedRoles array ("guest", "visitor",
"contributor") and the User/Session isSidebarRestricted helpers with
per-resource $config.allow(resource, perm) checks. The frontend now
mirrors the backend's authority (internal/auth/acl/rules.go) instead
of duplicating policy: each sidebar section reads the same ACL grant
the server uses to redact the JSON response. Drift fix on the way —
"contributor" wasn't a CE role and the static list silently allowed
the "viewer" role full sidebar access without any spec saying so.

New per-section computeds in sidebar/info.vue:
- canViewLibrary  -> $config.allow("photos", "access_library")
  Gates camera, lens, file name, and the rights / notes cluster
  (photographer-EXIF privacy fields).
- canViewPeople   -> $config.allow("people", "search")
- canViewLabels   -> $config.allow("labels", "search")
- canViewAlbums   -> $config.allow("albums", "search")
- canViewPlaces   -> $config.allow("places", "view")

lightbox.vue's fetchPhoto / preloadNextPhoto guards switch from
$session.isSidebarRestricted() to $config.deny("photos",
"access_library") so sessions without library access still skip
the extra GET /photos/:uid calls.
2026-05-14 14:52:34 +00:00
Michael Mayer
7230254a5a Frontend: Add Ctrl+H to toggle the lightbox caption #5580
PhotoSwipe Dynamic Caption overlay can now be hidden via Ctrl+H so
the photo takes the full canvas without leaving the lightbox or
opening the sidebar. State persists to localStorage as
`lightbox.caption` (mirrors the existing `lightbox.info` flag), so
the user's choice survives slide nav, lightbox close/reopen, and
page reload. The toggle gates the forked dynamic-caption plugin's
panAreaSize adjustment via a new `enabled` option and routes the
layout recompute through this.resize(true) after $nextTick (the
.hide-caption class binding is reactive), so the photo actually
reclaims the caption's reserved vertical space when not width-
bound by aspect ratio. Shortcut sits inside the existing input-
focus gate, is inert in face-marker mode, and is a no-op when the
sidebar is open (captions render in the sidebar there). Also adds
mouse-driven Show/Hide Caption entries to the lightbox dropdown
menu, mirroring the Archive/Restore mutually-exclusive pattern.
2026-05-14 13:33:46 +00:00
Michael Mayer
8089214483 Frontend: Regenerate src/locales/json/fr.json #4966 #5579
Signed-off-by: Michael Mayer <michael@photoprism.app>
2026-05-14 11:30:27 +02:00
Weblate
68c6a0a789 Weblate: Update frontend translations 2026-05-14 11:29:34 +02:00
Michael Mayer
c1e4680861 Frontend: Regenerate translation files #4966 #5578
Signed-off-by: Michael Mayer <michael@photoprism.app>
2026-05-14 11:21:28 +02:00
Weblate
77d3e23b52 Weblate: Update frontend translations 2026-05-14 11:19:33 +02:00
Michael Mayer
d68676ad41 Frontend: Regenerate translation catalogs #4966
Picks up the "Edit Faces" / "Remove Name" → "Edit" / "Unassign"
renames from the previous commit. "Edit" was already broadly
translated; "Unassign" is freshly active and needs new translations
across locales. "Remove Name" demoted to obsolete (#~) with prior
translations preserved.
2026-05-14 09:08:57 +00:00
Michael Mayer
34ecdb08d6 Frontend: Tighten face-marker toolbar labels (Edit, Unassign) #4966
Renames the People-section toggle from "Edit Faces" to "Edit" — the
mdi-pencil icon plus surrounding section context already convey the
scope, and the shorter label aligns with the rest of the inline-toolbar
pencil affordances. Renames the marker-name eject tooltip from "Remove
Name" to "Unassign", which more accurately describes the action (the
subject assignment is removed, not the name as text). Same string now
covers both edit-dialog and sidebar surfaces. Also reorders a couple
of v-btn attributes for consistency.
2026-05-14 09:08:50 +00:00
Michael Mayer
7252c82695 Frontend: Replace "subject" icon in lightbox sidebar #4966
Signed-off-by: Michael Mayer <michael@photoprism.app>
2026-05-14 10:45:41 +02:00
Michael Mayer
231d3c6291 Frontend: Replace "artist" icon in lightbox sidebar #4966
Signed-off-by: Michael Mayer <michael@photoprism.app>
2026-05-14 10:33:11 +02:00
Michael Mayer
f741f8e103 Frontend: Skip sidebar row tooltip when no icon anchors it #4966
Notes uses `icon: null` so Vuetify's default `'end center'` tooltip
location anchors against an empty prepend slot, putting the tooltip
in a visually inconsistent place vs sibling rows. Gate the tooltip
on the row having an icon so Notes (and any future no-icon row in
detailsFields) skips the tooltip entirely — matches Title and
Caption above which never had one. The "Notes" / placeholder text
is already visible inline as the add-prompt, so no information is
lost.
2026-05-14 07:33:13 +00:00
Michael Mayer
15dabc5dcd Frontend: Highlight matching face marker on sidebar hover #4966
Hovering a person row in the lightbox sidebar's People section now
sets a thicker purple stroke + glow on the matching face-marker rect
on the image, making the visual link between the row and the box
unambiguous in both display and edit modes. Implementation routes
through a new hoveredMarkerUid field on the $faceMarkers singleton
that the lightbox forwards as a prop to the overlay. Reverse
direction (rect hover → sidebar row) isn't wired since rects carry
pointer-events: none — recorded for future work in the singleton +
CSS comments. Also drops two redundant text-align: start declarations
on .meta-title / .meta-caption (defaults already match).
2026-05-14 07:06:10 +00:00
Michael Mayer
30e41c12d5 Frontend: Move Archive shortcut to Ctrl+X, gate keys on input focus #4966
Adds an input/textarea/contenteditable focus gate at the top of the
lightbox onShortCut handler so Ctrl+A select-all, Ctrl+C copy, etc.
no longer trigger global lightbox actions when a sidebar inline
editor or edit-dialog field has focus. Migrates the Archive/Restore
binding from KeyA to KeyX (Gmail/Outlook-style mnemonic) so the
canonical select-all chord stays free for text editing in addition
to the focus gate. Updates the matching face-marker disabled-shortcut
list and acceptance + vitest test cases.
2026-05-14 06:39:19 +00:00
Michael Mayer
ee775026e4 Frontend: Merge sidebar text fields, add Undo + commit-on-Enter #4966
Unifies Subject / Artist / Copyright / License / Keywords / Notes
into a single icon-prepended row layout (Option A from the proposal),
adds per-field maxLength sourced from new PhotoMaxLength constants
mirroring the backend VARCHAR caps, fixes a pre-existing textarea
auto-grow regression caused by a static min-height override in
lightbox.css, wires Enter-to-commit on the single-line fields via a
commitOnEnter registry flag, and adds an Undo affordance + new
hideEditUndo / hideEditSave toggles for A/B testing the inline
toolbar chrome.
2026-05-14 06:38:53 +00:00
Michael Mayer
367af097f0 Frontend: Replace action-verb literals with ACTION_* consts #4966
common/event.js now exports six action-verb constants (ACTION_CREATED,
ACTION_UPDATED, ACTION_DELETED, ACTION_ARCHIVED, ACTION_RESTORED,
ACTION_EDITED) and composes ENTITY_MUTATIONS from them so there is one
source of truth. Every onUpdate(ev, data) switch in the frontend now
references the symbols instead of the literal strings — eight cache /
page-level handlers across albums, labels, library/browse, photos,
people, the album-photos view, and the photo edit dialog.

The motivation is navigation: grep / IDE find-references on
"updated" or "created" is overwhelmed by Vue lifecycle hooks, Date
methods, prose in comments, and unrelated occurrences of the same
English word. ACTION_UPDATED finds only the event-handling sites.
The count.* subtype switch in common/config.js stays on string
literals because those are count category names, not entity-mutation
verbs.
2026-05-14 03:37:38 +00:00