import { baseUrl } from "@/api/baseUrl"; import Chip from "@/components/indicators/Chip"; import UploadImageDialog from "@/components/overlay/dialog/UploadImageDialog"; import { Button } from "@/components/ui/button"; import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; import { Toaster } from "@/components/ui/sonner"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import useOptimisticState from "@/hooks/use-optimistic-state"; import axios from "axios"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { isDesktop } from "react-device-detect"; import { LuImagePlus, LuTrash } from "react-icons/lu"; import { toast } from "sonner"; import useSWR from "swr"; export default function FaceLibrary() { const [page, setPage] = useState(); const [pageToggle, setPageToggle] = useOptimisticState(page, setPage, 100); const tabsRef = useRef(null); // face data const { data: faceData, mutate: refreshFaces } = useSWR("faces"); const faces = useMemo( () => faceData ? Object.keys(faceData).filter((face) => face != "debug") : [], [faceData], ); const faceImages = useMemo( () => (pageToggle && faceData ? faceData[pageToggle] : []), [pageToggle, faceData], ); const faceAttempts = useMemo( () => faceData?.["debug"] || [], [faceData], ); useEffect(() => { if (!pageToggle) { if (faceAttempts.length > 0) { setPageToggle("attempts"); } else if (faces) { setPageToggle(faces[0]); } } else if (pageToggle == "attempts" && faceAttempts.length == 0) { setPageToggle(faces[0]); } // we need to listen on the value of the faces list // eslint-disable-next-line react-hooks/exhaustive-deps }, [faceAttempts, faces]); // upload const [upload, setUpload] = useState(false); const onUploadImage = useCallback( (file: File) => { const formData = new FormData(); formData.append("file", file); axios .post(`faces/${pageToggle}`, formData, { headers: { "Content-Type": "multipart/form-data", }, }) .then((resp) => { if (resp.status == 200) { setUpload(false); refreshFaces(); toast.success( "Successfully uploaded image. View the file in the /exports folder.", { position: "top-center" }, ); } }) .catch((error) => { if (error.response?.data?.message) { toast.error( `Failed to upload image: ${error.response.data.message}`, { position: "top-center" }, ); } else { toast.error(`Failed to upload image: ${error.message}`, { position: "top-center", }); } }); }, [pageToggle, refreshFaces], ); return (
{ if (value) { setPageToggle(value); } }} > {faceAttempts.length > 0 && ( <>
Attempts
|
)} {Object.values(faces).map((item) => (
{item}
))}
{pageToggle && (pageToggle == "attempts" ? ( ) : ( ))}
); } type AttemptsGridProps = { attemptImages: string[]; onRefresh: () => void; }; function AttemptsGrid({ attemptImages, onRefresh }: AttemptsGridProps) { return (
{attemptImages.map((image: string) => ( ))}
); } type FaceAttemptProps = { image: string; onRefresh: () => void; }; function FaceAttempt({ image, onRefresh }: FaceAttemptProps) { const [hovered, setHovered] = useState(false); const data = useMemo(() => { const parts = image.split("-"); return { eventId: `${parts[0]}-${parts[1]}`, name: parts[2], score: parts[3], }; }, [image]); const onDelete = useCallback(() => { axios .post(`/faces/debug/delete`, { ids: [image] }) .then((resp) => { if (resp.status == 200) { toast.success(`Successfully deleted face.`, { position: "top-center", }); onRefresh(); } }) .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", }); } }); }, [image, onRefresh]); return (
setHovered(true) : undefined} onMouseLeave={isDesktop ? () => setHovered(false) : undefined} onClick={isDesktop ? undefined : () => setHovered(!hovered)} > {hovered && (
onDelete()} >
)}
{`${data.name}: ${data.score}`}
); } type FaceGridProps = { faceImages: string[]; pageToggle: string; setUpload: (upload: boolean) => void; onRefresh: () => void; }; function FaceGrid({ faceImages, pageToggle, setUpload, onRefresh, }: FaceGridProps) { return (
{faceImages.map((image: string) => ( ))}
); } type FaceImageProps = { name: string; image: string; onRefresh: () => void; }; function FaceImage({ name, image, onRefresh }: FaceImageProps) { const [hovered, setHovered] = useState(false); const onDelete = useCallback(() => { axios .post(`/faces/${name}/delete`, { ids: [image] }) .then((resp) => { if (resp.status == 200) { toast.success(`Successfully deleted face.`, { position: "top-center", }); onRefresh(); } }) .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, onRefresh]); return (
setHovered(true) : undefined} onMouseLeave={isDesktop ? () => setHovered(false) : undefined} onClick={isDesktop ? undefined : () => setHovered(!hovered)} > {hovered && (
onDelete()} >
)}
); }