add to camera management pane

This commit is contained in:
Josh Hawkins 2026-05-26 09:52:16 -05:00
parent 6c5558271a
commit 0e56a1ee1c

View File

@ -15,6 +15,7 @@ import CameraWizardDialog from "@/components/settings/CameraWizardDialog";
import DeleteCameraDialog from "@/components/overlay/dialog/DeleteCameraDialog"; import DeleteCameraDialog from "@/components/overlay/dialog/DeleteCameraDialog";
import { import {
LuCheck, LuCheck,
LuCopy,
LuExternalLink, LuExternalLink,
LuGripVertical, LuGripVertical,
LuPencil, LuPencil,
@ -22,6 +23,7 @@ import {
LuRefreshCcw, LuRefreshCcw,
LuTrash2, LuTrash2,
} from "react-icons/lu"; } from "react-icons/lu";
import CloneCameraDialog from "@/components/settings/CloneCameraDialog";
import { Reorder, useDragControls } from "framer-motion"; import { Reorder, useDragControls } from "framer-motion";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { useDocDomain } from "@/hooks/use-doc-domain"; import { useDocDomain } from "@/hooks/use-doc-domain";
@ -88,6 +90,9 @@ export default function CameraManagementView({
const [showWizard, setShowWizard] = useState(false); const [showWizard, setShowWizard] = useState(false);
const [showDeleteDialog, setShowDeleteDialog] = useState(false); const [showDeleteDialog, setShowDeleteDialog] = useState(false);
const [cloneSourceCamera, setCloneSourceCamera] = useState<string | null>(
null,
);
// State for restart dialog when enabling a disabled camera // State for restart dialog when enabling a disabled camera
const [restartDialogOpen, setRestartDialogOpen] = useState(false); const [restartDialogOpen, setRestartDialogOpen] = useState(false);
@ -288,6 +293,7 @@ export default function CameraManagementView({
onConfigChanged={updateConfig} onConfigChanged={updateConfig}
onDragEnd={handleReorderDragEnd} onDragEnd={handleReorderDragEnd}
setRestartDialogOpen={setRestartDialogOpen} setRestartDialogOpen={setRestartDialogOpen}
onClone={setCloneSourceCamera}
/> />
))} ))}
</Reorder.Group> </Reorder.Group>
@ -307,6 +313,7 @@ export default function CameraManagementView({
camera={camera} camera={camera}
onConfigChanged={updateConfig} onConfigChanged={updateConfig}
setRestartDialogOpen={setRestartDialogOpen} setRestartDialogOpen={setRestartDialogOpen}
onClone={setCloneSourceCamera}
/> />
))} ))}
</div> </div>
@ -364,6 +371,11 @@ export default function CameraManagementView({
onClose={() => setRestartDialogOpen(false)} onClose={() => setRestartDialogOpen(false)}
onRestart={() => sendRestart("restart")} onRestart={() => sendRestart("restart")}
/> />
<CloneCameraDialog
open={cloneSourceCamera !== null}
onClose={() => setCloneSourceCamera(null)}
sourceCamera={cloneSourceCamera ?? ""}
/>
</> </>
); );
} }
@ -404,6 +416,7 @@ type ActiveCameraRowProps = {
onConfigChanged: () => Promise<unknown>; onConfigChanged: () => Promise<unknown>;
onDragEnd: () => void; onDragEnd: () => void;
setRestartDialogOpen: React.Dispatch<React.SetStateAction<boolean>>; setRestartDialogOpen: React.Dispatch<React.SetStateAction<boolean>>;
onClone: (camera: string) => void;
}; };
function ActiveCameraRow({ function ActiveCameraRow({
@ -411,6 +424,7 @@ function ActiveCameraRow({
onConfigChanged, onConfigChanged,
onDragEnd, onDragEnd,
setRestartDialogOpen, setRestartDialogOpen,
onClone,
}: ActiveCameraRowProps) { }: ActiveCameraRowProps) {
const { t } = useTranslation(["views/settings"]); const { t } = useTranslation(["views/settings"]);
const controls = useDragControls(); const controls = useDragControls();
@ -438,6 +452,22 @@ function ActiveCameraRow({
cameraName={camera} cameraName={camera}
onConfigChanged={onConfigChanged} onConfigChanged={onConfigChanged}
/> />
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="size-7"
aria-label={t("cameraManagement.clone.triggerAriaLabel", {
cameraName: camera,
})}
onClick={() => onClone(camera)}
>
<LuCopy className="size-3.5" />
</Button>
</TooltipTrigger>
<TooltipContent>{t("cameraManagement.clone.trigger")}</TooltipContent>
</Tooltip>
</div> </div>
<CameraStatusSelect <CameraStatusSelect
cameraName={camera} cameraName={camera}
@ -453,13 +483,17 @@ type DisabledCameraRowProps = {
camera: string; camera: string;
onConfigChanged: () => Promise<unknown>; onConfigChanged: () => Promise<unknown>;
setRestartDialogOpen: React.Dispatch<React.SetStateAction<boolean>>; setRestartDialogOpen: React.Dispatch<React.SetStateAction<boolean>>;
onClone: (camera: string) => void;
}; };
function DisabledCameraRow({ function DisabledCameraRow({
camera, camera,
onConfigChanged, onConfigChanged,
setRestartDialogOpen, setRestartDialogOpen,
onClone,
}: DisabledCameraRowProps) { }: DisabledCameraRowProps) {
const { t } = useTranslation(["views/settings"]);
return ( return (
<div className="flex flex-row items-center justify-between"> <div className="flex flex-row items-center justify-between">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
@ -468,6 +502,22 @@ function DisabledCameraRow({
cameraName={camera} cameraName={camera}
onConfigChanged={onConfigChanged} onConfigChanged={onConfigChanged}
/> />
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="size-7"
aria-label={t("cameraManagement.clone.triggerAriaLabel", {
cameraName: camera,
})}
onClick={() => onClone(camera)}
>
<LuCopy className="size-3.5" />
</Button>
</TooltipTrigger>
<TooltipContent>{t("cameraManagement.clone.trigger")}</TooltipContent>
</Tooltip>
</div> </div>
<CameraStatusSelect <CameraStatusSelect
cameraName={camera} cameraName={camera}