diff --git a/web/src/components/overlay/detail/FaceCreateWizardDialog.tsx b/web/src/components/overlay/detail/FaceCreateWizardDialog.tsx index 5729fc760..8302c112a 100644 --- a/web/src/components/overlay/detail/FaceCreateWizardDialog.tsx +++ b/web/src/components/overlay/detail/FaceCreateWizardDialog.tsx @@ -34,6 +34,7 @@ type CreateFaceWizardDialogProps = { export default function CreateFaceWizardDialog({ open, setOpen, + onFinish, }: CreateFaceWizardDialogProps) { const { t } = useTranslation("views/faceLibrary"); @@ -142,7 +143,13 @@ export default function CreateFaceWizardDialog({
-
diff --git a/web/src/pages/FaceLibrary.tsx b/web/src/pages/FaceLibrary.tsx index f1309ea5d..1400dee5d 100644 --- a/web/src/pages/FaceLibrary.tsx +++ b/web/src/pages/FaceLibrary.tsx @@ -121,7 +121,11 @@ export default function FaceLibrary() { const [selectedFaces, setSelectedFaces] = useState([]); const onClickFace = useCallback( - (imageId: string) => { + (imageId: string, ctrl: boolean) => { + if (selectedFaces.length == 0 && !ctrl) { + return; + } + const index = selectedFaces.indexOf(imageId); if (index != -1) { @@ -143,39 +147,42 @@ export default function FaceLibrary() { [selectedFaces, setSelectedFaces], ); - const onDelete = useCallback(() => { - axios - .post(`/faces/train/delete`, { ids: selectedFaces }) - .then((resp) => { - setSelectedFaces([]); + const onDelete = useCallback( + (name: string, ids: string[]) => { + axios + .post(`/faces/${name}/delete`, { ids }) + .then((resp) => { + setSelectedFaces([]); - if (resp.status == 200) { - toast.success(t("toast.success.deletedFace"), { + if (resp.status == 200) { + toast.success(t("toast.success.deletedFace"), { + position: "top-center", + }); + + if (faceImages.length == 1) { + // face has been deleted + setPageToggle(""); + } + + refreshFaces(); + } + }) + .catch((error) => { + const errorMessage = + error.response?.data?.message || + error.response?.data?.detail || + "Unknown error"; + toast.error(t("toast.error.deleteFaceFailed", { errorMessage }), { position: "top-center", }); - - if (faceImages.length == 1) { - // face has been deleted - setPageToggle(""); - } - - refreshFaces(); - } - }) - .catch((error) => { - const errorMessage = - error.response?.data?.message || - error.response?.data?.detail || - "Unknown error"; - toast.error(t("toast.error.deleteFaceFailed", { errorMessage }), { - position: "top-center", }); - }); - }, [faceImages, selectedFaces, refreshFaces, setPageToggle, t]); + }, + [faceImages, refreshFaces, setPageToggle, t], + ); // keyboard - useKeyboardListener(["a"], (key, modifiers) => { + useKeyboardListener(["a", "Escape"], (key, modifiers) => { if (modifiers.repeat || !modifiers.down) { return; } @@ -186,6 +193,9 @@ export default function FaceLibrary() { setSelectedFaces([...trainImages]); } break; + case "Escape": + setSelectedFaces([]); + break; } }); @@ -258,7 +268,10 @@ export default function FaceLibrary() { {selectedFaces?.length > 0 ? (
- @@ -292,7 +305,7 @@ export default function FaceLibrary() { ))}
@@ -304,7 +317,7 @@ type TrainingGridProps = { attemptImages: string[]; faceNames: string[]; selectedFaces: string[]; - onClickFace: (image: string) => void; + onClickFace: (image: string, ctrl: boolean) => void; onRefresh: () => void; }; function TrainingGrid({ @@ -324,7 +337,7 @@ function TrainingGrid({ faceNames={faceNames} threshold={config.face_recognition.recognition_threshold} selected={selectedFaces.includes(image)} - onClick={() => onClickFace(image)} + onClick={(meta) => onClickFace(image, meta)} onRefresh={onRefresh} /> ))} @@ -337,7 +350,7 @@ type FaceAttemptProps = { faceNames: string[]; threshold: number; selected: boolean; - onClick: () => void; + onClick: (meta: boolean) => void; onRefresh: () => void; }; function FaceAttempt({ @@ -415,7 +428,7 @@ function FaceAttempt({ ? "shadow-selected outline-selected" : "outline-transparent duration-500", )} - onClick={onClick} + onClick={(e) => onClick(e.metaKey || e.ctrlKey)} >
@@ -479,9 +492,9 @@ function FaceAttempt({ type FaceGridProps = { faceImages: string[]; pageToggle: string; - onRefresh: () => void; + onDelete: (name: string, ids: string[]) => void; }; -function FaceGrid({ faceImages, pageToggle, onRefresh }: FaceGridProps) { +function FaceGrid({ faceImages, pageToggle, onDelete }: FaceGridProps) { return (
{faceImages.map((image: string) => ( @@ -489,7 +502,7 @@ function FaceGrid({ faceImages, pageToggle, onRefresh }: FaceGridProps) { key={image} name={pageToggle} image={image} - onRefresh={onRefresh} + onDelete={onDelete} /> ))}
@@ -499,31 +512,10 @@ function FaceGrid({ faceImages, pageToggle, onRefresh }: FaceGridProps) { type FaceImageProps = { name: string; image: string; - onRefresh: () => void; + onDelete: (name: string, ids: string[]) => void; }; -function FaceImage({ name, image, onRefresh }: FaceImageProps) { +function FaceImage({ name, image, onDelete }: FaceImageProps) { const { t } = useTranslation(["views/faceLibrary"]); - const onDelete = useCallback(() => { - axios - .post(`/faces/${name}/delete`, { ids: [image] }) - .then((resp) => { - if (resp.status == 200) { - toast.success(t("toast.success.deletedFace"), { - position: "top-center", - }); - onRefresh(); - } - }) - .catch((error) => { - const errorMessage = - error.response?.data?.message || - error.response?.data?.detail || - "Unknown error"; - toast.error(t("toast.error.deleteFaceFailed", { errorMessage }), { - position: "top-center", - }); - }); - }, [name, image, onRefresh, t]); return (
@@ -540,7 +532,7 @@ function FaceImage({ name, image, onRefresh }: FaceImageProps) { onDelete(name, [image])} /> {t("button.deleteFaceAttempts")}