mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
feat: create new person in face editor (#27364)
* feat: create new person in face editor * add delay * fix: test * i18n * fix: unit test * pr feedback
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import { shortcut } from '$lib/actions/shortcut';
|
||||
import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte';
|
||||
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
|
||||
import FaceCreateTagModal from '$lib/modals/CreateFaceModal.svelte';
|
||||
import { getPeopleThumbnailUrl } from '$lib/utils';
|
||||
import { getNaturalSize, scaleToFit } from '$lib/utils/container-utils';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
@@ -260,6 +261,44 @@
|
||||
};
|
||||
};
|
||||
|
||||
type FaceCoordinates = NonNullable<ReturnType<typeof getFaceCroppedCoordinates>>;
|
||||
|
||||
const getFacePreviewUrl = (data: FaceCoordinates) => {
|
||||
if (!htmlElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const natural = getNaturalSize(htmlElement);
|
||||
if (natural.width <= 0 || natural.height <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const x = clamp(data.x, 0, natural.width - 1);
|
||||
const y = clamp(data.y, 0, natural.height - 1);
|
||||
const width = clamp(data.width, 1, natural.width - x);
|
||||
const height = clamp(data.height, 1, natural.height - y);
|
||||
|
||||
if (width <= 0 || height <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
const context = canvas.getContext('2d');
|
||||
if (!context) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
context.drawImage(htmlElement, x, y, width, height, 0, 0, width, height);
|
||||
return canvas.toDataURL('image/png');
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const tagFace = async (person: PersonResponseDto) => {
|
||||
try {
|
||||
const data = getFaceCroppedCoordinates();
|
||||
@@ -294,6 +333,28 @@
|
||||
}
|
||||
};
|
||||
|
||||
const showCreateFaceModal = async () => {
|
||||
try {
|
||||
const data = getFaceCroppedCoordinates();
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
const created = await modalManager.show(FaceCreateTagModal, {
|
||||
assetId,
|
||||
...data,
|
||||
previewUrl: getFacePreviewUrl(data),
|
||||
});
|
||||
if (!created) {
|
||||
return;
|
||||
}
|
||||
|
||||
onClose();
|
||||
} catch (error) {
|
||||
handleError(error, 'Error creating and tagging face');
|
||||
}
|
||||
};
|
||||
|
||||
onDestroy(() => {
|
||||
onClose();
|
||||
});
|
||||
@@ -303,19 +364,19 @@
|
||||
|
||||
<div
|
||||
id="face-editor-data"
|
||||
class="absolute start-0 top-0 z-5 h-full w-full overflow-hidden"
|
||||
class="absolute inset-s-0 top-0 z-5 h-full w-full overflow-hidden"
|
||||
data-overlay-interactive
|
||||
data-face-left={faceBoxPosition.left}
|
||||
data-face-top={faceBoxPosition.top}
|
||||
data-face-width={faceBoxPosition.width}
|
||||
data-face-height={faceBoxPosition.height}
|
||||
>
|
||||
<canvas bind:this={canvasEl} id="face-editor" class="absolute top-0 start-0"></canvas>
|
||||
<canvas bind:this={canvasEl} id="face-editor" class="absolute top-0 inset-s-0"></canvas>
|
||||
|
||||
<div
|
||||
id="face-selector"
|
||||
bind:this={faceSelectorEl}
|
||||
class="absolute top-[calc(50%-250px)] start-[calc(50%-125px)] max-w-[250px] w-[250px] bg-white dark:bg-immich-dark-gray dark:text-immich-dark-fg backdrop-blur-sm px-2 py-4 rounded-xl border border-gray-200 dark:border-gray-800 transition-[top,left] duration-200 ease-out"
|
||||
class="absolute top-[calc(50%-250px)] inset-s-[calc(50%-125px)] max-w-62.5 w-62.5 bg-white dark:bg-immich-dark-gray dark:text-immich-dark-fg backdrop-blur-sm px-2 py-4 rounded-xl border border-gray-200 dark:border-gray-800 transition-[top,left] duration-200 ease-out"
|
||||
>
|
||||
<p class="text-center text-sm">{$t('select_person_to_tag')}</p>
|
||||
|
||||
@@ -354,6 +415,12 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<Button size="small" fullWidth onclick={onClose} color="danger" class="mt-2">{$t('cancel')}</Button>
|
||||
<Button size="small" fullWidth onclick={showCreateFaceModal} variant="outline" class="mt-2">
|
||||
{$t('create_person')}
|
||||
</Button>
|
||||
|
||||
<Button size="small" fullWidth onclick={onClose} color="danger" class="mt-2">
|
||||
{$t('cancel')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user