diff --git a/web/src/components/settings/PolygonItem.tsx b/web/src/components/settings/PolygonItem.tsx index 21815563b..d72c06076 100644 --- a/web/src/components/settings/PolygonItem.tsx +++ b/web/src/components/settings/PolygonItem.tsx @@ -33,6 +33,7 @@ import IconWrapper from "../ui/icon-wrapper"; import { buttonVariants } from "../ui/button"; import { Trans, useTranslation } from "react-i18next"; import ActivityIndicator from "../indicators/activity-indicator"; +import { cn } from "@/lib/utils"; type PolygonItemProps = { polygon: Polygon; @@ -42,6 +43,10 @@ type PolygonItemProps = { setActivePolygonIndex: (index: number | undefined) => void; setEditPane: (type: PolygonType) => void; handleCopyCoordinates: (index: number) => void; + isLoading: boolean; + setIsLoading: (loading: boolean) => void; + loadingPolygonIndex: number | undefined; + setLoadingPolygonIndex: (index: number | undefined) => void; }; export default function PolygonItem({ @@ -52,12 +57,15 @@ export default function PolygonItem({ setActivePolygonIndex, setEditPane, handleCopyCoordinates, + isLoading, + setIsLoading, + loadingPolygonIndex, + setLoadingPolygonIndex, }: PolygonItemProps) { const { t } = useTranslation("views/settings"); const { data: config, mutate: updateConfig } = useSWR("config"); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); - const [isLoading, setIsLoading] = useState(false); const cameraConfig = useMemo(() => { if (polygon?.camera && config) { @@ -89,6 +97,7 @@ export default function PolygonItem({ : polygon.type; setIsLoading(true); + setLoadingPolygonIndex(index); if (polygon.type === "zone") { // Zones use query string format @@ -233,9 +242,17 @@ export default function PolygonItem({ }) .finally(() => { setIsLoading(false); + setLoadingPolygonIndex(undefined); }); }, - [updateConfig, cameraConfig, t], + [ + updateConfig, + cameraConfig, + t, + setIsLoading, + index, + setLoadingPolygonIndex, + ], ); const handleDelete = () => { @@ -261,6 +278,7 @@ export default function PolygonItem({ : polygon.type; setIsLoading(true); + setLoadingPolygonIndex(index); if (polygon.type === "zone") { // Zones use query string format @@ -390,9 +408,18 @@ export default function PolygonItem({ }) .finally(() => { setIsLoading(false); + setLoadingPolygonIndex(undefined); }); }, - [updateConfig, cameraConfig, t, polygon], + [ + updateConfig, + cameraConfig, + t, + polygon, + setIsLoading, + index, + setLoadingPolygonIndex, + ], ); return ( @@ -420,7 +447,7 @@ export default function PolygonItem({ }`} > {PolygonItemIcon && - (isLoading ? ( + (isLoading && loadingPolygonIndex === index ? (
@@ -431,7 +458,7 @@ export default function PolygonItem({ type="button" onClick={handleToggleEnabled} disabled={isLoading} - className="mr-2 cursor-pointer border-none bg-transparent p-0 transition-opacity hover:opacity-70" + className="mr-2 cursor-pointer border-none bg-transparent p-0 transition-opacity hover:opacity-70 disabled:cursor-not-allowed disabled:opacity-50" > { setActivePolygonIndex(index); setEditPane(polygon.type); @@ -518,6 +546,7 @@ export default function PolygonItem({ handleCopyCoordinates(index)} > {t("button.copy", { ns: "common" })} @@ -539,10 +568,17 @@ export default function PolygonItem({ { - setActivePolygonIndex(index); - setEditPane(polygon.type); + if (!isLoading) { + setActivePolygonIndex(index); + setEditPane(polygon.type); + } }} /> @@ -555,10 +591,16 @@ export default function PolygonItem({ handleCopyCoordinates(index)} + className={cn( + "size-[15px] cursor-pointer", + hoveredPolygonIndex === index && "text-primary-variant", + isLoading && "cursor-not-allowed opacity-50", + )} + onClick={() => { + if (!isLoading) { + handleCopyCoordinates(index); + } + }} /> @@ -570,10 +612,13 @@ export default function PolygonItem({ !isLoading && setDeleteDialogOpen(true)} /> diff --git a/web/src/views/settings/MasksAndZonesView.tsx b/web/src/views/settings/MasksAndZonesView.tsx index 052b706bb..5fa932dbb 100644 --- a/web/src/views/settings/MasksAndZonesView.tsx +++ b/web/src/views/settings/MasksAndZonesView.tsx @@ -53,6 +53,9 @@ export default function MasksAndZonesView({ const [allPolygons, setAllPolygons] = useState([]); const [editingPolygons, setEditingPolygons] = useState([]); const [isLoading, setIsLoading] = useState(false); + const [loadingPolygonIndex, setLoadingPolygonIndex] = useState< + number | undefined + >(undefined); const [activePolygonIndex, setActivePolygonIndex] = useState< number | undefined >(undefined); @@ -538,6 +541,10 @@ export default function MasksAndZonesView({ setActivePolygonIndex={setActivePolygonIndex} setEditPane={setEditPane} handleCopyCoordinates={handleCopyCoordinates} + isLoading={isLoading} + setIsLoading={setIsLoading} + loadingPolygonIndex={loadingPolygonIndex} + setLoadingPolygonIndex={setLoadingPolygonIndex} /> ))} @@ -608,6 +615,10 @@ export default function MasksAndZonesView({ setActivePolygonIndex={setActivePolygonIndex} setEditPane={setEditPane} handleCopyCoordinates={handleCopyCoordinates} + isLoading={isLoading} + setIsLoading={setIsLoading} + loadingPolygonIndex={loadingPolygonIndex} + setLoadingPolygonIndex={setLoadingPolygonIndex} /> ))} @@ -678,6 +689,10 @@ export default function MasksAndZonesView({ setActivePolygonIndex={setActivePolygonIndex} setEditPane={setEditPane} handleCopyCoordinates={handleCopyCoordinates} + isLoading={isLoading} + setIsLoading={setIsLoading} + loadingPolygonIndex={loadingPolygonIndex} + setLoadingPolygonIndex={setLoadingPolygonIndex} /> ))}