Commit graph

822 commits

Author SHA1 Message Date
Theresa Gresch
543a7c4258 Tests: Adapt tests to changes 2026-07-08 16:13:02 +02:00
Ömer Duran
6dc0acac13
Settings: Reorganize UI and add an Accessibility section #848 #799 #5429
* Frontend: Add settings for albums, favorites, folders, and media features

* Frontend: Enhance download settings for files and albums #848

* Frontend: Enhance download settings for files and albums #848

* Frontend: Update album download settings to restrict access to super admins

* Frontend: Add album sorting options to settings for super admins

* Frontend: Update download settings visibility

* Frontend: Refine download settings access control for user roles

* Tests: Add album, folder, moment, state, and month order selection options to page-model

* Frontend: Update access control for super admins in settings to include scope check

* Tests: Add new tests for download name and album order settings persistence

* Tests: Implement new tests for default album sort order persistence in settings

* Frontend: Move default album order settings

* Frontend: Add zoom accessibility option in settings #799

* Frontend: Update download name from "Share Identifier" to "Share Friendly" and refine hints for download options in settings

* Tests: Add new acceptance test for disabling album downloads while allowing file downloads in settings

* Frontend: Improve download options layout and hints

* Frontend: Refactor download options condition in settings page

* Frontend: Implement native zoom control in lightbox component

* Settings: Add Collections tab and move Services to its own page #848 #5429

- Add a super-admin Collections tab after Content holding the album
  Download and Sort Order settings; remove those cards from Content.
- Move Services from a settings tab to a dedicated page
  (page/services.vue) with a search toolbar and a Reload / Learn More
  action menu; link it from the navigation settings menu.
- Implement server-side services search (acc_name filter) with tests.
- De-duplicate the picture sort-order options into a shared
  SortOrderOptions builder in options.js.
- Update acceptance/unit tests and page models to match.

* Frontend: Regenerate translation catalogs #848 #5429

Ran `make gettext-extract` to pick up the new settings strings
(Collections, Features, Disable Downloads, and related messages) so
Weblate can translate them. Existing translations are preserved.

* Frontend: Move lightbox viewport-zoom handling into the view helper

- Move the viewport pinch-zoom lock/restore (disableNativeZoom /
  restoreNativeZoom + savedViewportContent) from lightbox.vue onto the
  View singleton in common/view.js.
- Drive it passively from View.apply() via a disableViewportZoom flag in
  the PLightbox case, alongside hideScrollbar / disableScrolling /
  disableNavigationGestures; lightbox.vue no longer references zoom.
- Make save/restore idempotent and extract the viewport constants.
- Cover the helpers and the apply() wiring in view.test.js.

* Settings: Add Accessibility section to General with Reduce Motion #848 #5429

- Move the page-zoom checkbox out of the feature-flags grid into a
  dedicated Accessibility card (super-admin only) with four toggles:
  Open on Hover, Reduce Motion, Hide Scrollbar, and Allow Page Zoom.
- Add a UI.ReduceMotion flag (defaults false) that toggles an
  html.reduce-motion class via config.setReduceMotion and suppresses map
  fly-to animations non-destructively (util.mapAnimateDuration).
- Make Open on Hover apply live by reading shouldOpenOnHover() through a
  reactive computed in the action/auth/lightbox menus; Zoom and Hide
  Scrollbar reload since they are baked into the server-rendered HTML.
- Reword two feature-grid hints to the "pictures" convention and cover
  the new helpers with Go and Vitest tests.

* Frontend: Regenerate translation catalogs #848 #5429

Ran `make gettext-extract` to pick up the new Accessibility settings
strings (Accessibility, Allow Page Zoom, Open on Hover, Hide Scrollbar,
Reduce Motion, and their hints) and the reworded feature-grid hints.
Existing translations are preserved.

---------

