frontend and i18n keys

This commit is contained in:
Josh Hawkins 2025-04-23 17:38:40 -05:00
parent f85223dde1
commit a07e7a0923
2 changed files with 97 additions and 11 deletions

View File

@ -36,9 +36,15 @@
"title": "Delete Name", "title": "Delete Name",
"desc": "Are you sure you want to delete the collection {{name}}? This will permanently delete all associated faces." "desc": "Are you sure you want to delete the collection {{name}}? This will permanently delete all associated faces."
}, },
"renameFace": {
"title": "Rename Face",
"desc": "Enter a new name for {{name}}"
},
"button": { "button": {
"deleteFaceAttempts": "Delete Face Attempts", "deleteFaceAttempts": "Delete Face Attempts",
"addFace": "Add Face", "addFace": "Add Face",
"renameFace": "Rename Face",
"deleteFace": "Delete Face",
"uploadImage": "Upload Image", "uploadImage": "Upload Image",
"reprocessFace": "Reprocess Face" "reprocessFace": "Reprocess Face"
}, },
@ -62,6 +68,7 @@
"deletedName_zero": "Empty collection deleted successfully.", "deletedName_zero": "Empty collection deleted successfully.",
"deletedName_one": "{{count}} face has been successfully deleted.", "deletedName_one": "{{count}} face has been successfully deleted.",
"deletedName_other": "{{count}} faces have been successfully deleted.", "deletedName_other": "{{count}} faces have been successfully deleted.",
"renamedFace": "Successfully renamed face to {{name}}",
"trainedFace": "Successfully trained face.", "trainedFace": "Successfully trained face.",
"updatedFaceScore": "Successfully updated face score." "updatedFaceScore": "Successfully updated face score."
}, },
@ -70,6 +77,7 @@
"addFaceLibraryFailed": "Failed to set face name: {{errorMessage}}", "addFaceLibraryFailed": "Failed to set face name: {{errorMessage}}",
"deleteFaceFailed": "Failed to delete: {{errorMessage}}", "deleteFaceFailed": "Failed to delete: {{errorMessage}}",
"deleteNameFailed": "Failed to delete name: {{errorMessage}}", "deleteNameFailed": "Failed to delete name: {{errorMessage}}",
"renameFaceFailed": "Failed to rename face: {{errorMessage}}",
"trainFailed": "Failed to train: {{errorMessage}}", "trainFailed": "Failed to train: {{errorMessage}}",
"updateFaceScoreFailed": "Failed to update face score: {{errorMessage}}" "updateFaceScoreFailed": "Failed to update face score: {{errorMessage}}"
} }

View File

