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 cf8f4eb135.

* 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
This commit is contained in:
idubnori
2025-12-02 01:25:12 +09:00
committed by GitHub
parent ab35afd3b1
commit d8ca210641
3 changed files with 63 additions and 13 deletions

View File

@@ -692,4 +692,42 @@ describe('TimelineManager', () => {
expect(discoveredAssets.size).toBe(assetCount); 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);
});
});
}); });

View File

@@ -14,6 +14,7 @@ import {
} from '$lib/managers/timeline-manager/internal/search-support.svelte'; } from '$lib/managers/timeline-manager/internal/search-support.svelte';
import { WebsocketSupport } from '$lib/managers/timeline-manager/internal/websocket-support.svelte'; import { WebsocketSupport } from '$lib/managers/timeline-manager/internal/websocket-support.svelte';
import { CancellableTask } from '$lib/utils/cancellable-task'; import { CancellableTask } from '$lib/utils/cancellable-task';
import { PersistedLocalStorage } from '$lib/utils/persisted';
import { import {
setDifference, setDifference,
toTimelineAsset, toTimelineAsset,
@@ -90,6 +91,19 @@ export class TimelineManager extends VirtualScrollManager {
#options: TimelineManagerOptions = TimelineManager.#INIT_OPTIONS; #options: TimelineManagerOptions = TimelineManager.#INIT_OPTIONS;
#updatingIntersections = false; #updatingIntersections = false;
#scrollableElement: HTMLElement | undefined = $state(); #scrollableElement: HTMLElement | undefined = $state();
#showAssetOwners = new PersistedLocalStorage<boolean>('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() { constructor() {
super(); super();

View File

@@ -66,6 +66,7 @@
} from '@immich/sdk'; } from '@immich/sdk';
import { Button, Icon, IconButton, modalManager, toastManager } from '@immich/ui'; import { Button, Icon, IconButton, modalManager, toastManager } from '@immich/ui';
import { import {
mdiAccountEye,
mdiAccountEyeOutline, mdiAccountEyeOutline,
mdiArrowLeft, mdiArrowLeft,
mdiCogOutline, mdiCogOutline,
@@ -101,7 +102,9 @@
let isCreatingSharedAlbum = $state(false); let isCreatingSharedAlbum = $state(false);
let isShowActivity = $state(false); let isShowActivity = $state(false);
let albumOrder: AssetOrder | undefined = $state(data.album.order); let albumOrder: AssetOrder | undefined = $state(data.album.order);
let showAlbumUsers = $state(false);
let timelineManager = $state<TimelineManager>() as TimelineManager;
let showAlbumUsers = $derived(timelineManager?.showAssetOwners ?? false);
const assetInteraction = new AssetInteraction(); const assetInteraction = new AssetInteraction();
const timelineInteraction = new AssetInteraction(); const timelineInteraction = new AssetInteraction();
@@ -303,7 +306,6 @@
} }
}); });
let timelineManager = $state<TimelineManager>() as TimelineManager;
const options = $derived.by(() => { const options = $derived.by(() => {
if (viewMode === AlbumPageViewMode.SELECT_ASSETS) { if (viewMode === AlbumPageViewMode.SELECT_ASSETS) {
return { return {
@@ -597,17 +599,6 @@
{#snippet trailing()} {#snippet trailing()}
<CastButton /> <CastButton />
{#if containsEditors}
<IconButton
variant="ghost"
shape="round"
color="secondary"
aria-label={$t('view_asset_owners')}
icon={mdiAccountEyeOutline}
onclick={() => (showAlbumUsers = !showAlbumUsers)}
/>
{/if}
{#if isEditor} {#if isEditor}
<IconButton <IconButton
variant="ghost" variant="ghost"
@@ -668,6 +659,13 @@
color="secondary" color="secondary"
offset={{ x: 175, y: 25 }} offset={{ x: 175, y: 25 }}
> >
{#if containsEditors}
<MenuOption
icon={showAlbumUsers ? mdiAccountEye : mdiAccountEyeOutline}
text={$t('view_asset_owners')}
onClick={() => timelineManager.toggleShowAssetOwners()}
/>
{/if}
{#if album.assetCount > 0} {#if album.assetCount > 0}
<MenuOption <MenuOption
icon={mdiImageOutline} icon={mdiImageOutline}