mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-20 15:18:21 +03:00
enforce atomic config update in the frontend
This commit is contained in:
parent
1e061538a1
commit
7df7330eae
@ -33,6 +33,7 @@ import IconWrapper from "../ui/icon-wrapper";
|
|||||||
import { buttonVariants } from "../ui/button";
|
import { buttonVariants } from "../ui/button";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import ActivityIndicator from "../indicators/activity-indicator";
|
import ActivityIndicator from "../indicators/activity-indicator";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
type PolygonItemProps = {
|
type PolygonItemProps = {
|
||||||
polygon: Polygon;
|
polygon: Polygon;
|
||||||
@ -42,6 +43,10 @@ type PolygonItemProps = {
|
|||||||
setActivePolygonIndex: (index: number | undefined) => void;
|
setActivePolygonIndex: (index: number | undefined) => void;
|
||||||
setEditPane: (type: PolygonType) => void;
|
setEditPane: (type: PolygonType) => void;
|
||||||
handleCopyCoordinates: (index: number) => void;
|
handleCopyCoordinates: (index: number) => void;
|
||||||
|
isLoading: boolean;
|
||||||
|
setIsLoading: (loading: boolean) => void;
|
||||||
|
loadingPolygonIndex: number | undefined;
|
||||||
|
setLoadingPolygonIndex: (index: number | undefined) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function PolygonItem({
|
export default function PolygonItem({
|
||||||
@ -52,12 +57,15 @@ export default function PolygonItem({
|
|||||||
setActivePolygonIndex,
|
setActivePolygonIndex,
|
||||||
setEditPane,
|
setEditPane,
|
||||||
handleCopyCoordinates,
|
handleCopyCoordinates,
|
||||||
|
isLoading,
|
||||||
|
setIsLoading,
|
||||||
|
loadingPolygonIndex,
|
||||||
|
setLoadingPolygonIndex,
|
||||||
}: PolygonItemProps) {
|
}: PolygonItemProps) {
|
||||||
const { t } = useTranslation("views/settings");
|
const { t } = useTranslation("views/settings");
|
||||||
const { data: config, mutate: updateConfig } =
|
const { data: config, mutate: updateConfig } =
|
||||||
useSWR<FrigateConfig>("config");
|
useSWR<FrigateConfig>("config");
|
||||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
|
|
||||||
const cameraConfig = useMemo(() => {
|
const cameraConfig = useMemo(() => {
|
||||||
if (polygon?.camera && config) {
|
if (polygon?.camera && config) {
|
||||||
@ -89,6 +97,7 @@ export default function PolygonItem({
|
|||||||
: polygon.type;
|
: polygon.type;
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
setLoadingPolygonIndex(index);
|
||||||
|
|
||||||
if (polygon.type === "zone") {
|
if (polygon.type === "zone") {
|
||||||
// Zones use query string format
|
// Zones use query string format
|
||||||
@ -233,9 +242,17 @@ export default function PolygonItem({
|
|||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
setLoadingPolygonIndex(undefined);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[updateConfig, cameraConfig, t],
|
[
|
||||||
|
updateConfig,
|
||||||
|
cameraConfig,
|
||||||
|
t,
|
||||||
|
setIsLoading,
|
||||||
|
index,
|
||||||
|
setLoadingPolygonIndex,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
@ -261,6 +278,7 @@ export default function PolygonItem({
|
|||||||
: polygon.type;
|
: polygon.type;
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
setLoadingPolygonIndex(index);
|
||||||
|
|
||||||
if (polygon.type === "zone") {
|
if (polygon.type === "zone") {
|
||||||
// Zones use query string format
|
// Zones use query string format
|
||||||
@ -390,9 +408,18 @@ export default function PolygonItem({
|
|||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
setLoadingPolygonIndex(undefined);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[updateConfig, cameraConfig, t, polygon],
|
[
|
||||||
|
updateConfig,
|
||||||
|
cameraConfig,
|
||||||
|
t,
|
||||||
|
polygon,
|
||||||
|
setIsLoading,
|
||||||
|
index,
|
||||||
|
setLoadingPolygonIndex,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -420,7 +447,7 @@ export default function PolygonItem({
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{PolygonItemIcon &&
|
{PolygonItemIcon &&
|
||||||
(isLoading ? (
|
(isLoading && loadingPolygonIndex === index ? (
|
||||||
<div className="mr-2">
|
<div className="mr-2">
|
||||||
<ActivityIndicator className="size-5" />
|
<ActivityIndicator className="size-5" />
|
||||||
</div>
|
</div>
|
||||||
@ -431,7 +458,7 @@ export default function PolygonItem({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={handleToggleEnabled}
|
onClick={handleToggleEnabled}
|
||||||
disabled={isLoading}
|
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"
|
||||||
>
|
>
|
||||||
<PolygonItemIcon
|
<PolygonItemIcon
|
||||||
className="size-5"
|
className="size-5"
|
||||||
@ -509,6 +536,7 @@ export default function PolygonItem({
|
|||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
aria-label={t("button.edit", { ns: "common" })}
|
aria-label={t("button.edit", { ns: "common" })}
|
||||||
|
disabled={isLoading}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setActivePolygonIndex(index);
|
setActivePolygonIndex(index);
|
||||||
setEditPane(polygon.type);
|
setEditPane(polygon.type);
|
||||||
@ -518,6 +546,7 @@ export default function PolygonItem({
|
|||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
aria-label={t("button.copy", { ns: "common" })}
|
aria-label={t("button.copy", { ns: "common" })}
|
||||||
|
disabled={isLoading}
|
||||||
onClick={() => handleCopyCoordinates(index)}
|
onClick={() => handleCopyCoordinates(index)}
|
||||||
>
|
>
|
||||||
{t("button.copy", { ns: "common" })}
|
{t("button.copy", { ns: "common" })}
|
||||||
@ -539,10 +568,17 @@ export default function PolygonItem({
|
|||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<IconWrapper
|
<IconWrapper
|
||||||
icon={LuPencil}
|
icon={LuPencil}
|
||||||
className={`size-[15px] cursor-pointer ${hoveredPolygonIndex === index && "text-primary-variant"}`}
|
disabled={isLoading}
|
||||||
|
className={cn(
|
||||||
|
"size-[15px] cursor-pointer",
|
||||||
|
hoveredPolygonIndex === index && "text-primary-variant",
|
||||||
|
isLoading && "cursor-not-allowed opacity-50",
|
||||||
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setActivePolygonIndex(index);
|
if (!isLoading) {
|
||||||
setEditPane(polygon.type);
|
setActivePolygonIndex(index);
|
||||||
|
setEditPane(polygon.type);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
@ -555,10 +591,16 @@ export default function PolygonItem({
|
|||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<IconWrapper
|
<IconWrapper
|
||||||
icon={LuCopy}
|
icon={LuCopy}
|
||||||
className={`size-[15px] cursor-pointer ${
|
className={cn(
|
||||||
hoveredPolygonIndex === index && "text-primary-variant"
|
"size-[15px] cursor-pointer",
|
||||||
}`}
|
hoveredPolygonIndex === index && "text-primary-variant",
|
||||||
onClick={() => handleCopyCoordinates(index)}
|
isLoading && "cursor-not-allowed opacity-50",
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
if (!isLoading) {
|
||||||
|
handleCopyCoordinates(index);
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
@ -570,10 +612,13 @@ export default function PolygonItem({
|
|||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<IconWrapper
|
<IconWrapper
|
||||||
icon={HiTrash}
|
icon={HiTrash}
|
||||||
className={`size-[15px] cursor-pointer ${
|
disabled={isLoading}
|
||||||
|
className={cn(
|
||||||
|
"size-[15px] cursor-pointer",
|
||||||
hoveredPolygonIndex === index &&
|
hoveredPolygonIndex === index &&
|
||||||
"fill-primary-variant text-primary-variant"
|
"fill-primary-variant text-primary-variant",
|
||||||
}`}
|
isLoading && "cursor-not-allowed opacity-50",
|
||||||
|
)}
|
||||||
onClick={() => !isLoading && setDeleteDialogOpen(true)}
|
onClick={() => !isLoading && setDeleteDialogOpen(true)}
|
||||||
/>
|
/>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
|
|||||||
@ -53,6 +53,9 @@ export default function MasksAndZonesView({
|
|||||||
const [allPolygons, setAllPolygons] = useState<Polygon[]>([]);
|
const [allPolygons, setAllPolygons] = useState<Polygon[]>([]);
|
||||||
const [editingPolygons, setEditingPolygons] = useState<Polygon[]>([]);
|
const [editingPolygons, setEditingPolygons] = useState<Polygon[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [loadingPolygonIndex, setLoadingPolygonIndex] = useState<
|
||||||
|
number | undefined
|
||||||
|
>(undefined);
|
||||||
const [activePolygonIndex, setActivePolygonIndex] = useState<
|
const [activePolygonIndex, setActivePolygonIndex] = useState<
|
||||||
number | undefined
|
number | undefined
|
||||||
>(undefined);
|
>(undefined);
|
||||||
@ -538,6 +541,10 @@ export default function MasksAndZonesView({
|
|||||||
setActivePolygonIndex={setActivePolygonIndex}
|
setActivePolygonIndex={setActivePolygonIndex}
|
||||||
setEditPane={setEditPane}
|
setEditPane={setEditPane}
|
||||||
handleCopyCoordinates={handleCopyCoordinates}
|
handleCopyCoordinates={handleCopyCoordinates}
|
||||||
|
isLoading={isLoading}
|
||||||
|
setIsLoading={setIsLoading}
|
||||||
|
loadingPolygonIndex={loadingPolygonIndex}
|
||||||
|
setLoadingPolygonIndex={setLoadingPolygonIndex}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -608,6 +615,10 @@ export default function MasksAndZonesView({
|
|||||||
setActivePolygonIndex={setActivePolygonIndex}
|
setActivePolygonIndex={setActivePolygonIndex}
|
||||||
setEditPane={setEditPane}
|
setEditPane={setEditPane}
|
||||||
handleCopyCoordinates={handleCopyCoordinates}
|
handleCopyCoordinates={handleCopyCoordinates}
|
||||||
|
isLoading={isLoading}
|
||||||
|
setIsLoading={setIsLoading}
|
||||||
|
loadingPolygonIndex={loadingPolygonIndex}
|
||||||
|
setLoadingPolygonIndex={setLoadingPolygonIndex}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -678,6 +689,10 @@ export default function MasksAndZonesView({
|
|||||||
setActivePolygonIndex={setActivePolygonIndex}
|
setActivePolygonIndex={setActivePolygonIndex}
|
||||||
setEditPane={setEditPane}
|
setEditPane={setEditPane}
|
||||||
handleCopyCoordinates={handleCopyCoordinates}
|
handleCopyCoordinates={handleCopyCoordinates}
|
||||||
|
isLoading={isLoading}
|
||||||
|
setIsLoading={setIsLoading}
|
||||||
|
loadingPolygonIndex={loadingPolygonIndex}
|
||||||
|
setLoadingPolygonIndex={setLoadingPolygonIndex}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user