mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-06-25 05:41:53 +03:00
add reordering save text to camera management view
This commit is contained in:
parent
be8c81e173
commit
e33da68fff
@ -482,6 +482,8 @@
|
|||||||
"disableDesc": "Enable a camera that is currently not visible in the UI and disabled in the configuration. A restart of Frigate is required after enabling.",
|
"disableDesc": "Enable a camera that is currently not visible in the UI and disabled in the configuration. A restart of Frigate is required after enabling.",
|
||||||
"enableSuccess": "Enabled {{cameraName}} in configuration. Restart Frigate to apply the changes.",
|
"enableSuccess": "Enabled {{cameraName}} in configuration. Restart Frigate to apply the changes.",
|
||||||
"reorderHandle": "Drag to reorder",
|
"reorderHandle": "Drag to reorder",
|
||||||
|
"saving": "Saving…",
|
||||||
|
"saved": "Saved",
|
||||||
"friendlyName": {
|
"friendlyName": {
|
||||||
"edit": "Edit camera display name",
|
"edit": "Edit camera display name",
|
||||||
"title": "Edit Display Name",
|
"title": "Edit Display Name",
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import CameraEditForm from "@/components/settings/CameraEditForm";
|
|||||||
import CameraWizardDialog from "@/components/settings/CameraWizardDialog";
|
import CameraWizardDialog from "@/components/settings/CameraWizardDialog";
|
||||||
import DeleteCameraDialog from "@/components/overlay/dialog/DeleteCameraDialog";
|
import DeleteCameraDialog from "@/components/overlay/dialog/DeleteCameraDialog";
|
||||||
import {
|
import {
|
||||||
|
LuCheck,
|
||||||
LuExternalLink,
|
LuExternalLink,
|
||||||
LuGripVertical,
|
LuGripVertical,
|
||||||
LuPencil,
|
LuPencil,
|
||||||
@ -52,6 +53,10 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
|
|
||||||
|
const REORDER_SAVED_INDICATOR_MS = 1500;
|
||||||
|
|
||||||
|
type ReorderSaveStatus = "idle" | "saving" | "saved";
|
||||||
|
|
||||||
type CameraManagementViewProps = {
|
type CameraManagementViewProps = {
|
||||||
setUnsavedChanges: React.Dispatch<React.SetStateAction<boolean>>;
|
setUnsavedChanges: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
profileState?: ProfileState;
|
profileState?: ProfileState;
|
||||||
@ -113,6 +118,19 @@ export default function CameraManagementView({
|
|||||||
});
|
});
|
||||||
}, [enabledCameras]);
|
}, [enabledCameras]);
|
||||||
|
|
||||||
|
const [reorderSaveStatus, setReorderSaveStatus] =
|
||||||
|
useState<ReorderSaveStatus>("idle");
|
||||||
|
const reorderSavedTimerRef = useRef<ReturnType<typeof setTimeout> | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (reorderSavedTimerRef.current) {
|
||||||
|
clearTimeout(reorderSavedTimerRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleReorderDragEnd = useCallback(async () => {
|
const handleReorderDragEnd = useCallback(async () => {
|
||||||
const current = orderedCamerasRef.current;
|
const current = orderedCamerasRef.current;
|
||||||
if (
|
if (
|
||||||
@ -127,14 +145,26 @@ export default function CameraManagementView({
|
|||||||
cameraUpdates[cam] = { ui: { order: i * 10 } };
|
cameraUpdates[cam] = { ui: { order: i * 10 } };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (reorderSavedTimerRef.current) {
|
||||||
|
clearTimeout(reorderSavedTimerRef.current);
|
||||||
|
reorderSavedTimerRef.current = null;
|
||||||
|
}
|
||||||
|
setReorderSaveStatus("saving");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await axios.put("config/set", {
|
await axios.put("config/set", {
|
||||||
requires_restart: 0,
|
requires_restart: 0,
|
||||||
config_data: { cameras: cameraUpdates },
|
config_data: { cameras: cameraUpdates },
|
||||||
});
|
});
|
||||||
await updateConfig();
|
await updateConfig();
|
||||||
|
setReorderSaveStatus("saved");
|
||||||
|
reorderSavedTimerRef.current = setTimeout(() => {
|
||||||
|
setReorderSaveStatus("idle");
|
||||||
|
reorderSavedTimerRef.current = null;
|
||||||
|
}, REORDER_SAVED_INDICATOR_MS);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setOrderedCameras(enabledCameras);
|
setOrderedCameras(enabledCameras);
|
||||||
|
setReorderSaveStatus("idle");
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
axios.isAxiosError(error) &&
|
axios.isAxiosError(error) &&
|
||||||
(error.response?.data?.message || error.response?.data?.detail)
|
(error.response?.data?.message || error.response?.data?.detail)
|
||||||
@ -238,22 +268,27 @@ export default function CameraManagementView({
|
|||||||
</p>
|
</p>
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
<Reorder.Group
|
<div className="max-w-md space-y-1.5">
|
||||||
as="div"
|
<Reorder.Group
|
||||||
axis="y"
|
as="div"
|
||||||
values={orderedCameras}
|
axis="y"
|
||||||
onReorder={setOrderedCameras}
|
values={orderedCameras}
|
||||||
className="max-w-md space-y-2 rounded-lg bg-secondary p-4"
|
onReorder={setOrderedCameras}
|
||||||
>
|
className="space-y-2 rounded-lg bg-secondary p-4"
|
||||||
{orderedCameras.map((camera) => (
|
>
|
||||||
<EnabledCameraRow
|
{orderedCameras.map((camera) => (
|
||||||
key={camera}
|
<EnabledCameraRow
|
||||||
camera={camera}
|
key={camera}
|
||||||
onConfigChanged={updateConfig}
|
camera={camera}
|
||||||
onDragEnd={handleReorderDragEnd}
|
onConfigChanged={updateConfig}
|
||||||
/>
|
onDragEnd={handleReorderDragEnd}
|
||||||
))}
|
/>
|
||||||
</Reorder.Group>
|
))}
|
||||||
|
</Reorder.Group>
|
||||||
|
<ReorderSaveStatusIndicator
|
||||||
|
status={reorderSaveStatus}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<p className="text-sm text-muted-foreground md:hidden">
|
<p className="text-sm text-muted-foreground md:hidden">
|
||||||
<Trans ns="views/settings">
|
<Trans ns="views/settings">
|
||||||
cameraManagement.streams.enableDesc
|
cameraManagement.streams.enableDesc
|
||||||
@ -373,6 +408,37 @@ export default function CameraManagementView({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ReorderSaveStatusIndicatorProps = {
|
||||||
|
status: ReorderSaveStatus;
|
||||||
|
};
|
||||||
|
|
||||||
|
function ReorderSaveStatusIndicator({
|
||||||
|
status,
|
||||||
|
}: ReorderSaveStatusIndicatorProps) {
|
||||||
|
const { t } = useTranslation(["views/settings"]);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
aria-live="polite"
|
||||||
|
className={cn(
|
||||||
|
"flex h-4 items-center justify-start gap-1 text-xs transition-opacity duration-200",
|
||||||
|
status === "idle" ? "opacity-0" : "opacity-100",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{status === "saving" && (
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
{t("cameraManagement.streams.saving")}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{status === "saved" && (
|
||||||
|
<span className="flex items-center gap-1 text-success">
|
||||||
|
<LuCheck className="size-3.5" />
|
||||||
|
{t("cameraManagement.streams.saved")}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
type EnabledCameraRowProps = {
|
type EnabledCameraRowProps = {
|
||||||
camera: string;
|
camera: string;
|
||||||
onConfigChanged: () => Promise<unknown>;
|
onConfigChanged: () => Promise<unknown>;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user