refactor: favorite action (#25121)

This commit is contained in:
Jason Rasmussen
2026-01-07 16:21:19 -05:00
committed by GitHub
parent 78229baeab
commit 5bb3492616
17 changed files with 111 additions and 102 deletions
+1 -1
View File
@@ -9,6 +9,6 @@
const { title, icon, color = 'secondary', onAction } = $derived(action);
</script>
{#if action.$if?.() ?? true}
{#if icon && (action.$if?.() ?? true)}
<IconButton variant="ghost" shape="round" {color} {icon} aria-label={title} onclick={() => onAction(action)} />
{/if}
+1 -1
View File
@@ -10,6 +10,6 @@
const { title, icon, onAction } = $derived(action);
</script>
{#if action.$if?.() ?? true}
{#if icon && (action.$if?.() ?? true)}
<IconButton {size} shape="round" color="primary" {icon} aria-label={title} onclick={() => onAction(action)} />
{/if}
@@ -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 };
@@ -1,51 +0,0 @@
<script lang="ts">
import { shortcut } from '$lib/actions/shortcut';
import { AssetAction } from '$lib/constants';
import { handleError } from '$lib/utils/handle-error';
import { toTimelineAsset } from '$lib/utils/timeline-util';
import { updateAsset, type AssetResponseDto } from '@immich/sdk';
import { IconButton, toastManager } from '@immich/ui';
import { mdiHeart, mdiHeartOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
import type { OnAction } from './action';
interface Props {
asset: AssetResponseDto;
onAction: OnAction;
}
let { asset, onAction }: Props = $props();
const toggleFavorite = async () => {
try {
const data = await updateAsset({
id: asset.id,
updateAssetDto: {
isFavorite: !asset.isFavorite,
},
});
asset = { ...asset, isFavorite: data.isFavorite };
onAction({
type: asset.isFavorite ? AssetAction.FAVORITE : AssetAction.UNFAVORITE,
asset: toTimelineAsset(asset),
});
toastManager.success(asset.isFavorite ? $t('added_to_favorites') : $t('removed_from_favorites'));
} catch (error) {
handleError(error, $t('errors.unable_to_add_remove_favorites', { values: { favorite: asset.isFavorite } }));
}
};
</script>
<svelte:document use:shortcut={{ shortcut: { key: 'f' }, onShortcut: toggleFavorite }} />
<IconButton
color="secondary"
shape="round"
variant="ghost"
icon={asset.isFavorite ? mdiHeart : mdiHeartOutline}
aria-label={asset.isFavorite ? $t('unfavorite') : $t('to_favorite')}
onclick={toggleFavorite}
/>
@@ -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 @@
<CommandPaletteDefaultProvider
name={$t('assets')}
actions={[Close, Share, Offline, PlayMotionPhoto, StopMotionPhoto, Info]}
actions={withoutIcons([Close, Share, Offline, Favorite, Unfavorite, PlayMotionPhoto, StopMotionPhoto, Info])}
/>
<div
@@ -172,9 +174,10 @@
{/if}
<ActionButton action={Info} />
<ActionButton action={Favorite} />
<ActionButton action={Unfavorite} />
{#if isOwner}
<FavoriteAction {asset} {onAction} />
<RatingAction {asset} {onAction} />
{/if}
@@ -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;
}
};
</script>
<OnEvents onAssetReplace={handleAssetReplace} />
<OnEvents {onAssetReplace} {onAssetUpdate} />
<svelte:document bind:fullscreenElement />
@@ -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;
@@ -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;