From d8ca2106416cc953d3aaa65bdbf06847e78da82a Mon Sep 17 00:00:00 2001 From: idubnori Date: Tue, 2 Dec 2025 01:25:12 +0900 Subject: [PATCH] chore(web): minor UX improvements of "view asset owners" feature (#24319) * feat: toggle in options modal * feat(i18n): add labels to display who uploaded each asset and show asset owners * feat: migrate asset owner settings to TimelineManager and update AlbumOptionsModal * Revert "feat(i18n): add labels to display who uploaded each asset and show asset owners" This reverts commit cf8f4eb1356d45d88fc4625c90a5b511d4d86333. * fix: simplify AlbumOptionsModal invocation and update aria-label for asset owners * feat(i18n): add label for viewing asset owners in the interface * feat: add tests for showAssetOwners functionality in TimelineManager * chore: move asset owner visibility toggle to kebabu menu --- .../timeline-manager.svelte.spec.ts | 38 +++++++++++++++++++ .../timeline-manager.svelte.ts | 14 +++++++ .../[[assetId=id]]/+page.svelte | 24 ++++++------ 3 files changed, 63 insertions(+), 13 deletions(-) diff --git a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.spec.ts b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.spec.ts index 2c63348f88..bb58704214 100644 --- a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.spec.ts +++ b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.spec.ts @@ -692,4 +692,42 @@ describe('TimelineManager', () => { expect(discoveredAssets.size).toBe(assetCount); }); }); + + describe('showAssetOwners', () => { + const LS_KEY = 'album-show-asset-owners'; + + beforeEach(() => { + // ensure clean state + globalThis.localStorage?.removeItem(LS_KEY); + }); + + it('defaults to false', () => { + const timelineManager = new TimelineManager(); + expect(timelineManager.showAssetOwners).toBe(false); + }); + + it('setShowAssetOwners updates value', () => { + const timelineManager = new TimelineManager(); + timelineManager.setShowAssetOwners(true); + expect(timelineManager.showAssetOwners).toBe(true); + timelineManager.setShowAssetOwners(false); + expect(timelineManager.showAssetOwners).toBe(false); + }); + + it('toggleShowAssetOwners flips value', () => { + const timelineManager = new TimelineManager(); + expect(timelineManager.showAssetOwners).toBe(false); + timelineManager.toggleShowAssetOwners(); + expect(timelineManager.showAssetOwners).toBe(true); + timelineManager.toggleShowAssetOwners(); + expect(timelineManager.showAssetOwners).toBe(false); + }); + + it('persists across instances via localStorage', () => { + const a = new TimelineManager(); + a.setShowAssetOwners(true); + const b = new TimelineManager(); + expect(b.showAssetOwners).toBe(true); + }); + }); }); diff --git a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts index 93b8364930..feba73a0f8 100644 --- a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts +++ b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts @@ -14,6 +14,7 @@ import { } from '$lib/managers/timeline-manager/internal/search-support.svelte'; import { WebsocketSupport } from '$lib/managers/timeline-manager/internal/websocket-support.svelte'; import { CancellableTask } from '$lib/utils/cancellable-task'; +import { PersistedLocalStorage } from '$lib/utils/persisted'; import { setDifference, toTimelineAsset, @@ -90,6 +91,19 @@ export class TimelineManager extends VirtualScrollManager { #options: TimelineManagerOptions = TimelineManager.#INIT_OPTIONS; #updatingIntersections = false; #scrollableElement: HTMLElement | undefined = $state(); + #showAssetOwners = new PersistedLocalStorage('album-show-asset-owners', false); + + get showAssetOwners() { + return this.#showAssetOwners.current; + } + + setShowAssetOwners(value: boolean) { + this.#showAssetOwners.current = value; + } + + toggleShowAssetOwners() { + this.#showAssetOwners.current = !this.#showAssetOwners.current; + } constructor() { super(); diff --git a/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte index 02dc55ead2..3f4d3dd39f 100644 --- a/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -66,6 +66,7 @@ } from '@immich/sdk'; import { Button, Icon, IconButton, modalManager, toastManager } from '@immich/ui'; import { + mdiAccountEye, mdiAccountEyeOutline, mdiArrowLeft, mdiCogOutline, @@ -101,7 +102,9 @@ let isCreatingSharedAlbum = $state(false); let isShowActivity = $state(false); let albumOrder: AssetOrder | undefined = $state(data.album.order); - let showAlbumUsers = $state(false); + + let timelineManager = $state() as TimelineManager; + let showAlbumUsers = $derived(timelineManager?.showAssetOwners ?? false); const assetInteraction = new AssetInteraction(); const timelineInteraction = new AssetInteraction(); @@ -303,7 +306,6 @@ } }); - let timelineManager = $state() as TimelineManager; const options = $derived.by(() => { if (viewMode === AlbumPageViewMode.SELECT_ASSETS) { return { @@ -597,17 +599,6 @@ {#snippet trailing()} - {#if containsEditors} - (showAlbumUsers = !showAlbumUsers)} - /> - {/if} - {#if isEditor} + {#if containsEditors} + timelineManager.toggleShowAssetOwners()} + /> + {/if} {#if album.assetCount > 0}