From 5bb34926169e82f2035807b2a8e32fd7a9731fa6 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Wed, 7 Jan 2026 16:21:19 -0500 Subject: [PATCH] refactor: favorite action (#25121) --- pnpm-lock.yaml | 10 ++-- web/package.json | 2 +- web/src/lib/components/ActionButton.svelte | 2 +- web/src/lib/components/TableButton.svelte | 2 +- .../components/asset-viewer/actions/action.ts | 2 - .../actions/favorite-action.svelte | 51 ---------------- .../asset-viewer/asset-viewer-nav-bar.svelte | 13 ++-- .../asset-viewer/asset-viewer.svelte | 26 ++++---- .../lib/components/timeline/Timeline.svelte | 8 +-- .../timeline/TimelineAssetViewer.svelte | 10 +--- web/src/lib/constants.ts | 2 - web/src/lib/managers/event-manager.svelte.ts | 2 + .../timeline-manager.svelte.ts | 13 ++++ web/src/lib/services/asset.service.ts | 60 ++++++++++++++++++- web/src/lib/utils.ts | 5 +- .../[[assetId=id]]/+page.svelte | 2 - web/src/routes/+layout.svelte | 3 +- 17 files changed, 111 insertions(+), 102 deletions(-) delete mode 100644 web/src/lib/components/asset-viewer/actions/favorite-action.svelte diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5048babbc6..d4ffc00639 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -726,8 +726,8 @@ importers: specifier: file:../open-api/typescript-sdk version: link:../open-api/typescript-sdk '@immich/ui': - specifier: ^0.52.0 - version: 0.52.0(@sveltejs/kit@2.49.2(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)))(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)))(svelte@5.46.1) + specifier: ^0.53.3 + version: 0.53.3(@sveltejs/kit@2.49.2(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)))(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)))(svelte@5.46.1) '@mapbox/mapbox-gl-rtl-text': specifier: 0.2.3 version: 0.2.3(mapbox-gl@1.13.3) @@ -3075,8 +3075,8 @@ packages: peerDependencies: svelte: ^5.0.0 - '@immich/ui@0.52.0': - resolution: {integrity: sha512-ECQIE5qYNpe7Q5+hifIGUDaRQXBkPOp9dvZaHELWWzAGIhbwG+mUYwMpUgU2TO7fV5u8XU6nHyBuC055zApiWQ==} + '@immich/ui@0.53.3': + resolution: {integrity: sha512-Ax7ctU9KIZgET58+PoMQnf1XDOIH76Xa341TXDfLwF96F3fQZ/v4TA7Ycb6hmTwIYGU9arIgqGqQDbuuNxc2vA==} peerDependencies: svelte: ^5.0.0 @@ -15078,7 +15078,7 @@ snapshots: dependencies: svelte: 5.46.1 - '@immich/ui@0.52.0(@sveltejs/kit@2.49.2(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)))(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)))(svelte@5.46.1)': + '@immich/ui@0.53.3(@sveltejs/kit@2.49.2(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)))(svelte@5.46.1)(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.97.1)(terser@5.44.1)(yaml@2.8.2)))(svelte@5.46.1)': dependencies: '@immich/svelte-markdown-preprocess': 0.1.0(svelte@5.46.1) '@internationalized/date': 3.10.0 diff --git a/web/package.json b/web/package.json index f05fc6dee7..ef48a8a92f 100644 --- a/web/package.json +++ b/web/package.json @@ -27,7 +27,7 @@ "@formatjs/icu-messageformat-parser": "^3.0.0", "@immich/justified-layout-wasm": "^0.4.3", "@immich/sdk": "file:../open-api/typescript-sdk", - "@immich/ui": "^0.52.0", + "@immich/ui": "^0.53.3", "@mapbox/mapbox-gl-rtl-text": "0.2.3", "@mdi/js": "^7.4.47", "@photo-sphere-viewer/core": "^5.14.0", diff --git a/web/src/lib/components/ActionButton.svelte b/web/src/lib/components/ActionButton.svelte index e0e7e1eff7..4d0e474389 100644 --- a/web/src/lib/components/ActionButton.svelte +++ b/web/src/lib/components/ActionButton.svelte @@ -9,6 +9,6 @@ const { title, icon, color = 'secondary', onAction } = $derived(action); -{#if action.$if?.() ?? true} +{#if icon && (action.$if?.() ?? true)} onAction(action)} /> {/if} diff --git a/web/src/lib/components/TableButton.svelte b/web/src/lib/components/TableButton.svelte index 844c4c0bf8..619d2f6c27 100644 --- a/web/src/lib/components/TableButton.svelte +++ b/web/src/lib/components/TableButton.svelte @@ -10,6 +10,6 @@ const { title, icon, onAction } = $derived(action); -{#if action.$if?.() ?? true} +{#if icon && (action.$if?.() ?? true)} onAction(action)} /> {/if} diff --git a/web/src/lib/components/asset-viewer/actions/action.ts b/web/src/lib/components/asset-viewer/actions/action.ts index df61b5d073..19cc5afa8d 100644 --- a/web/src/lib/components/asset-viewer/actions/action.ts +++ b/web/src/lib/components/asset-viewer/actions/action.ts @@ -5,8 +5,6 @@ import type { AlbumResponseDto, AssetResponseDto, PersonResponseDto, StackRespon type ActionMap = { [AssetAction.ARCHIVE]: { asset: TimelineAsset }; [AssetAction.UNARCHIVE]: { asset: TimelineAsset }; - [AssetAction.FAVORITE]: { asset: TimelineAsset }; - [AssetAction.UNFAVORITE]: { asset: TimelineAsset }; [AssetAction.TRASH]: { asset: TimelineAsset }; [AssetAction.DELETE]: { asset: TimelineAsset }; [AssetAction.RESTORE]: { asset: TimelineAsset }; diff --git a/web/src/lib/components/asset-viewer/actions/favorite-action.svelte b/web/src/lib/components/asset-viewer/actions/favorite-action.svelte deleted file mode 100644 index ba23570d36..0000000000 --- a/web/src/lib/components/asset-viewer/actions/favorite-action.svelte +++ /dev/null @@ -1,51 +0,0 @@ - - - - - diff --git a/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte b/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte index 38ab066c82..4aa74c9fe5 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte @@ -8,7 +8,6 @@ import ArchiveAction from '$lib/components/asset-viewer/actions/archive-action.svelte'; import DeleteAction from '$lib/components/asset-viewer/actions/delete-action.svelte'; import DownloadAction from '$lib/components/asset-viewer/actions/download-action.svelte'; - import FavoriteAction from '$lib/components/asset-viewer/actions/favorite-action.svelte'; import KeepThisDeleteOthersAction from '$lib/components/asset-viewer/actions/keep-this-delete-others.svelte'; import RatingAction from '$lib/components/asset-viewer/actions/rating-action.svelte'; import RemoveAssetFromStack from '$lib/components/asset-viewer/actions/remove-asset-from-stack.svelte'; @@ -28,7 +27,7 @@ import { photoViewerImgElement } from '$lib/stores/assets-store.svelte'; import { user } from '$lib/stores/user.store'; import { photoZoomState } from '$lib/stores/zoom-image.store'; - import { getAssetJobName, getSharedLink } from '$lib/utils'; + import { getAssetJobName, getSharedLink, withoutIcons } from '$lib/utils'; import type { OnUndoDelete } from '$lib/utils/actions'; import { canCopyImageToClipboard } from '$lib/utils/asset-utils'; import { toTimelineAsset } from '$lib/utils/timeline-util'; @@ -105,6 +104,7 @@ const Close: ActionItem = { title: $t('go_back'), + type: $t('assets'), icon: mdiArrowLeft, $if: () => !!onClose, onAction: () => onClose?.(), @@ -113,7 +113,9 @@ const { Cast } = $derived(getGlobalActions($t)); - const { Share, Offline, PlayMotionPhoto, StopMotionPhoto, Info } = $derived(getAssetActions($t, asset)); + const { Share, Offline, Favorite, Unfavorite, PlayMotionPhoto, StopMotionPhoto, Info } = $derived( + getAssetActions($t, asset), + ); // $: showEditorButton = // isOwner && @@ -128,7 +130,7 @@
+ + {#if isOwner} - {/if} diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte index 19b9bd3555..27fe0f8c74 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -350,15 +350,6 @@ selectedEditType = type; }; - const handleAssetReplace = async ({ oldAssetId, newAssetId }: { oldAssetId: string; newAssetId: string }) => { - if (oldAssetId !== asset.id) { - return; - } - - await new Promise((promise) => setTimeout(promise, 500)); - await goto(`${AppRoute.PHOTOS}/${newAssetId}`); - }; - let isFullScreen = $derived(fullscreenElement !== null); $effect(() => { @@ -391,9 +382,24 @@ preloadManager.preload(cursor.nextAsset); preloadManager.preload(cursor.previousAsset); }); + + const onAssetReplace = async ({ oldAssetId, newAssetId }: { oldAssetId: string; newAssetId: string }) => { + if (oldAssetId !== asset.id) { + return; + } + + await new Promise((promise) => setTimeout(promise, 500)); + await goto(`${AppRoute.PHOTOS}/${newAssetId}`); + }; + + const onAssetUpdate = (update: AssetResponseDto) => { + if (asset.id === update.id) { + asset = update; + } + }; - + diff --git a/web/src/lib/components/timeline/Timeline.svelte b/web/src/lib/components/timeline/Timeline.svelte index b8093da90d..f2ef209ad5 100644 --- a/web/src/lib/components/timeline/Timeline.svelte +++ b/web/src/lib/components/timeline/Timeline.svelte @@ -39,13 +39,7 @@ timelineManager?: TimelineManager; options?: TimelineManagerOptions; assetInteraction: AssetInteraction; - removeAction?: - | AssetAction.UNARCHIVE - | AssetAction.ARCHIVE - | AssetAction.FAVORITE - | AssetAction.UNFAVORITE - | AssetAction.SET_VISIBILITY_TIMELINE - | null; + removeAction?: AssetAction.UNARCHIVE | AssetAction.ARCHIVE | AssetAction.SET_VISIBILITY_TIMELINE | null; withStacked?: boolean; showArchiveIcon?: boolean; isShared?: boolean; diff --git a/web/src/lib/components/timeline/TimelineAssetViewer.svelte b/web/src/lib/components/timeline/TimelineAssetViewer.svelte index 29894308b2..4d88700bf5 100644 --- a/web/src/lib/components/timeline/TimelineAssetViewer.svelte +++ b/web/src/lib/components/timeline/TimelineAssetViewer.svelte @@ -25,13 +25,7 @@ album?: AlbumResponseDto; person?: PersonResponseDto; - removeAction?: - | AssetAction.UNARCHIVE - | AssetAction.ARCHIVE - | AssetAction.FAVORITE - | AssetAction.UNFAVORITE - | AssetAction.SET_VISIBILITY_TIMELINE - | null; + removeAction?: AssetAction.UNARCHIVE | AssetAction.ARCHIVE | AssetAction.SET_VISIBILITY_TIMELINE | null; } let { @@ -141,8 +135,6 @@ switch (action.type) { case AssetAction.ARCHIVE: case AssetAction.UNARCHIVE: - case AssetAction.FAVORITE: - case AssetAction.UNFAVORITE: case AssetAction.ADD: { timelineManager.upsertAssets([action.asset]); break; diff --git a/web/src/lib/constants.ts b/web/src/lib/constants.ts index 72056008cd..fca52c301f 100644 --- a/web/src/lib/constants.ts +++ b/web/src/lib/constants.ts @@ -3,8 +3,6 @@ export const UUID_REGEX = /^[\dA-Fa-f]{8}(?:\b-[\dA-Fa-f]{4}){3}\b-[\dA-Fa-f]{12 export enum AssetAction { ARCHIVE = 'archive', UNARCHIVE = 'unarchive', - FAVORITE = 'favorite', - UNFAVORITE = 'unfavorite', TRASH = 'trash', DELETE = 'delete', RESTORE = 'restore', diff --git a/web/src/lib/managers/event-manager.svelte.ts b/web/src/lib/managers/event-manager.svelte.ts index 6038c3c3f0..f9fa87e0cf 100644 --- a/web/src/lib/managers/event-manager.svelte.ts +++ b/web/src/lib/managers/event-manager.svelte.ts @@ -3,6 +3,7 @@ import type { ReleaseEvent } from '$lib/types'; import type { AlbumResponseDto, ApiKeyResponseDto, + AssetResponseDto, LibraryResponseDto, LoginResponseDto, QueueResponseDto, @@ -24,6 +25,7 @@ export type Events = { ApiKeyUpdate: [ApiKeyResponseDto]; ApiKeyDelete: [ApiKeyResponseDto]; + AssetUpdate: [AssetResponseDto]; AssetReplace: [{ oldAssetId: string; newAssetId: string }]; AlbumUpdate: [AlbumResponseDto]; 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 b0dc30dc6e..7625659e94 100644 --- a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts +++ b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts @@ -1,5 +1,6 @@ import { VirtualScrollManager } from '$lib/managers/VirtualScrollManager/VirtualScrollManager.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte'; +import { eventManager } from '$lib/managers/event-manager.svelte'; import { GroupInsertionCache } from '$lib/managers/timeline-manager/group-insertion-cache.svelte'; import { updateIntersectionMonthGroup } from '$lib/managers/timeline-manager/internal/intersection-support.svelte'; import { updateGeometry } from '$lib/managers/timeline-manager/internal/layout-support.svelte'; @@ -93,6 +94,7 @@ export class TimelineManager extends VirtualScrollManager { #updatingIntersections = false; #scrollableElement: HTMLElement | undefined = $state(); #showAssetOwners = new PersistedLocalStorage('album-show-asset-owners', false); + #unsubscribes: Array<() => void> = []; get showAssetOwners() { return this.#showAssetOwners.current; @@ -108,6 +110,12 @@ export class TimelineManager extends VirtualScrollManager { constructor() { super(); + + const onAssetUpdate = (asset: AssetResponseDto) => this.upsertAssets([toTimelineAsset(asset)]); + + eventManager.on('AssetUpdate', onAssetUpdate); + + this.#unsubscribes.push(() => eventManager.off('AssetUpdate', onAssetUpdate)); } override get scrollTop(): number { @@ -269,6 +277,11 @@ export class TimelineManager extends VirtualScrollManager { public override destroy() { this.disconnect(); this.isInitialized = false; + + for (const unsubscribe of this.#unsubscribes) { + unsubscribe(); + } + super.destroy(); } diff --git a/web/src/lib/services/asset.service.ts b/web/src/lib/services/asset.service.ts index de5223db23..d22d8ab241 100644 --- a/web/src/lib/services/asset.service.ts +++ b/web/src/lib/services/asset.service.ts @@ -3,10 +3,14 @@ import { eventManager } from '$lib/managers/event-manager.svelte'; import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte'; import { user as authUser } from '$lib/stores/user.store'; import { openFileUploadDialog } from '$lib/utils/file-uploader'; -import { AssetVisibility, copyAsset, deleteAssets, type AssetResponseDto } from '@immich/sdk'; -import { modalManager, type ActionItem } from '@immich/ui'; +import { handleError } from '$lib/utils/handle-error'; +import { getFormatter } from '$lib/utils/i18n'; +import { AssetVisibility, copyAsset, deleteAssets, updateAsset, type AssetResponseDto } from '@immich/sdk'; +import { modalManager, toastManager, type ActionItem } from '@immich/ui'; import { mdiAlertOutline, + mdiHeart, + mdiHeartOutline, mdiInformationOutline, mdiMotionPauseOutline, mdiMotionPlayOutline, @@ -16,9 +20,13 @@ import type { MessageFormatter } from 'svelte-i18n'; import { get } from 'svelte/store'; export const getAssetActions = ($t: MessageFormatter, asset: AssetResponseDto) => { + const currentAuthUser = get(authUser); + const isOwner = !!(currentAuthUser && currentAuthUser.id === asset.ownerId); + const Share: ActionItem = { title: $t('share'), icon: mdiShareVariantOutline, + type: $t('assets'), $if: () => !!(get(authUser) && !asset.isTrashed && asset.visibility !== AssetVisibility.Locked), onAction: () => modalManager.show(SharedLinkCreateModal, { assetIds: [asset.id] }), }; @@ -26,6 +34,7 @@ export const getAssetActions = ($t: MessageFormatter, asset: AssetResponseDto) = const PlayMotionPhoto: ActionItem = { title: $t('play_motion_photo'), icon: mdiMotionPlayOutline, + type: $t('assets'), $if: () => !!asset.livePhotoVideoId && !assetViewerManager.isPlayingMotionPhoto, onAction: () => { assetViewerManager.isPlayingMotionPhoto = true; @@ -35,15 +44,35 @@ export const getAssetActions = ($t: MessageFormatter, asset: AssetResponseDto) = const StopMotionPhoto: ActionItem = { title: $t('stop_motion_photo'), icon: mdiMotionPauseOutline, + type: $t('assets'), $if: () => !!asset.livePhotoVideoId && assetViewerManager.isPlayingMotionPhoto, onAction: () => { assetViewerManager.isPlayingMotionPhoto = false; }, }; + const Favorite: ActionItem = { + title: $t('to_favorite'), + icon: mdiHeartOutline, + type: $t('assets'), + $if: () => isOwner && !asset.isFavorite, + onAction: () => handleFavorite(asset), + shortcuts: [{ key: 'f' }], + }; + + const Unfavorite: ActionItem = { + title: $t('unfavorite'), + icon: mdiHeart, + type: $t('assets'), + $if: () => isOwner && asset.isFavorite, + onAction: () => handleUnfavorite(asset), + shortcuts: [{ key: 'f' }], + }; + const Offline: ActionItem = { title: $t('asset_offline'), icon: mdiAlertOutline, + type: $t('assets'), color: 'danger', $if: () => !!asset.isOffline, onAction: () => assetViewerManager.toggleDetailPanel(), @@ -52,12 +81,37 @@ export const getAssetActions = ($t: MessageFormatter, asset: AssetResponseDto) = const Info: ActionItem = { title: $t('info'), icon: mdiInformationOutline, + type: $t('assets'), $if: () => asset.hasMetadata, onAction: () => assetViewerManager.toggleDetailPanel(), shortcuts: [{ key: 'i' }], }; - return { Share, PlayMotionPhoto, StopMotionPhoto, Offline, Info }; + return { Share, Offline, Info, Favorite, Unfavorite, PlayMotionPhoto, StopMotionPhoto }; +}; + +const handleFavorite = async (asset: AssetResponseDto) => { + const $t = await getFormatter(); + + try { + const response = await updateAsset({ id: asset.id, updateAssetDto: { isFavorite: true } }); + toastManager.success($t('added_to_favorites')); + eventManager.emit('AssetUpdate', response); + } catch (error) { + handleError(error, $t('errors.unable_to_add_remove_favorites', { values: { favorite: asset.isFavorite } })); + } +}; + +const handleUnfavorite = async (asset: AssetResponseDto) => { + const $t = await getFormatter(); + + try { + const response = await updateAsset({ id: asset.id, updateAssetDto: { isFavorite: false } }); + toastManager.success($t('removed_from_favorites')); + eventManager.emit('AssetUpdate', response); + } catch (error) { + handleError(error, $t('errors.unable_to_add_remove_favorites', { values: { favorite: asset.isFavorite } })); + } }; export const handleReplaceAsset = async (oldAssetId: string) => { diff --git a/web/src/lib/utils.ts b/web/src/lib/utils.ts index b89e1a68bb..0437b05700 100644 --- a/web/src/lib/utils.ts +++ b/web/src/lib/utils.ts @@ -26,7 +26,7 @@ import { type SharedLinkResponseDto, type UserResponseDto, } from '@immich/sdk'; -import { toastManager } from '@immich/ui'; +import { toastManager, type ActionItem } from '@immich/ui'; import { mdiCogRefreshOutline, mdiDatabaseRefreshOutline, mdiHeadSyncOutline, mdiImageRefreshOutline } from '@mdi/js'; import { init, register, t } from 'svelte-i18n'; import { derived, get } from 'svelte/store'; @@ -440,3 +440,6 @@ export const getReleaseType = ( }; export const semverToName = ({ major, minor, patch }: ServerVersionResponseDto) => `v${major}.${minor}.${patch}`; + +export const withoutIcons = (actions: ActionItem[]): ActionItem[] => + actions.map((action) => ({ ...action, icon: undefined })); diff --git a/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte index 781dc80ec8..d33c5e7474 100644 --- a/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -16,7 +16,6 @@ import TagAction from '$lib/components/timeline/actions/TagAction.svelte'; import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte'; import Timeline from '$lib/components/timeline/Timeline.svelte'; - import { AssetAction } from '$lib/constants'; import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { preferences } from '$lib/stores/user.store'; @@ -55,7 +54,6 @@ bind:timelineManager {options} {assetInteraction} - removeAction={AssetAction.UNFAVORITE} onEscape={handleEscape} > {#snippet empty()} diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte index d921392512..1c7a190b08 100644 --- a/web/src/routes/+layout.svelte +++ b/web/src/routes/+layout.svelte @@ -145,7 +145,6 @@ icon: mdiThemeLightDark, onAction: () => themeManager.toggleTheme(), shortcuts: { shift: true, key: 't' }, - isGlobal: true, }, ]; @@ -181,7 +180,7 @@ icon: mdiServer, onAction: () => goto(AppRoute.ADMIN_STATS), }, - ].map((route) => ({ ...route, type: $t('page'), isGlobal: true, $if: () => $user?.isAdmin })); + ].map((route) => ({ ...route, type: $t('page'), $if: () => $user?.isAdmin })); const commands = $derived([...userCommands, ...adminCommands]);