Co-authored-by: Michael Mayer <michael@photoprism.app>
2026-07-08 14:11:36 +02:00
Michael Mayer
3e5ea71fb6 Users: Strengthen Add Account and built-in super-admin form gating 2026-06-29 21:59:48 +00:00
Michael Mayer
0575659c08 Frontend: Render auth error toasts in the current UI locale #5699 #5682 2026-06-27 11:34:54 +00:00
Ömer Duran
b2b1785126
Tests: Add unit tests for recent metadata, auth, and config changes #5697 2026-06-26 14:12:59 +02:00
Michael Mayer
d2a2597ab6 Users: Centralize Add Account auth field gating in the user model 2026-06-25 16:24:55 +00:00
Theresa Gresch
cd0deacbc1 Tests: Make acceptance tests more stable 2026-06-25 15:29:11 +02:00
Michael Mayer
6ee98a14ad Auth: Chain RP-initiated OIDC logout on direct /logout entry #5684 2026-06-24 22:52:48 +02:00
Michael Mayer
11c074cb85 Auth: Delegate cluster OIDC logout to the Portal end-session endpoint #5684
On a cluster-OIDC Sign-Out the instance now delegates the Portal session and the
upstream RP-logout to the Portal's end-session endpoint instead of doing them
itself: revokePeerSessions skips the Portal peer's backend DELETE (still clearing
its storage), and DeleteSession skips clearing the OP session cookie when it
returns a providerLogoutUri — otherwise the Portal OP could not resolve the
session (via that cookie) to chain the upstream logout. onLogout now resolves to
the chosen landing URL so the /logout route guard follows the provider logout URL
too. Adds the oidcLogout client-config accessor and an instance.portal session
marker so the Portal peer is identified explicitly rather than by URL shape.
2026-06-24 15:22:04 +00:00
Michael Mayer
50976f427e Auth: Add RP-initiated OIDC logout via PHOTOPRISM_OIDC_LOGOUT #5684 #5433
Sign-out of an OIDC session redirects the browser to the provider's
discovered end_session_endpoint (id_token_hint + absolute
post_logout_redirect_uri) when PHOTOPRISM_OIDC_LOGOUT is enabled, so the
provider SSO session ends and the next login re-prompts. Opt-in (default
off); local/LDAP logout and providers without an end-session endpoint fall
back to local logout.

Adds the shared CE /api/v1/oauth/logout route and OAuthLogoutHandler hook
plus the Portal OP end_session_endpoint advertised in the Portal discovery
document, for Portal builds to serve via the override hook.
2026-06-24 17:21:00 +02:00
Michael Mayer
95ae2be80c Frontend: Harden i18n positional shim against literal % in translations #5682
interpolatePositional now matches only real Go fmt verb letters and disallows the space flag, so a literal percent in a translated message (e.g. "100% sicher") is not mistaken for a verb and corrupted. Adds a regression test.
2026-06-23 13:30:22 +00:00
Michael Mayer
2fd9e93247 Tests: Fix label toast assertions after %{name} change #5682
labels.vue now notifies via $gettext("Removed/Added %{name}"); update the expected toast strings to match.
2026-06-23 12:07:01 +00:00
Michael Mayer
9ecdb0ae26 UX: Render backend notifications in the current UI locale #5682
Backend *Msg helpers now publish the untranslated message id and params alongside the rendered fallback; the frontend translates the id and applies a positional-placeholder shim so notifications render in each user's UI language.
2026-06-23 09:06:46 +00:00
Michael Mayer
544a4cfef2 Lightbox: Handle each Escape press once in face-marker mode #5672 2026-06-23 10:51:21 +02:00
Ömer Duran
4443c892ef
UX: Refine face-marker overlay performance and interaction #5672 #5677 2026-06-23 07:38:23 +02:00
Michael Mayer
74339513f4 Lightbox: Native icon buttons, contain inline-editor Enter #1307 #4966 2026-06-18 10:33:16 +00:00
Michael Mayer
63e0f02727 Lightbox: Pause slideshow on any content interaction #352 #5623 #5671 2026-06-18 11:43:15 +02:00
Theresa Gresch
acb04f2a05 Tests: Improve acceptance tests 2026-06-18 11:31:03 +02:00
Ömer Duran
3b21cf111b
Lightbox: Stop slideshow on sphere gestures #352 #4718 #5623 #5671 2026-06-18 11:13:59 +02:00
Michael Mayer
d52554fc50 Lightbox: Add 16K thumbnails and refine 360° viewer routing #5669 #5623 2026-06-17 04:12:41 +00:00
Ömer Duran
3ea547a564
Lightbox: Add support for 360° photos and videos #352 #4718 #5623
* Lightbox: Add 360° photo and video sphere viewer support

Wires Photo Sphere Viewer v5 into the lightbox for equirectangular
media. Backend detects UsePanoramaViewer and IsPhotosphere markers
alongside ProjectionType, and exposes Panorama + Projection in the
viewer / search result DTOs so the frontend can dispatch on the
projection type. The renderer (ThreeJS + PSV core + video plugin +
equirectangular video adapter) is lazy-loaded into a dedicated
webpack chunk so the base bundle is unchanged for users with no
360° media. PhotoPrism's existing lightbox controls drive the
underlying HTMLVideoElement that the PSV adapter creates for 360°
videos. Includes Vitest coverage for the sphere helper and the
lightbox dispatch, plus a TestCafe smoke spec and three new
ExifTool metadata fixtures.

* Lightbox: Fix 360° controls leak, hide zoom button, add sidebar icon

* Lightbox: Fix 360° touch panning and navigation arrows

* Lightbox: Fix fast swipe switching photos on 360° slides

