mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-18 17:14:26 +03:00
Add ability to view attempts
This commit is contained in:
parent
f84713487f
commit
fd07fa5124
@ -14,7 +14,7 @@ import { toast } from "sonner";
|
||||
import useSWR from "swr";
|
||||
|
||||
export default function FaceLibrary() {
|
||||
const [page, setPage] = useState<string>();
|
||||
const [page, setPage] = useState<string>("attempts");
|
||||
const [pageToggle, setPageToggle] = useOptimisticState(page, setPage, 100);
|
||||
const tabsRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
@ -23,7 +23,8 @@ export default function FaceLibrary() {
|
||||
const { data: faceData, mutate: refreshFaces } = useSWR("faces");
|
||||
|
||||
const faces = useMemo<string[]>(
|
||||
() => (faceData ? Object.keys(faceData) : []),
|
||||
() =>
|
||||
faceData ? Object.keys(faceData).filter((face) => face != "debug") : [],
|
||||
[faceData],
|
||||
);
|
||||
const faceImages = useMemo<string[]>(
|
||||
@ -31,6 +32,11 @@ export default function FaceLibrary() {
|
||||
[pageToggle, faceData],
|
||||
);
|
||||
|
||||
const faceAttempts = useMemo<string[]>(
|
||||
() => faceData?.["debug"] || [],
|
||||
[faceData],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!pageToggle && faces) {
|
||||
setPageToggle(faces[0]);
|
||||
@ -105,10 +111,24 @@ export default function FaceLibrary() {
|
||||
}
|
||||
}}
|
||||
>
|
||||
{faceAttempts.length > 0 && (
|
||||
<>
|
||||
<ToggleGroupItem
|
||||
value="attempts"
|
||||
className={`flex scroll-mx-10 items-center justify-between gap-2 ${pageToggle == "attempts" ? "" : "*:text-muted-foreground"}`}
|
||||
data-nav-item="attempts"
|
||||
aria-label="Select attempts"
|
||||
>
|
||||
<div>Attempts</div>
|
||||
</ToggleGroupItem>
|
||||
<div>|</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{Object.values(faces).map((item) => (
|
||||
<ToggleGroupItem
|
||||
key={item}
|
||||
className={`flex scroll-mx-10 items-center justify-between gap-2 ${page == "UI settings" ? "last:mr-20" : ""} ${pageToggle == item ? "" : "*:text-muted-foreground"}`}
|
||||
className={`flex scroll-mx-10 items-center justify-between gap-2 ${pageToggle == item ? "" : "*:text-muted-foreground"}`}
|
||||
value={item}
|
||||
data-nav-item={item}
|
||||
aria-label={`Select ${item}`}
|
||||
@ -121,20 +141,100 @@ export default function FaceLibrary() {
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
{pageToggle && (
|
||||
<div className="scrollbar-container flex flex-wrap gap-2 overflow-y-scroll">
|
||||
{faceImages.map((image: string) => (
|
||||
<FaceImage key={image} name={pageToggle} image={image} />
|
||||
))}
|
||||
<Button
|
||||
key="upload"
|
||||
className="size-40"
|
||||
onClick={() => setUpload(true)}
|
||||
{pageToggle &&
|
||||
(pageToggle == "attempts" ? (
|
||||
<AttemptsGrid attemptImages={faceAttempts} />
|
||||
) : (
|
||||
<FaceGrid
|
||||
faceImages={faceImages}
|
||||
pageToggle={pageToggle}
|
||||
setUpload={setUpload}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type AttemptsGridProps = {
|
||||
attemptImages: string[];
|
||||
};
|
||||
function AttemptsGrid({ attemptImages }: AttemptsGridProps) {
|
||||
return (
|
||||
<div className="scrollbar-container flex flex-wrap gap-2 overflow-y-scroll">
|
||||
{attemptImages.map((image: string) => (
|
||||
<FaceImage key={image} name={"something"} image={image} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type FaceAttemptProps = {
|
||||
name: string;
|
||||
image: string;
|
||||
};
|
||||
function FaceAttempt({ name, image }: FaceAttemptProps) {
|
||||
const [hovered, setHovered] = useState(false);
|
||||
|
||||
const onDelete = useCallback(() => {
|
||||
axios
|
||||
.post(`/faces/${name}/delete`, { ids: [image] })
|
||||
.then((resp) => {
|
||||
if (resp.status == 200) {
|
||||
toast.error(`Successfully deleted face.`, { position: "top-center" });
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response?.data?.message) {
|
||||
toast.error(`Failed to delete: ${error.response.data.message}`, {
|
||||
position: "top-center",
|
||||
});
|
||||
} else {
|
||||
toast.error(`Failed to delete: ${error.message}`, {
|
||||
position: "top-center",
|
||||
});
|
||||
}
|
||||
});
|
||||
}, [name, image]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative h-40"
|
||||
onMouseEnter={isDesktop ? () => setHovered(true) : undefined}
|
||||
onMouseLeave={isDesktop ? () => setHovered(false) : undefined}
|
||||
onClick={isDesktop ? undefined : () => setHovered(!hovered)}
|
||||
>
|
||||
{hovered && (
|
||||
<div className="absolute right-1 top-1">
|
||||
<Chip
|
||||
className="cursor-pointer rounded-md bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500"
|
||||
onClick={() => onDelete()}
|
||||
>
|
||||
<LuImagePlus className="size-10" />
|
||||
</Button>
|
||||
<LuTrash className="size-4 fill-destructive text-destructive" />
|
||||
</Chip>
|
||||
</div>
|
||||
)}
|
||||
<img
|
||||
className="h-40 rounded-md"
|
||||
src={`${baseUrl}clips/faces/${name}/${image}`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type FaceGridProps = {
|
||||
faceImages: string[];
|
||||
pageToggle: string;
|
||||
setUpload: (upload: boolean) => void;
|
||||
};
|
||||
function FaceGrid({ faceImages, pageToggle, setUpload }: FaceGridProps) {
|
||||
return (
|
||||
<div className="scrollbar-container flex flex-wrap gap-2 overflow-y-scroll">
|
||||
{faceImages.map((image: string) => (
|
||||
<FaceImage key={image} name={pageToggle} image={image} />
|
||||
))}
|
||||
<Button key="upload" className="size-40" onClick={() => setUpload(true)}>
|
||||
<LuImagePlus className="size-10" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user