feat(web): manual face tagging and deletion (#16062)

This commit is contained in:
Alex 2025-02-21 09:58:25 -06:00 committed by GitHub
parent 94c0e8253a
commit 007eaaceb9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 2054 additions and 106 deletions

View file

@ -13,9 +13,10 @@
AssetTypeEnum,
type AssetFaceResponseDto,
type PersonResponseDto,
deleteFace,
} from '@immich/sdk';
import Icon from '$lib/components/elements/icon.svelte';
import { mdiAccountOff, mdiArrowLeftThin, mdiPencil, mdiRestart } from '@mdi/js';
import { mdiAccountOff, mdiArrowLeftThin, mdiPencil, mdiRestart, mdiTrashCan } from '@mdi/js';
import { onMount } from 'svelte';
import { linear } from 'svelte/easing';
import { fly } from 'svelte/transition';
@ -24,8 +25,10 @@
import AssignFaceSidePanel from './assign-face-side-panel.svelte';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { zoomImageToBase64 } from '$lib/utils/people-utils';
import { photoViewer } from '$lib/stores/assets.store';
import { photoViewerImgElement } from '$lib/stores/assets.store';
import { t } from 'svelte-i18n';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
interface Props {
assetId: string;
@ -163,6 +166,30 @@
editedFace = face;
showSelectedFaces = true;
};
const deleteAssetFace = async (face: AssetFaceResponseDto) => {
try {
if (!face.person) {
return;
}
const isConfirmed = await dialogController.show({
prompt: $t('confirm_delete_face', { values: { name: face.person.name } }),
});
if (!isConfirmed) {
return;
}
await deleteFace({ id: face.id, assetFaceDeleteDto: { force: false } });
peopleWithFaces = peopleWithFaces.filter((f) => f.id !== face.id);
await assetViewingStore.setAssetId(assetId);
} catch (error) {
handleError(error, $t('error_delete_face'));
}
};
</script>
<section
@ -242,7 +269,7 @@
hidden={face.person.isHidden}
/>
{:else}
{#await zoomImageToBase64(face, assetId, assetType, $photoViewer)}
{#await zoomImageToBase64(face, assetId, assetType, $photoViewerImgElement)}
<ImageThumbnail
curve
shadow
@ -308,6 +335,19 @@
</div>
{/if}
</div>
{#if face.person != null}
<div class="absolute -right-[5px] top-[25px] h-[20px] w-[20px] rounded-full">
<CircleIconButton
color="red"
icon={mdiTrashCan}
title={$t('delete_face')}
size="18"
padding="1"
class="absolute left-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform"
onclick={() => deleteAssetFace(face)}
/>
</div>
{/if}
</div>
</div>
{/each}