Suppress PhotoSwipe swipe/drag navigation at its source via the dispatched pointer hook while a sphere slide is active, since the per-element gesture trap is outrun by a fast swipe whose pointer leaves the sphere container (touch-capable Windows). UI controls stay excluded so buttons and arrows still navigate.

* Lightbox: Route only equirectangular media to the 360° viewer

Routing was based on the loose photo_panorama flag for videos, which is also
true for cubemap and merely-wide (aspect > 1.9) videos, so non-equirectangular
media wrongly opened, distorted, in the sphere viewer. Route strictly on the
equirectangular projection for both photos and videos. Surface the video file's
projection in the viewer DTO via Photo.MediaProjection() (the primary search row
is a poster JPEG with no projection). Add an MP4 metadata-detection test.

* Lightbox: Open tagless 2:1 360° videos in the sphere viewer

Many 360° videos carry no projection tag PhotoPrism can read, so strict
equirectangular-projection routing left them in the flat player. Fall back to
equirectangular's defining 2:1 frame for panorama-flagged videos without a
projection, via is360Equirectangular() in common/sphere.js, while cubemap and
ultrawide (~2.35:1) clips stay flat.

* Tests: Make the 360° sphere video test assert instead of skipping
2026-06-17 03:26:25 +02:00
Michael Mayer
bf081bd68b Users: Add canEnableLogin() to gate the Web Login toggle 2026-06-17 00:26:18 +00:00
Michael Mayer
50e1e595de Places: Group coincident map photos by screen proximity #5643
Past the cluster zoom threshold, group un-clustered photo features by
projected screen distance instead of exact coordinates, so pictures at
the same place stack into one counter marker even when their stored
coordinates differ slightly (GPS jitter, tile quantization). Key each
stack by a stable member UID and open it by its bounding box so the
panel lists exactly those pictures.
2026-06-16 23:19:37 +00:00
Michael Mayer
f31db8a03b Tests: Add missing $util.shouldOpenOnHover mock to people-tab suite #5666
PActionMenu reads $util.shouldOpenOnHover() in data(); the suite only
passed because PActionMenu was stubbed. Complete the $util mock so the
tests no longer depend on that stub to hide the call, which otherwise
fails with "this.$util.shouldOpenOnHover is not a function".
2026-06-16 03:12:34 +00:00
Michael Mayer
2f94755af6 People: Seed type-ahead cache on name save to fix stale suggestions #5666
Naming faces in quick succession left the second new name out of the
people type-ahead until a manual reload, because eviction relied on the
subjects WS event, which can lag the next focus-triggered read.

Marker.setName() and Face.setName() now seed the shared people cache with
the saved name so it is suggestible immediately; the rename path seeds via
recognized.vue (Subject.update() also backs hide/show, so it can't host the
seed). Harden the cache itself: get() snapshots an epoch so a fetch that
resolves after an evict/upsert no longer clobbers the slot with stale data,
and upsertPeople merges idempotently. Reload new.vue suggestions on focus
to match the photo people tab.
2026-06-16 02:57:58 +00:00
Michael Mayer
cbc548d771 Frontend: Revert menu/action icons back to vertical dots
see c13c260

Signed-off-by: Michael Mayer <michael@photoprism.app>
2026-06-16 04:02:33 +02:00
Michael Mayer
bf14649ce4 Places: Stack photos at identical coordinates on the map #5643
Group un-clustered geo features by exact coordinates so that pictures
sharing a spot render as one stack marker with a counter instead of
overlapping, hidden duplicates once zoomed past the cluster threshold.
Clicking such a stack opens the location's collection panel rather than
a radius search that surfaced unrelated nearby photos.

Key the embedded photo list by latlng so an already-open panel re-runs
its search for the newly selected cluster or stack.
2026-06-16 01:56:03 +00:00
Michael Mayer
fe0a59a138 Users: Exclude remote LDAP accounts from canHavePassword() 2026-06-16 00:41:34 +00:00
Michael Mayer
85d58f7cbe Users: Add canHavePassword() to gate password change actions 2026-06-16 00:30:13 +00:00
Michael Mayer
aff25636b5 People: Fix name-save re-entry and avoid full-list refetch #5666
Guard recognized.vue onSave against re-submission while the people cache
resolves by setting busy before the async lookup. Drop updatePersonList so
Browse Pictures / Set Cover no longer evict and refetch the whole people
list; the name combobox reloads suggestions on focus and WS events keep
the cache current. Make the people-tab test seed via a mocked getPeople.
2026-06-15 16:50:35 +00:00
Michael Mayer
997ff2e70d People: Load name suggestions from typeahead cache #5666
Move the people name list out of the client config into the shared
typeahead cache (getPeople, backed by GET /api/v1/subjects), matching
labels and albums. Consumers load suggestions on demand and the cache
evicts on people.* / subjects.* events and clears on logout; the
config.js people machinery is removed and ClientConfig keeps only
count.people for the navigation badge.
2026-06-15 14:38:04 +00:00
Michael Mayer
a3f9df12ca Tests: Fix cleanup album revert helper for deleted albums #5646
helperAfterEach reverted photos before restoring albums, so re-adding a
photo to an album a test soft-deleted targeted a dead UID, 404'd, and
cascaded into helperFailures. Determine album changes first so the photo
loop can skip albums revertAlbums restores (recreated foreign-owned ones
get a new UID), and tolerate 404 on add-to-album.
2026-06-15 13:04:25 +00:00
Michael Mayer
2fd0ed8631 Events: UID-only camera/lens/country payloads & people refetch #5663 #1307
Make the cameras/lenses/countries content-channel events publish only a
stable identity ([]string of slug or ISO code) instead of full entity
models, matching the UID-only invariant of every other content channel.
The update path no longer republishes count.* (an edit does not change
the count).

