mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-08 20:25:26 +03:00
Add camera feature buttons
This commit is contained in:
parent
027337a865
commit
b9d4a3d51a
60
web/src/components/dynamic/CameraFeatureToggle.tsx
Normal file
60
web/src/components/dynamic/CameraFeatureToggle.tsx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { IconType } from "react-icons";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/ui/tooltip";
|
||||||
|
import { isDesktop } from "react-device-detect";
|
||||||
|
|
||||||
|
const variants = {
|
||||||
|
primary: {
|
||||||
|
active: "font-bold text-primary-foreground bg-primary",
|
||||||
|
inactive: "text-muted-foreground bg-muted",
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
active: "font-bold text-primary",
|
||||||
|
inactive: "text-muted-foreground",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
type CameraFeatureToggleProps = {
|
||||||
|
className?: string;
|
||||||
|
variant?: "primary" | "secondary";
|
||||||
|
isActive: boolean;
|
||||||
|
Icon: IconType;
|
||||||
|
title: string;
|
||||||
|
onClick?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function CameraFeatureToggle({
|
||||||
|
className = "",
|
||||||
|
variant = "primary",
|
||||||
|
isActive,
|
||||||
|
Icon,
|
||||||
|
title,
|
||||||
|
onClick,
|
||||||
|
}: CameraFeatureToggleProps) {
|
||||||
|
const content = (
|
||||||
|
<div
|
||||||
|
onClick={onClick}
|
||||||
|
className={`${className} flex flex-col justify-center items-center rounded-lg ${
|
||||||
|
variants[variant][isActive ? "active" : "inactive"]
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Icon className="size-5 md:m-[6px]" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isDesktop) {
|
||||||
|
return (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>{content}</TooltipTrigger>
|
||||||
|
<TooltipContent side="bottom">
|
||||||
|
<p>{title}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
@ -1,4 +1,11 @@
|
|||||||
import { usePtzCommand } from "@/api/ws";
|
import {
|
||||||
|
useAudioState,
|
||||||
|
useDetectState,
|
||||||
|
usePtzCommand,
|
||||||
|
useRecordingsState,
|
||||||
|
useSnapshotsState,
|
||||||
|
} from "@/api/ws";
|
||||||
|
import CameraFeatureToggle from "@/components/dynamic/CameraFeatureToggle";
|
||||||
import LivePlayer from "@/components/player/LivePlayer";
|
import LivePlayer from "@/components/player/LivePlayer";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@ -7,6 +14,7 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
import useKeyboardListener from "@/hooks/use-keyboard-listener";
|
import useKeyboardListener from "@/hooks/use-keyboard-listener";
|
||||||
import { CameraConfig } from "@/types/frigateConfig";
|
import { CameraConfig } from "@/types/frigateConfig";
|
||||||
import { CameraPtzInfo } from "@/types/ptz";
|
import { CameraPtzInfo } from "@/types/ptz";
|
||||||
@ -20,7 +28,15 @@ import {
|
|||||||
FaAngleUp,
|
FaAngleUp,
|
||||||
} from "react-icons/fa";
|
} from "react-icons/fa";
|
||||||
import { IoMdArrowBack } from "react-icons/io";
|
import { IoMdArrowBack } from "react-icons/io";
|
||||||
import { MdZoomIn, MdZoomOut } from "react-icons/md";
|
import { LuEar, LuEarOff, LuVideo, LuVideoOff } from "react-icons/lu";
|
||||||
|
import {
|
||||||
|
MdNoPhotography,
|
||||||
|
MdPersonOff,
|
||||||
|
MdPersonSearch,
|
||||||
|
MdPhotoCamera,
|
||||||
|
MdZoomIn,
|
||||||
|
MdZoomOut,
|
||||||
|
} from "react-icons/md";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
@ -30,6 +46,19 @@ type LiveCameraViewProps = {
|
|||||||
export default function LiveCameraView({ camera }: LiveCameraViewProps) {
|
export default function LiveCameraView({ camera }: LiveCameraViewProps) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
// camera features
|
||||||
|
|
||||||
|
const { payload: detectState, send: sendDetect } = useDetectState(
|
||||||
|
camera.name,
|
||||||
|
);
|
||||||
|
const { payload: recordState, send: sendRecord } = useRecordingsState(
|
||||||
|
camera.name,
|
||||||
|
);
|
||||||
|
const { payload: snapshotState, send: sendSnapshot } = useSnapshotsState(
|
||||||
|
camera.name,
|
||||||
|
);
|
||||||
|
const { payload: audioState, send: sendAudio } = useAudioState(camera.name);
|
||||||
|
|
||||||
const growClassName = useMemo(() => {
|
const growClassName = useMemo(() => {
|
||||||
if (camera.detect.width / camera.detect.height > 2) {
|
if (camera.detect.width / camera.detect.height > 2) {
|
||||||
return "absolute left-2 right-2 top-[50%] -translate-y-[50%]";
|
return "absolute left-2 right-2 top-[50%] -translate-y-[50%]";
|
||||||
@ -45,6 +74,36 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) {
|
|||||||
<IoMdArrowBack className="size-5 mr-[10px]" />
|
<IoMdArrowBack className="size-5 mr-[10px]" />
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
|
<TooltipProvider>
|
||||||
|
<div className="flex items-center gap-1 mr-1 *:rounded-lg">
|
||||||
|
<CameraFeatureToggle
|
||||||
|
Icon={detectState == "ON" ? MdPersonSearch : MdPersonOff}
|
||||||
|
isActive={detectState == "ON"}
|
||||||
|
title={`${detectState == "ON" ? "Disable" : "Enable"} Detect`}
|
||||||
|
onClick={() => sendDetect(detectState == "ON" ? "OFF" : "ON")}
|
||||||
|
/>
|
||||||
|
<CameraFeatureToggle
|
||||||
|
Icon={recordState == "ON" ? LuVideo : LuVideoOff}
|
||||||
|
isActive={recordState == "ON"}
|
||||||
|
title={`${recordState == "ON" ? "Disable" : "Enable"} Recording`}
|
||||||
|
onClick={() => sendRecord(recordState == "ON" ? "OFF" : "ON")}
|
||||||
|
/>
|
||||||
|
<CameraFeatureToggle
|
||||||
|
Icon={snapshotState == "ON" ? MdPhotoCamera : MdNoPhotography}
|
||||||
|
isActive={snapshotState == "ON"}
|
||||||
|
title={`${snapshotState == "ON" ? "Disable" : "Enable"} Snapshots`}
|
||||||
|
onClick={() => sendSnapshot(snapshotState == "ON" ? "OFF" : "ON")}
|
||||||
|
/>
|
||||||
|
{camera.audio.enabled_in_config && (
|
||||||
|
<CameraFeatureToggle
|
||||||
|
Icon={audioState == "ON" ? LuEar : LuEarOff}
|
||||||
|
isActive={audioState == "ON"}
|
||||||
|
title={`${audioState == "ON" ? "Disable" : "Enable"} Audio Detect`}
|
||||||
|
onClick={() => sendAudio(audioState == "ON" ? "OFF" : "ON")}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</TooltipProvider>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative size-full">
|
<div className="relative size-full">
|
||||||
@ -70,7 +129,7 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) {
|
|||||||
function PtzControlPanel({ camera }: { camera: string }) {
|
function PtzControlPanel({ camera }: { camera: string }) {
|
||||||
const { data: ptz } = useSWR<CameraPtzInfo>(`${camera}/ptz/info`);
|
const { data: ptz } = useSWR<CameraPtzInfo>(`${camera}/ptz/info`);
|
||||||
|
|
||||||
const { payload: _, send: sendPtz } = usePtzCommand(camera);
|
const { send: sendPtz } = usePtzCommand(camera);
|
||||||
|
|
||||||
const onStop = useCallback(
|
const onStop = useCallback(
|
||||||
(e: React.SyntheticEvent) => {
|
(e: React.SyntheticEvent) => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user