From 20862f1d4a10f1de85a83008d1da1c0f37db8a2d Mon Sep 17 00:00:00 2001
From: Mees Frensel <33722705+meesfrensel@users.noreply.github.com>
Date: Wed, 23 Sep 2026 15:44:03 +0200
Subject: [PATCH] fix(web): refresh people after setting person thumbnail
(#31744)
---
.../asset-viewer/AssetViewer.svelte | 25 +++++++----
.../asset-viewer/AssetViewerNavBar.svelte | 5 ++-
.../actions/SetPersonFeaturedAction.svelte | 41 -------------------
.../components/asset-viewer/actions/action.ts | 2 -
web/src/lib/constants.ts | 1 -
web/src/lib/services/person.service.ts | 25 ++++++++++-
6 files changed, 44 insertions(+), 55 deletions(-)
delete mode 100644 web/src/lib/components/asset-viewer/actions/SetPersonFeaturedAction.svelte
diff --git a/web/src/lib/components/asset-viewer/AssetViewer.svelte b/web/src/lib/components/asset-viewer/AssetViewer.svelte
index 3b69f8b2f..4873b0b41 100644
--- a/web/src/lib/components/asset-viewer/AssetViewer.svelte
+++ b/web/src/lib/components/asset-viewer/AssetViewer.svelte
@@ -177,6 +177,14 @@
}
};
+ const onPersonThumbnailReady = async ({ id: personId }: { id: string }) => {
+ if (person && person.id !== personId) {
+ return;
+ }
+ faceManager.clear();
+ await faceManager.getAssetFaces(asset.id);
+ };
+
onMount(() => {
syncAssetViewerOpenClass(true);
const slideshowStateUnsubscribe = slideshowState.subscribe((value) => {
@@ -345,19 +353,13 @@
preAction?.(action);
};
- const handleAction = async (action: Action) => {
+ const handleAction = (action: Action) => {
switch (action.type) {
case AssetAction.DELETE:
case AssetAction.TRASH: {
eventManager.emit('AssetsDelete', [asset.id]);
break;
}
- case AssetAction.SET_PERSON_FEATURED_PHOTO: {
- const assetInfo = await getAssetInfo({ id: asset.id });
- cursor.current = { ...asset, people: assetInfo.people };
- eventManager.emit('AssetUpdate', cursor.current);
- break;
- }
case AssetAction.RATING: {
cursor.current = {
...asset,
@@ -490,7 +492,14 @@
- closeViewer()} {onStackUpdate} />
+ closeViewer()}
+ {onStackUpdate}
+ {onPersonThumbnailReady}
+/>
{/if}
{#if person}
-
+ {@const { SetFeaturedPhoto } = getPersonAssetActions($t, person, asset)}
+
{/if}
diff --git a/web/src/lib/components/asset-viewer/actions/SetPersonFeaturedAction.svelte b/web/src/lib/components/asset-viewer/actions/SetPersonFeaturedAction.svelte
deleted file mode 100644
index 86e201c61..000000000
--- a/web/src/lib/components/asset-viewer/actions/SetPersonFeaturedAction.svelte
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-
diff --git a/web/src/lib/components/asset-viewer/actions/action.ts b/web/src/lib/components/asset-viewer/actions/action.ts
index 6cfd68dc3..c02fadf81 100644
--- a/web/src/lib/components/asset-viewer/actions/action.ts
+++ b/web/src/lib/components/asset-viewer/actions/action.ts
@@ -1,4 +1,3 @@
-import type { AssetResponseDto, PersonResponseDto } from '@immich/sdk';
import type { AssetAction } from '$lib/constants';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
@@ -10,7 +9,6 @@ type ActionMap = {
[AssetAction.RESTORE]: { asset: TimelineAsset };
[AssetAction.SET_VISIBILITY_LOCKED]: { asset: TimelineAsset };
[AssetAction.SET_VISIBILITY_TIMELINE]: { asset: TimelineAsset };
- [AssetAction.SET_PERSON_FEATURED_PHOTO]: { asset: AssetResponseDto; person: PersonResponseDto };
[AssetAction.RATING]: { asset: TimelineAsset; rating: number | null };
};
diff --git a/web/src/lib/constants.ts b/web/src/lib/constants.ts
index 2c3e7b889..5079e44c0 100644
--- a/web/src/lib/constants.ts
+++ b/web/src/lib/constants.ts
@@ -8,7 +8,6 @@ export enum AssetAction {
RESTORE = 'restore',
SET_VISIBILITY_LOCKED = 'set-visibility-locked',
SET_VISIBILITY_TIMELINE = 'set-visibility-timeline',
- SET_PERSON_FEATURED_PHOTO = 'set-person-featured-photo',
RATING = 'rating',
}
diff --git a/web/src/lib/services/person.service.ts b/web/src/lib/services/person.service.ts
index c51a7d42b..9835c5e82 100644
--- a/web/src/lib/services/person.service.ts
+++ b/web/src/lib/services/person.service.ts
@@ -1,9 +1,10 @@
-import { updatePerson, type PersonResponseDto } from '@immich/sdk';
+import { updatePerson, type AssetResponseDto, type PersonResponseDto } from '@immich/sdk';
import { modalManager, toastManager, type ActionItem } from '@immich/ui';
import {
mdiCalendarEditOutline,
mdiEyeOffOutline,
mdiEyeOutline,
+ mdiFaceManProfile,
mdiHeartMinusOutline,
mdiHeartOutline,
} from '@mdi/js';
@@ -51,6 +52,16 @@ export const getPersonActions = ($t: MessageFormatter, person: PersonResponseDto
return { SetDateOfBirth, Favorite, Unfavorite, HidePerson, ShowPerson };
};
+export const getPersonAssetActions = ($t: MessageFormatter, person: PersonResponseDto, asset: AssetResponseDto) => {
+ const SetFeaturedPhoto: ActionItem = {
+ title: $t('set_as_featured_photo'),
+ icon: mdiFaceManProfile,
+ onAction: () => handleSetFeaturedPhoto(person, asset.id),
+ };
+
+ return { SetFeaturedPhoto };
+};
+
const handleFavoritePerson = async (person: { id: string }) => {
const $t = await getFormatter();
@@ -111,3 +122,15 @@ export const handleUpdatePersonBirthDate = async (person: PersonResponseDto, bir
handleError(error, $t('errors.unable_to_save_date_of_birth'));
}
};
+
+const handleSetFeaturedPhoto = async (person: PersonResponseDto, featureFaceAssetId: string) => {
+ const $t = await getFormatter();
+
+ try {
+ const response = await updatePerson({ id: person.id, personUpdateDto: { featureFaceAssetId } });
+ toastManager.primary($t('feature_photo_updated'));
+ eventManager.emit('PersonUpdate', response);
+ } catch (error) {
+ handleError(error, $t('errors.unable_to_set_feature_photo'));
+ }
+};