add select all link to face library, classification, and explore

This commit is contained in:
Josh Hawkins 2025-12-10 08:28:48 -06:00
parent 44c92e84cd
commit 97ba7a6b78
6 changed files with 75 additions and 6 deletions

View File

@ -52,6 +52,7 @@
}, },
"selected_one": "{{count}} selected", "selected_one": "{{count}} selected",
"selected_other": "{{count}} selected", "selected_other": "{{count}} selected",
"select_all": "All",
"camera": "Camera", "camera": "Camera",
"detected": "detected", "detected": "detected",
"normalActivity": "Normal", "normalActivity": "Normal",

View File

@ -29,6 +29,7 @@
}, },
"train": { "train": {
"title": "Recent Recognitions", "title": "Recent Recognitions",
"titleShort": "Recent",
"aria": "Select recent recognitions", "aria": "Select recent recognitions",
"empty": "There are no recent face recognition attempts" "empty": "There are no recent face recognition attempts"
}, },

View File

@ -22,11 +22,15 @@ type SearchActionGroupProps = {
selectedObjects: string[]; selectedObjects: string[];
setSelectedObjects: (ids: string[]) => void; setSelectedObjects: (ids: string[]) => void;
pullLatestData: () => void; pullLatestData: () => void;
onSelectAllObjects: () => void;
totalItems: number;
}; };
export default function SearchActionGroup({ export default function SearchActionGroup({
selectedObjects, selectedObjects,
setSelectedObjects, setSelectedObjects,
pullLatestData, pullLatestData,
onSelectAllObjects,
totalItems,
}: SearchActionGroupProps) { }: SearchActionGroupProps) {
const { t } = useTranslation(["components/filter"]); const { t } = useTranslation(["components/filter"]);
const isAdmin = useIsAdmin(); const isAdmin = useIsAdmin();
@ -124,6 +128,17 @@ export default function SearchActionGroup({
> >
{t("button.unselect", { ns: "common" })} {t("button.unselect", { ns: "common" })}
</div> </div>
{selectedObjects.length < totalItems && (
<>
<div className="p-1">{"|"}</div>
<div
className="cursor-pointer p-2 text-primary hover:rounded-lg hover:bg-secondary"
onClick={onSelectAllObjects}
>
{t("select_all", { ns: "views/events" })}
</div>
</>
)}
</div> </div>
{isAdmin && ( {isAdmin && (
<div className="flex items-center gap-1 md:gap-2"> <div className="flex items-center gap-1 md:gap-2">

View File

@ -52,7 +52,7 @@ import {
useRef, useRef,
useState, useState,
} from "react"; } from "react";
import { isDesktop } from "react-device-detect"; import { isDesktop, isMobileOnly } from "react-device-detect";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
import { import {
LuFolderCheck, LuFolderCheck,
@ -370,10 +370,10 @@ export default function FaceLibrary() {
/> />
{selectedFaces?.length > 0 ? ( {selectedFaces?.length > 0 ? (
<div className="flex items-center justify-center gap-2"> <div className="flex items-center justify-center gap-2">
<div className="mx-1 flex w-48 items-center justify-center text-sm text-muted-foreground"> <div className="mx-1 flex w-auto items-center justify-center text-sm text-muted-foreground">
<div className="p-1"> <div className="p-1">
{t("selected", { {t("selected", {
ns: "views/event", ns: "views/events",
count: selectedFaces.length, count: selectedFaces.length,
})} })}
</div> </div>
@ -384,6 +384,24 @@ export default function FaceLibrary() {
> >
{t("button.unselect", { ns: "common" })} {t("button.unselect", { ns: "common" })}
</div> </div>
{selectedFaces.length <
(pageToggle === "train"
? trainImages.length
: faceImages.length) && (
<>
<div className="p-1">{"|"}</div>
<div
className="cursor-pointer p-2 text-primary hover:rounded-lg hover:bg-secondary"
onClick={() =>
setSelectedFaces([
...(pageToggle === "train" ? trainImages : faceImages),
])
}
>
{t("select_all", { ns: "views/events" })}
</div>
</>
)}
</div> </div>
<Button <Button
className="flex gap-2" className="flex gap-2"
@ -482,6 +500,18 @@ function LibrarySelector({
[renameFace], [renameFace],
); );
const pageTitle = useMemo(() => {
if (pageToggle != "train") {
return pageToggle;
}
if (isMobileOnly) {
return t("train.titleShort");
}
return t("train.title");
}, [pageToggle, t]);
return ( return (
<> <>
<Dialog <Dialog
@ -532,7 +562,7 @@ function LibrarySelector({
<DropdownMenu modal={false}> <DropdownMenu modal={false}>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button className="flex justify-between smart-capitalize"> <Button className="flex justify-between smart-capitalize">
{pageToggle == "train" ? t("train.title") : pageToggle} {pageTitle}
<span className="ml-2 text-primary-variant"> <span className="ml-2 text-primary-variant">
({(pageToggle && faceData?.[pageToggle]?.length) || 0}) ({(pageToggle && faceData?.[pageToggle]?.length) || 0})
</span> </span>

View File

@ -421,10 +421,10 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) {
isMobileOnly && "justify-between", isMobileOnly && "justify-between",
)} )}
> >
<div className="flex w-48 items-center justify-center text-sm text-muted-foreground"> <div className="flex w-auto items-center justify-center text-sm text-muted-foreground md:w-auto">
<div className="p-1"> <div className="p-1">
{t("selected", { {t("selected", {
ns: "views/event", ns: "views/events",
count: selectedImages.length, count: selectedImages.length,
})} })}
</div> </div>
@ -435,6 +435,26 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) {
> >
{t("button.unselect", { ns: "common" })} {t("button.unselect", { ns: "common" })}
</div> </div>
{selectedImages.length <
(pageToggle === "train"
? trainImages?.length || 0
: dataset?.[pageToggle]?.length || 0) && (
<>
<div className="p-1">{"|"}</div>
<div
className="cursor-pointer p-2 text-primary hover:rounded-lg hover:bg-secondary"
onClick={() =>
setSelectedImages([
...(pageToggle === "train"
? trainImages || []
: dataset?.[pageToggle] || []),
])
}
>
{t("select_all", { ns: "views/events" })}
</div>
</>
)}
</div> </div>
<Button <Button
className="flex gap-2" className="flex gap-2"

View File

@ -572,6 +572,8 @@ export default function SearchView({
selectedObjects={selectedObjects} selectedObjects={selectedObjects}
setSelectedObjects={setSelectedObjects} setSelectedObjects={setSelectedObjects}
pullLatestData={refresh} pullLatestData={refresh}
onSelectAllObjects={onSelectAllObjects}
totalItems={uniqueResults.length}
/> />
</div> </div>
)} )}