feat(web): bounding box for faces when hovering over the face in photo view (#26667)

* feat(web): when hovering over a face already deteced, display the bounding box also shown when hovering over the person in the details-pane.

* prevent lint error

* fix unused var
This commit is contained in:
Andreas Heinz 2026-03-04 16:27:26 +01:00 committed by GitHub
parent 8279e1078a
commit 5caa7e1902
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -11,7 +11,7 @@
import { imageManager } from '$lib/managers/ImageManager.svelte';
import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
import { ocrManager } from '$lib/stores/ocr.svelte';
import { boundingBoxesArray } from '$lib/stores/people.store';
import { boundingBoxesArray, type Faces } from '$lib/stores/people.store';
import { SlideshowLook, SlideshowState, slideshowLookCssMapping, slideshowStore } from '$lib/stores/slideshow.store';
import { getAssetUrl, targetImageSize as getTargetImageSize, handlePromiseError } from '$lib/utils';
import { canCopyImageToClipboard, copyImageToClipboard } from '$lib/utils/asset-utils';
@ -195,6 +195,42 @@
}
lastUrl = imageLoaderUrl;
});
const faceToNameMap = $derived.by(() => {
// eslint-disable-next-line svelte/prefer-svelte-reactivity
const map = new Map<Faces, string>();
for (const person of asset.people ?? []) {
for (const face of person.faces ?? []) {
map.set(face, person.name);
}
}
return map;
});
const faces = $derived(Array.from(faceToNameMap.keys()));
const handleImageMouseMove = (event: MouseEvent) => {
$boundingBoxesArray = [];
if (!assetViewerManager.imgRef || !element || isFaceEditMode.value || ocrManager.showOverlay) {
return;
}
const containerRect = element.getBoundingClientRect();
const mouseX = event.clientX - containerRect.left;
const mouseY = event.clientY - containerRect.top;
const faceBoxes = getBoundingBox(faces, overlayMetrics);
for (const [index, box] of faceBoxes.entries()) {
if (mouseX >= box.left && mouseX <= box.left + box.width && mouseY >= box.top && mouseY <= box.top + box.height) {
$boundingBoxesArray.push(faces[index]);
}
}
};
const handleImageMouseLeave = () => {
$boundingBoxesArray = [];
};
</script>
<AssetViewerEvents {onCopy} {onZoom} />
@ -218,6 +254,9 @@
class="relative h-full w-full select-none"
bind:clientWidth={containerWidth}
bind:clientHeight={containerHeight}
role="presentation"
onmousemove={handleImageMouseMove}
onmouseleave={handleImageMouseLeave}
>
{#if !imageLoaded}
<div id="spinner" class="flex h-full items-center justify-center">
@ -248,11 +287,20 @@
: slideshowLookCssMapping[$slideshowLook]}"
draggable="false"
/>
{#each getBoundingBox($boundingBoxesArray, overlayMetrics) as boundingbox (boundingbox.id)}
{#each getBoundingBox($boundingBoxesArray, overlayMetrics) as boundingbox, index (boundingbox.id)}
<div
class="absolute border-solid border-white border-3 rounded-lg"
style="top: {boundingbox.top}px; left: {boundingbox.left}px; height: {boundingbox.height}px; width: {boundingbox.width}px;"
></div>
{#if faceToNameMap.get($boundingBoxesArray[index])}
<div
class="absolute bg-white/90 text-black px-2 py-1 rounded text-sm font-medium whitespace-nowrap pointer-events-none shadow-lg"
style="top: {boundingbox.top + boundingbox.height + 4}px; left: {boundingbox.left +
boundingbox.width}px; transform: translateX(-100%);"
>
{faceToNameMap.get($boundingBoxesArray[index])}
</div>
{/if}
{/each}
{#each ocrBoxes as ocrBox (ocrBox.id)}