feat(faces): show a detected face in its source photo, outlined (#1120)
Phase 2 of #1096. Stacked on the phase-1 branch — it needs the Postgres fix there, or the face list this reads comes back empty. A 64px avatar answers "is this a person", not "is this the same person as that other cluster". The reporter's revised use for this is the pre-merge decision: who they were standing next to, what the occasion was. So the face opens in its own photo with the detected box drawn, and prev/next walks that person's other appearances without leaving the modal. The box is positioned in PERCENTAGES of the original frame, not measured pixels: the container carries the photo's aspect ratio, so the same four numbers land correctly at any rendered size, with no resize listener. Verified against real data before writing the component — bbox [221.9, 174.9, 294.5, 405.8] on a 750x750 frame resolves to left 29.6% / top 23.3% / width 39.3% / height 54.1% and lands squarely on the face. Preview rendition, never thumbnail, and that is load-bearing rather than a quality preference: thumbnail_fit is seeded to 'cover' on every install, so a thumbnail has had its edges cut off and ratios taken against the ORIGINAL land nowhere on it. That was #1100, and it presented as a broken detector. Not built on AdminPhotoViewer, deliberately. It wants full AdminPhoto objects (this endpoint returns photo_id + bbox + dimensions), it carries delete and category actions that are wrong for "who is this?", and there is no seam to draw the box. Three things review caught, all real: - The container had a height cap but no width cap, so a panorama derived its width from the aspect ratio and overflowed the modal sideways, taking part of the outlined face off-screen. - The per-tile affordance was hover-only, so on a tablet it was permanently invisible and there was no way to inspect a specific tile. - The row action opened index 0, which is the TOP-SCORING face — the same thing as the cover only until someone uses phase 1 to pick a different one, at which point the row showed one face and opened another. It now resolves to the cover's own index. Round 2 found three more, all real: - The counter called a list truncated whenever it hit 500, so a person with exactly 500 faces was told their complete list was capped. It now compares against total_face_count. - facesLoading goes false with an empty array on a zero-face person or a failed request, so the panel sat on a spinner that would never resolve. - Five 32px actions plus a 64px avatar exceed a 320px row, and the name is what got pushed out. flex-wrap alone did not fix it — the toolbar still claimed its max-content width first — so its basis is capped at small sizes and the buttons wrap to a second line instead. A cover that falls outside the capped list opens the first face instead. That case implies the list IS capped, so the truncation note already explains it — real pagination is a bigger change and is not in this. Verified end to end: picked the 5th of 13 faces as cover, and the row action opened at 5 / 13 rather than 1 / 13. Frontend suite 178 passing, build clean, no new type errors.
This commit is contained in:
@@ -3133,6 +3133,13 @@
|
||||
"mergeHint": "Tippen Sie zwei oder mehr Gesichter an, um sie zu einer Person zusammenzuführen.",
|
||||
"selectedCount": "{{count}} ausgewählt",
|
||||
"coverTruncated": "Es werden die {{limit}} Gesichter mit der höchsten Konfidenz gezeigt.",
|
||||
"contextTruncated": "Es werden die ersten {{limit}} Aufnahmen dieser Person gezeigt.",
|
||||
"contextUnavailable": "Für diese Person konnte kein Foto geladen werden.",
|
||||
"contextAction": "Diese Person in ihrem Foto ansehen",
|
||||
"contextHelp": "Das erkannte Gesicht, im Originalfoto markiert — wer daneben stand, klärt meist, ob zwei ähnliche Personen dieselbe sind.",
|
||||
"contextNoBox": "Für dieses Foto sind keine Abmessungen gespeichert, daher kann das erkannte Gesicht nicht markiert werden.",
|
||||
"contextPrev": "Vorheriges Foto dieser Person",
|
||||
"contextNext": "Nächstes Foto dieser Person",
|
||||
"coverAction": "Wählen, welches Foto diese Person darstellt",
|
||||
"coverHelp": "Wählen Sie das Foto, das diese Person am besten zeigt. Es wird zum Avatar hier und in der Personenleiste für Gäste.",
|
||||
"coverPick": "Als Titelbild verwenden",
|
||||
|
||||
@@ -2705,6 +2705,13 @@
|
||||
"mergeHint": "Tap two or more faces to merge them into one person.",
|
||||
"selectedCount": "{{count}} selected",
|
||||
"coverTruncated": "Showing the {{limit}} highest-confidence faces of this person.",
|
||||
"contextTruncated": "Showing the first {{limit}} appearances of this person.",
|
||||
"contextUnavailable": "No photo could be loaded for this person.",
|
||||
"contextAction": "See this person in their photo",
|
||||
"contextHelp": "The detected face, outlined in its original photo — who they were standing next to is usually what settles whether two similar people are the same one.",
|
||||
"contextNoBox": "This photo has no stored dimensions, so the detected face cannot be outlined.",
|
||||
"contextPrev": "Previous photo of this person",
|
||||
"contextNext": "Next photo of this person",
|
||||
"coverAction": "Choose which photo represents this person",
|
||||
"coverHelp": "Pick the photo that best shows this person. It becomes their avatar here and in the guest-facing people strip.",
|
||||
"coverPick": "Use as cover",
|
||||
|
||||
Reference in New Issue
Block a user