Restore surgical people updates: onPeople refetches only the affected
people by UID via Subject.search (refetchPeople) and upserts them in
place instead of forcing a full client-config reload (a #1307 regression).

Document the dormant folders.*/faces.* subscribers (no publisher, not
forwarded) and fix the misleading payload example in the event README.
2026-06-15 11:37:26 +00:00
Michael Mayer
41369dd5ce Tests: Fix duplicate testIDs and inverted reversion logic #5646 2026-06-15 07:11:00 +00:00
Keith Martin
3a4140b686 Tests: add ability to run the reversion tests, remove outdated config, remove console.log and mark test as skipped due to inability to test 2026-06-15 08:53:33 +02:00
Keith Martin
389b361a25 Tests: Improve test logging (when SHOW_LOGS is true in environment 2026-06-15 08:53:33 +02:00
Keith Martin
541c554206 Tests: Fix issue with NotYetExisting being clicked due to middle of dialog box selection issue 2026-06-15 08:53:33 +02:00
Keith Martin
b8a2091e6c Tests: Performance improvements and utilisation of reversion capabilities 2026-06-15 08:53:33 +02:00
Keith Martin
592a9958e1 Tests: Implement beforeEach, afterEach and before for after each test change reversions 2026-06-15 08:53:33 +02:00
Keith Martin
9ca202b65a Tests: Add Reversion capability and tests thereof 2026-06-15 08:53:33 +02:00
Michael Mayer
b24e84f5f5 Frontend: Upgrade Vitest to v4 and Vite to v8 to drop esbuild #5659 2026-06-14 16:15:51 +00:00
Michael Mayer
0b0efaeddd Auth: Replace OIDC login error alert with the notification toast 2026-06-14 10:24:13 +00:00
Michael Mayer
e308fd7b58 Auth: Land cluster sign-out on the Portal login without return_to 2026-06-12 19:56:44 +00:00
Michael Mayer
9f9fce0976 Tests: Update stale portal paths after frontend URI change 2026-06-12 19:56:38 +00:00
Michael Mayer
ebf4a88183 Tests: Add unit tests for label and subject change notifications #1307 2026-06-11 17:54:02 +02:00
Michael Mayer
ae9a10431a Frontend: Add User isCurrentUser, isAdmin & isClusterAdmin helpers 2026-06-11 15:49:30 +00:00
Michael Mayer
7486982342 Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
Michael Mayer
08945d25d7 Frontend: Resolve theme asset icons by basename or /_theme path
themeAssetUri now accepts a bare filename (logo.svg, the recommended
form since the theme author can't get the mount path wrong) as well as a
root-relative /_theme/... path, resolving either against the base URI so
they load on path-prefixed deployments. Absolute, protocol-relative, data,
and already-prefixed values pass through. Memoized via memoize-one (the
base URI is fixed once the config is loaded).
2026-06-11 10:46:14 +02:00
Michael Mayer
d3a94bb8a8 Auth: Keep the deep-link target on a manual OIDC sign-in
onOidcLogin arms the per-tab OIDC attempt guard instead of the post-login redirect-loop guard, so an authenticated return within the loop window resumes the stored deep link or Portal return_to rather than discarding it. Updates the login spec test to pin the guard choice.
2026-06-10 20:08:36 +00:00
Michael Mayer
b59ebb994b Frontend: Resolve theme icon paths against the base URI
A theme-provided icon (theme variables.icon, e.g. /_theme/logo.svg) was
used verbatim, so on a path-prefixed deployment the browser requested it
without the /i/<node> base and got a 404 (the custom logo silently fell
back). Resolve it through a themeAssetUri helper in getIcon/getLoginIcon;
absolute, protocol-relative, data, and already-prefixed paths pass through
and root deployments are unaffected.
2026-06-10 19:02:34 +02:00