From 4e0e1b2c5cad3203df6af637b5feb793b6505664 Mon Sep 17 00:00:00 2001 From: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com> Date: Wed, 28 Jan 2026 14:05:21 -0600 Subject: [PATCH] fix: escape handling (#25627) --- .../timeline/TimelineAssetViewer.svelte | 1 - .../actions/TimelineKeyboardActions.svelte | 66 +++++++++---------- web/src/lib/modals/AlbumOptionsModal.svelte | 3 +- web/src/lib/services/album.service.ts | 16 ++++- .../[[assetId=id]]/+page.svelte | 39 ++--------- .../[[assetId=id]]/+page.svelte | 11 +--- 6 files changed, 56 insertions(+), 80 deletions(-) diff --git a/web/src/lib/components/timeline/TimelineAssetViewer.svelte b/web/src/lib/components/timeline/TimelineAssetViewer.svelte index f61d88c1c4..e26d969858 100644 --- a/web/src/lib/components/timeline/TimelineAssetViewer.svelte +++ b/web/src/lib/components/timeline/TimelineAssetViewer.svelte @@ -97,7 +97,6 @@ }; const handleClose = async (asset: { id: string }) => { - assetViewingStore.showAssetViewer(false); invisible = true; $gridScrollTarget = { at: asset.id }; await navigate({ targetRoute: 'current', assetId: null, assetGridRouteSearchParams: $gridScrollTarget }); diff --git a/web/src/lib/components/timeline/actions/TimelineKeyboardActions.svelte b/web/src/lib/components/timeline/actions/TimelineKeyboardActions.svelte index 6bc9ba3549..77f5300180 100644 --- a/web/src/lib/components/timeline/actions/TimelineKeyboardActions.svelte +++ b/web/src/lib/components/timeline/actions/TimelineKeyboardActions.svelte @@ -141,43 +141,41 @@ } }; - let shortcutList = $derived( - (() => { - if (searchStore.isSearchEnabled || $showAssetViewer) { - return []; - } + const shortcutList = $derived.by(() => { + if (searchStore.isSearchEnabled || $showAssetViewer) { + return []; + } - const shortcuts: ShortcutOptions[] = [ - { shortcut: { key: '?', shift: true }, onShortcut: handleOpenShortcutModal }, - { shortcut: { key: '/' }, onShortcut: () => goto(Route.explore()) }, - { shortcut: { key: 'A', ctrl: true }, onShortcut: () => selectAllAssets(timelineManager, assetInteraction) }, - { shortcut: { key: 'ArrowRight' }, onShortcut: () => setFocusTo('earlier', 'asset') }, - { shortcut: { key: 'ArrowLeft' }, onShortcut: () => setFocusTo('later', 'asset') }, - { shortcut: { key: 'D' }, onShortcut: () => setFocusTo('earlier', 'day') }, - { shortcut: { key: 'D', shift: true }, onShortcut: () => setFocusTo('later', 'day') }, - { shortcut: { key: 'M' }, onShortcut: () => setFocusTo('earlier', 'month') }, - { shortcut: { key: 'M', shift: true }, onShortcut: () => setFocusTo('later', 'month') }, - { shortcut: { key: 'Y' }, onShortcut: () => setFocusTo('earlier', 'year') }, - { shortcut: { key: 'Y', shift: true }, onShortcut: () => setFocusTo('later', 'year') }, - { shortcut: { key: 'G' }, onShortcut: handleOpenDateModal }, - ]; - if (onEscape) { - shortcuts.push({ shortcut: { key: 'Escape' }, onShortcut: onEscape }); - } + const shortcuts: ShortcutOptions[] = [ + { shortcut: { key: '?', shift: true }, onShortcut: handleOpenShortcutModal }, + { shortcut: { key: '/' }, onShortcut: () => goto(Route.explore()) }, + { shortcut: { key: 'A', ctrl: true }, onShortcut: () => selectAllAssets(timelineManager, assetInteraction) }, + { shortcut: { key: 'ArrowRight' }, onShortcut: () => setFocusTo('earlier', 'asset') }, + { shortcut: { key: 'ArrowLeft' }, onShortcut: () => setFocusTo('later', 'asset') }, + { shortcut: { key: 'D' }, onShortcut: () => setFocusTo('earlier', 'day') }, + { shortcut: { key: 'D', shift: true }, onShortcut: () => setFocusTo('later', 'day') }, + { shortcut: { key: 'M' }, onShortcut: () => setFocusTo('earlier', 'month') }, + { shortcut: { key: 'M', shift: true }, onShortcut: () => setFocusTo('later', 'month') }, + { shortcut: { key: 'Y' }, onShortcut: () => setFocusTo('earlier', 'year') }, + { shortcut: { key: 'Y', shift: true }, onShortcut: () => setFocusTo('later', 'year') }, + { shortcut: { key: 'G' }, onShortcut: handleOpenDateModal }, + ]; + if (onEscape) { + shortcuts.push({ shortcut: { key: 'Escape' }, onShortcut: onEscape }); + } - if (assetInteraction.selectionActive) { - shortcuts.push( - { shortcut: { key: 'Delete' }, onShortcut: onDelete }, - { shortcut: { key: 'Delete', shift: true }, onShortcut: () => trashOrDelete(true) }, - { shortcut: { key: 'D', ctrl: true }, onShortcut: () => deselectAllAssets() }, - { shortcut: { key: 's' }, onShortcut: () => onStackAssets() }, - { shortcut: { key: 'a', shift: true }, onShortcut: toggleArchive }, - ); - } + if (assetInteraction.selectionActive) { + shortcuts.push( + { shortcut: { key: 'Delete' }, onShortcut: onDelete }, + { shortcut: { key: 'Delete', shift: true }, onShortcut: () => trashOrDelete(true) }, + { shortcut: { key: 'D', ctrl: true }, onShortcut: () => deselectAllAssets() }, + { shortcut: { key: 's' }, onShortcut: () => onStackAssets() }, + { shortcut: { key: 'a', shift: true }, onShortcut: toggleArchive }, + ); + } - return shortcuts; - })(), - ); + return shortcuts; + }); diff --git a/web/src/lib/modals/AlbumOptionsModal.svelte b/web/src/lib/modals/AlbumOptionsModal.svelte index 4553f022df..392389fe92 100644 --- a/web/src/lib/modals/AlbumOptionsModal.svelte +++ b/web/src/lib/modals/AlbumOptionsModal.svelte @@ -3,6 +3,7 @@ import HeaderActionButton from '$lib/components/HeaderActionButton.svelte'; import OnEvents from '$lib/components/OnEvents.svelte'; import UserAvatar from '$lib/components/shared-components/user-avatar.svelte'; + import { AlbumPageViewMode } from '$lib/constants'; import { getAlbumActions, handleRemoveUserFromAlbum, @@ -55,7 +56,7 @@ sharedLinks = sharedLinks.filter(({ id }) => sharedLink.id !== id); }; - const { AddUsers, CreateSharedLink } = $derived(getAlbumActions($t, album)); + const { AddUsers, CreateSharedLink } = $derived(getAlbumActions($t, album, AlbumPageViewMode.OPTIONS)); let sharedLinks: SharedLinkResponseDto[] = $state([]); diff --git a/web/src/lib/services/album.service.ts b/web/src/lib/services/album.service.ts index 240867be54..ac0a1045b3 100644 --- a/web/src/lib/services/album.service.ts +++ b/web/src/lib/services/album.service.ts @@ -1,5 +1,6 @@ import { goto } from '$app/navigation'; import ToastAction from '$lib/components/ToastAction.svelte'; +import { AlbumPageViewMode } from '$lib/constants'; import { eventManager } from '$lib/managers/event-manager.svelte'; import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import AlbumAddUsersModal from '$lib/modals/AlbumAddUsersModal.svelte'; @@ -25,7 +26,7 @@ import { type UserResponseDto, } from '@immich/sdk'; import { modalManager, toastManager, type ActionItem } from '@immich/ui'; -import { mdiLink, mdiPlus, mdiPlusBoxOutline, mdiShareVariantOutline, mdiUpload } from '@mdi/js'; +import { mdiArrowLeft, mdiLink, mdiPlus, mdiPlusBoxOutline, mdiShareVariantOutline, mdiUpload } from '@mdi/js'; import { type MessageFormatter } from 'svelte-i18n'; import { get } from 'svelte/store'; @@ -39,7 +40,7 @@ export const getAlbumsActions = ($t: MessageFormatter) => { return { Create }; }; -export const getAlbumActions = ($t: MessageFormatter, album: AlbumResponseDto) => { +export const getAlbumActions = ($t: MessageFormatter, album: AlbumResponseDto, viewMode: AlbumPageViewMode) => { const isOwned = get(user).id === album.ownerId; const Share: ActionItem = { @@ -66,7 +67,16 @@ export const getAlbumActions = ($t: MessageFormatter, album: AlbumResponseDto) = onAction: () => modalManager.show(SharedLinkCreateModal, { albumId: album.id }), }; - return { Share, AddUsers, CreateSharedLink }; + const Close: ActionItem = { + title: $t('go_back'), + type: $t('command'), + icon: mdiArrowLeft, + onAction: () => goto(Route.albums()), + $if: () => viewMode === AlbumPageViewMode.VIEW, + shortcuts: { key: 'Escape' }, + }; + + return { Share, AddUsers, CreateSharedLink, Close }; }; export const getAlbumAssetsActions = ($t: MessageFormatter, album: AlbumResponseDto, assets: TimelineAsset[]) => { 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 c50d296de5..88baa416b8 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 @@ -1,5 +1,5 @@ @@ -346,7 +319,7 @@ onAlbumUserDelete={refreshAlbum} onAlbumUpdate={(newAlbum) => (album = newAlbum)} /> - +
@@ -512,7 +485,7 @@ {:else} {#if viewMode === AlbumPageViewMode.VIEW} - goto(backUrl)}> + goto(Route.albums())}> {#snippet trailing()} diff --git a/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte index ed005b1f9c..641510aae1 100644 --- a/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -33,7 +33,6 @@ import { Route } from '$lib/route'; import { getPersonActions } from '$lib/services/person.service'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; - import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { locale } from '$lib/stores/preferences.store'; import { preferences } from '$lib/stores/user.store'; import { websocketEvents } from '$lib/stores/websocket'; @@ -61,7 +60,6 @@ let { data }: Props = $props(); let numberOfAssets = $derived(data.statistics.assets); - let { isViewing: showAssetViewer } = assetViewingStore; let timelineManager = $state() as TimelineManager; const options = $derived({ visibility: AssetVisibility.Timeline, personId: data.person.id }); @@ -106,16 +104,13 @@ }); const handleEscape = async () => { - if ($showAssetViewer) { - return; - } if (assetInteraction.selectionActive) { assetInteraction.clearMultiselect(); return; - } else { - await goto(previousRoute); - return; } + + await goto(previousRoute); + return; }; const updateAssetCount = async () => {