From 06f3b4f25922d08d00ff06c42d4c4f9a4ff38b2a Mon Sep 17 00:00:00 2001 From: Mees Frensel <33722705+meesfrensel@users.noreply.github.com> Date: Tue, 23 Jun 2026 17:08:46 +0200 Subject: [PATCH] refactor(web): simple actions (#29257) --- .../asset-viewer/AssetViewer.svelte | 12 ++-- .../asset-viewer/AssetViewerNavBar.svelte | 57 +++++++------------ .../actions/SetProfilePictureAction.svelte | 20 ------- web/src/lib/services/asset.service.spec.ts | 6 ++ web/src/lib/services/asset.service.ts | 35 +++++++++++- 5 files changed, 65 insertions(+), 65 deletions(-) delete mode 100644 web/src/lib/components/asset-viewer/actions/SetProfilePictureAction.svelte diff --git a/web/src/lib/components/asset-viewer/AssetViewer.svelte b/web/src/lib/components/asset-viewer/AssetViewer.svelte index 9606077d52..e99bf13148 100644 --- a/web/src/lib/components/asset-viewer/AssetViewer.svelte +++ b/web/src/lib/components/asset-viewer/AssetViewer.svelte @@ -110,11 +110,11 @@ let sharedLink = getSharedLink(); let fullscreenElement = $state(); - let playOriginalVideo = $state($alwaysLoadOriginalVideo); + let isPlayingOriginalVideo = $state($alwaysLoadOriginalVideo); let slideshowStartAssetId = $state(); const setPlayOriginalVideo = (value: boolean) => { - playOriginalVideo = value; + isPlayingOriginalVideo = value; }; const refreshStack = async () => { @@ -504,7 +504,7 @@ {onUndoDelete} onClose={onClose ? () => onClose(stack?.primaryAssetId ?? asset.id) : undefined} {onRemoveFromAlbum} - {playOriginalVideo} + {isPlayingOriginalVideo} {setPlayOriginalVideo} /> @@ -542,7 +542,7 @@ onClose={closeViewer} onVideoEnded={() => navigateAsset()} onVideoStarted={handleVideoStarted} - {playOriginalVideo} + playOriginalVideo={isPlayingOriginalVideo} /> {:else if viewerKind === 'LiveVideoViewer'} navigateAsset('previous')} onNextAsset={() => navigateAsset('next')} onVideoEnded={() => (assetViewerManager.isPlayingMotionPhoto = false)} - {playOriginalVideo} + playOriginalVideo={isPlayingOriginalVideo} /> {:else if viewerKind === 'ImagePanaramaViewer'} @@ -574,7 +574,7 @@ onClose={closeViewer} onVideoEnded={() => navigateAsset()} onVideoStarted={handleVideoStarted} - {playOriginalVideo} + playOriginalVideo={isPlayingOriginalVideo} /> {/if} diff --git a/web/src/lib/components/asset-viewer/AssetViewerNavBar.svelte b/web/src/lib/components/asset-viewer/AssetViewerNavBar.svelte index d6d90aca8a..241ab6a4f2 100644 --- a/web/src/lib/components/asset-viewer/AssetViewerNavBar.svelte +++ b/web/src/lib/components/asset-viewer/AssetViewerNavBar.svelte @@ -1,5 +1,4 @@ @@ -169,41 +170,21 @@ {#if person} {/if} - {#if asset.type === AssetTypeEnum.Image && !isLocked} - - {/if} - {#if !isLocked} - {#if isOwner} - - {#if !asset.isArchived && !asset.isTrashed} - goto(Route.photos({ at: stack?.primaryAssetId ?? asset.id }))} - text={$t('view_in_timeline')} - /> - {/if} - {/if} - {#if !asset.isArchived && !asset.isTrashed && smartSearchEnabled} - goto(Route.search({ queryAssetId: stack?.primaryAssetId ?? asset.id }))} - text={$t('view_similar_photos')} - /> - {/if} + + + {#if isOwner && !isLocked} + {/if} + + {#if !asset.isTrashed && isOwner} {/if} - {#if asset.type === AssetTypeEnum.Video} - setPlayOriginalVideo(!playOriginalVideo)} - text={playOriginalVideo ? $t('play_transcoded_video') : $t('play_original_video')} - /> - {/if} + + {#if isOwner}
diff --git a/web/src/lib/components/asset-viewer/actions/SetProfilePictureAction.svelte b/web/src/lib/components/asset-viewer/actions/SetProfilePictureAction.svelte deleted file mode 100644 index d647e67bf8..0000000000 --- a/web/src/lib/components/asset-viewer/actions/SetProfilePictureAction.svelte +++ /dev/null @@ -1,20 +0,0 @@ - - - modalManager.show(ProfileImageCropperModal, { asset })} - text={$t('set_as_profile_picture')} -/> diff --git a/web/src/lib/services/asset.service.spec.ts b/web/src/lib/services/asset.service.spec.ts index 7c533997b1..68a3b0db09 100644 --- a/web/src/lib/services/asset.service.spec.ts +++ b/web/src/lib/services/asset.service.spec.ts @@ -31,6 +31,12 @@ vitest.mock('$lib/utils', async () => { }; }); +vi.mock(import('$lib/managers/feature-flags-manager.svelte'), function () { + return { + featureFlagsManager: { init: vi.fn(), loadFeatureFlags: vi.fn(), value: {} } as never, + }; +}); + describe('AssetService', () => { describe('getAssetActions', () => { beforeEach(() => { diff --git a/web/src/lib/services/asset.service.ts b/web/src/lib/services/asset.service.ts index 3e629b413f..1288aa6100 100644 --- a/web/src/lib/services/asset.service.ts +++ b/web/src/lib/services/asset.service.ts @@ -11,8 +11,10 @@ import { } from '@immich/sdk'; import { modalManager, toastManager, type ActionItem } from '@immich/ui'; import { + mdiAccountCircleOutline, mdiAlertOutline, mdiCogRefreshOutline, + mdiCompare, mdiContentCopy, mdiDatabaseRefreshOutline, mdiDownload, @@ -22,6 +24,7 @@ import { mdiHeart, mdiHeartOutline, mdiImageRefreshOutline, + mdiImageSearch, mdiInformationOutline, mdiMagnifyMinusOutline, mdiMagnifyPlusOutline, @@ -34,14 +37,18 @@ import { mdiTune, } from '@mdi/js'; import type { MessageFormatter } from 'svelte-i18n'; +import { goto } from '$app/navigation'; import { ProjectionType } from '$lib/constants'; import { assetMultiSelectManager } from '$lib/managers/asset-multi-select-manager.svelte'; import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte'; import { eventManager } from '$lib/managers/event-manager.svelte'; +import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte'; import AssetAddToAlbumModal from '$lib/modals/AssetAddToAlbumModal.svelte'; import AssetTagModal from '$lib/modals/AssetTagModal.svelte'; +import ProfileImageCropperModal from '$lib/modals/ProfileImageCropperModal.svelte'; import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte'; +import { Route } from '$lib/route'; import { SlideshowState, slideshowStore } from '$lib/stores/slideshow.store'; import { getAssetMediaUrl, getSharedLink, sleep } from '$lib/utils'; import { downloadUrl } from '$lib/utils'; @@ -92,10 +99,11 @@ export const getAssetBulkActions = ($t: MessageFormatter) => { return { AddToAlbum, RefreshFacesJob, RefreshMetadataJob, RegenerateThumbnailJob, TranscodeVideoJob }; }; -export const getAssetActions = ($t: MessageFormatter, asset: AssetResponseDto) => { +export const getAssetActions = ($t: MessageFormatter, asset: AssetResponseDto & { stackPrimaryAssetId?: string }) => { const sharedLink = getSharedLink(); const authUser = authManager.authenticated ? authManager.user : undefined; const isOwner = !!(authUser && authUser.id === asset.ownerId); + const smartSearchEnabled = featureFlagsManager.value.smartSearch; const Share: ActionItem = { title: $t('share'), @@ -242,6 +250,28 @@ export const getAssetActions = ($t: MessageFormatter, asset: AssetResponseDto) = shortcuts: [{ key: 'e' }], }; + const SetProfilePicture: ActionItem = { + title: $t('set_as_profile_picture'), + icon: mdiAccountCircleOutline, + $if: () => asset.type === AssetTypeEnum.Image && asset.visibility !== AssetVisibility.Locked, + onAction: () => modalManager.show(ProfileImageCropperModal, { asset }), + }; + + const ViewInTimeline: ActionItem = { + title: $t('view_in_timeline'), + icon: mdiImageSearch, + $if: () => isOwner && asset.visibility !== AssetVisibility.Locked && !asset.isArchived && !asset.isTrashed, + onAction: () => goto(Route.photos({ at: asset.stackPrimaryAssetId ?? asset.id })), + }; + + const ViewSimilar: ActionItem = { + title: $t('view_similar_photos'), + icon: mdiCompare, + $if: () => + asset.visibility !== AssetVisibility.Locked && !asset.isArchived && !asset.isTrashed && smartSearchEnabled, + onAction: () => goto(Route.search({ queryAssetId: asset.stackPrimaryAssetId ?? asset.id })), + }; + const RefreshFacesJob: ActionItem = { title: $t('refresh_faces'), icon: mdiHeadSyncOutline, @@ -286,6 +316,9 @@ export const getAssetActions = ($t: MessageFormatter, asset: AssetResponseDto) = Tag, TagPeople, Edit, + SetProfilePicture, + ViewInTimeline, + ViewSimilar, RefreshFacesJob, RefreshMetadataJob, RegenerateThumbnailJob,