@ -3,6 +3,7 @@ import TimeAgo from "@/components/dynamic/TimeAgo";
import AddFaceIcon from "@/components/icons/AddFaceIcon"; import AddFaceIcon from "@/components/icons/AddFaceIcon";
import ActivityIndicator from "@/components/indicators/activity-indicator"; import ActivityIndicator from "@/components/indicators/activity-indicator";
import CreateFaceWizardDialog from "@/components/overlay/detail/FaceCreateWizardDialog"; import CreateFaceWizardDialog from "@/components/overlay/detail/FaceCreateWizardDialog";
import TextEntryDialog from "@/components/overlay/dialog/TextEntryDialog";
import UploadImageDialog from "@/components/overlay/dialog/UploadImageDialog"; import UploadImageDialog from "@/components/overlay/dialog/UploadImageDialog";
import FaceSelectionDialog from "@/components/overlay/FaceSelectionDialog"; import FaceSelectionDialog from "@/components/overlay/FaceSelectionDialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@ -41,6 +42,7 @@ import { isDesktop, isMobile } from "react-device-detect";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import {
LuImagePlus, LuImagePlus,
LuPencil,
LuRefreshCw, LuRefreshCw,
LuScanFace, LuScanFace,
LuSearch, LuSearch,
@ -221,6 +223,32 @@ export default function FaceLibrary() {
[faceImages, refreshFaces, setPageToggle, t], [faceImages, refreshFaces, setPageToggle, t],
); );
const onRename = useCallback(
(oldName: string, newName: string) => {
axios
.put(`/faces/${oldName}/rename`, { new_name: newName })
.then((resp) => {
if (resp.status === 200) {
toast.success(t("toast.success.renamedFace", { name: newName }), {
position: "top-center",
});
setPageToggle("train");
refreshFaces();
}
})
.catch((error) => {
const errorMessage =
error.response?.data?.message ||
error.response?.data?.detail ||
"Unknown error";
toast.error(t("toast.error.renameFaceFailed", { errorMessage }), {
position: "top-center",
});
});
},
[setPageToggle, refreshFaces, t],
);
// keyboard // keyboard
useKeyboardListener(["a", "Escape"], (key, modifiers) => { useKeyboardListener(["a", "Escape"], (key, modifiers) => {
@ -274,6 +302,7 @@ export default function FaceLibrary() {
trainImages={trainImages} trainImages={trainImages}
setPageToggle={setPageToggle} setPageToggle={setPageToggle}
onDelete={onDelete} onDelete={onDelete}
onRename={onRename}
/> />
{selectedFaces?.length > 0 ? ( {selectedFaces?.length > 0 ? (
<div className="flex items-center justify-center gap-2"> <div className="flex items-center justify-center gap-2">
@ -338,6 +367,7 @@ type LibrarySelectorProps = {
trainImages: string[]; trainImages: string[];
setPageToggle: (toggle: string | undefined) => void; setPageToggle: (toggle: string | undefined) => void;
onDelete: (name: string, ids: string[], isName: boolean) => void; onDelete: (name: string, ids: string[], isName: boolean) => void;
onRename: (old_name: string, new_name: string) => void;
}; };
function LibrarySelector({ function LibrarySelector({
pageToggle, pageToggle,
@ -346,9 +376,11 @@ function LibrarySelector({
trainImages, trainImages,
setPageToggle, setPageToggle,
onDelete, onDelete,
onRename,
}: LibrarySelectorProps) { }: LibrarySelectorProps) {
const { t } = useTranslation(["views/faceLibrary"]); const { t } = useTranslation(["views/faceLibrary"]);
const [confirmDelete, setConfirmDelete] = useState<string | null>(null); const [confirmDelete, setConfirmDelete] = useState<string | null>(null);
const [renameFace, setRenameFace] = useState<string | null>(null);
const handleDeleteFace = useCallback( const handleDeleteFace = useCallback(
(faceName: string) => { (faceName: string) => {
@ -361,6 +393,13 @@ function LibrarySelector({
[faceData, onDelete, setPageToggle], [faceData, onDelete, setPageToggle],
); );
const handleSetOpen = useCallback(
(open: boolean) => {
setRenameFace(open ? renameFace : null);
},
[renameFace],
);
return ( return (
<> <>
<Dialog <Dialog
@ -393,6 +432,18 @@ function LibrarySelector({
</DialogContent> </DialogContent>
</Dialog> </Dialog>
<TextEntryDialog
open={!!renameFace}
setOpen={handleSetOpen}
title={t("renameFace.title")}
description={t("renameFace.desc", { name: renameFace })}
onSave={(newName) => {
onRename(renameFace!, newName);
setRenameFace(null);
}}
defaultValue={renameFace || ""}
/>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button className="flex justify-between smart-capitalize"> <Button className="flex justify-between smart-capitalize">
@ -440,17 +491,44 @@ function LibrarySelector({
({faceData?.[face].length}) ({faceData?.[face].length})
</span> </span>
</div> </div>
<Button <div className="flex gap-0.5">
variant="ghost" <Tooltip>
size="icon" <TooltipTrigger asChild>
className="size-7 opacity-0 transition-opacity group-hover:opacity-100" <Button
onClick={(e) => { variant="ghost"
e.stopPropagation(); size="icon"
setConfirmDelete(face); className="size-7 opacity-0 transition-opacity group-hover:opacity-100"
}} onClick={(e) => {
> e.stopPropagation();
<LuTrash2 className="size-4 text-destructive" /> setRenameFace(face);
</Button> }}
>
<LuPencil className="size-4 text-primary" />
</Button>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>{t("button.renameFace")}</TooltipContent>
</TooltipPortal>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="size-7 opacity-0 transition-opacity group-hover:opacity-100"
onClick={(e) => {
e.stopPropagation();
setConfirmDelete(face);
}}
>
<LuTrash2 className="size-4 text-destructive" />
</Button>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>{t("button.deleteFace")}</TooltipContent>
</TooltipPortal>
</Tooltip>
</div>
</DropdownMenuItem> </DropdownMenuItem>
))} ))}
</DropdownMenuContent> </DropdownMenuContent>