mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-26 00:27:40 +03:00
fix console errors with tooltips and dropdown in single camera live view
This commit is contained in:
parent
a525dd0ff7
commit
303471242f
@ -28,7 +28,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipProvider,
|
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { useResizeObserver } from "@/hooks/resize-observer";
|
import { useResizeObserver } from "@/hooks/resize-observer";
|
||||||
@ -116,6 +115,7 @@ import {
|
|||||||
SelectGroup,
|
SelectGroup,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { usePersistence } from "@/hooks/use-persistence";
|
import { usePersistence } from "@/hooks/use-persistence";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
@ -499,122 +499,118 @@ export default function LiveCameraView({
|
|||||||
) : (
|
) : (
|
||||||
<div />
|
<div />
|
||||||
)}
|
)}
|
||||||
<TooltipProvider>
|
<div
|
||||||
<div
|
className={`flex flex-row items-center gap-2 *:rounded-lg ${isMobile ? "landscape:flex-col" : ""}`}
|
||||||
className={`flex flex-row items-center gap-2 *:rounded-lg ${isMobile ? "landscape:flex-col" : ""}`}
|
>
|
||||||
>
|
{fullscreen && (
|
||||||
{fullscreen && (
|
<Button
|
||||||
<Button
|
className="bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500 text-primary"
|
||||||
className="bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500 text-primary"
|
aria-label={t("label.back", { ns: "common" })}
|
||||||
aria-label={t("label.back", { ns: "common" })}
|
size="sm"
|
||||||
size="sm"
|
onClick={() => navigate(-1)}
|
||||||
onClick={() => navigate(-1)}
|
>
|
||||||
>
|
<IoMdArrowRoundBack className="size-5 text-secondary-foreground" />
|
||||||
<IoMdArrowRoundBack className="size-5 text-secondary-foreground" />
|
{isDesktop && (
|
||||||
{isDesktop && (
|
<div className="text-secondary-foreground">
|
||||||
<div className="text-secondary-foreground">
|
{t("button.back", { ns: "common" })}
|
||||||
{t("button.back", { ns: "common" })}
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
</Button>
|
||||||
</Button>
|
)}
|
||||||
)}
|
{supportsFullscreen && (
|
||||||
{supportsFullscreen && (
|
<CameraFeatureToggle
|
||||||
<CameraFeatureToggle
|
className="p-2 md:p-0"
|
||||||
className="p-2 md:p-0"
|
variant={fullscreen ? "overlay" : "primary"}
|
||||||
variant={fullscreen ? "overlay" : "primary"}
|
Icon={fullscreen ? FaCompress : FaExpand}
|
||||||
Icon={fullscreen ? FaCompress : FaExpand}
|
isActive={fullscreen}
|
||||||
isActive={fullscreen}
|
title={
|
||||||
title={
|
fullscreen
|
||||||
fullscreen
|
? t("button.close", { ns: "common" })
|
||||||
? t("button.close", { ns: "common" })
|
: t("button.fullscreen", { ns: "common" })
|
||||||
: t("button.fullscreen", { ns: "common" })
|
|
||||||
}
|
|
||||||
onClick={toggleFullscreen}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!isIOS && !isFirefox && preferredLiveMode != "jsmpeg" && (
|
|
||||||
<CameraFeatureToggle
|
|
||||||
className="p-2 md:p-0"
|
|
||||||
variant={fullscreen ? "overlay" : "primary"}
|
|
||||||
Icon={LuPictureInPicture}
|
|
||||||
isActive={pip}
|
|
||||||
title={
|
|
||||||
pip
|
|
||||||
? t("button.close", { ns: "common" })
|
|
||||||
: t("button.pictureInPicture", { ns: "common" })
|
|
||||||
}
|
|
||||||
onClick={() => {
|
|
||||||
if (!pip) {
|
|
||||||
setPip(true);
|
|
||||||
} else {
|
|
||||||
document.exitPictureInPicture();
|
|
||||||
setPip(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
disabled={!cameraEnabled}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{supports2WayTalk && (
|
|
||||||
<CameraFeatureToggle
|
|
||||||
className="p-2 md:p-0"
|
|
||||||
variant={fullscreen ? "overlay" : "primary"}
|
|
||||||
Icon={mic ? FaMicrophone : FaMicrophoneSlash}
|
|
||||||
isActive={mic}
|
|
||||||
title={
|
|
||||||
mic
|
|
||||||
? t("twoWayTalk.disable", { ns: "views/live" })
|
|
||||||
: t("twoWayTalk.enable", { ns: "views/live" })
|
|
||||||
}
|
|
||||||
onClick={() => {
|
|
||||||
setMic(!mic);
|
|
||||||
if (!mic && !audio) {
|
|
||||||
setAudio(true);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
disabled={!cameraEnabled}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{supportsAudioOutput && preferredLiveMode != "jsmpeg" && (
|
|
||||||
<CameraFeatureToggle
|
|
||||||
className="p-2 md:p-0"
|
|
||||||
variant={fullscreen ? "overlay" : "primary"}
|
|
||||||
Icon={audio ? GiSpeaker : GiSpeakerOff}
|
|
||||||
isActive={audio ?? false}
|
|
||||||
title={
|
|
||||||
audio
|
|
||||||
? t("cameraAudio.disable", { ns: "views/live" })
|
|
||||||
: t("cameraAudio.enable", { ns: "views/live" })
|
|
||||||
}
|
|
||||||
onClick={() => setAudio(!audio)}
|
|
||||||
disabled={!cameraEnabled}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<FrigateCameraFeatures
|
|
||||||
camera={camera}
|
|
||||||
recordingEnabled={camera.record.enabled_in_config}
|
|
||||||
audioDetectEnabled={camera.audio.enabled_in_config}
|
|
||||||
autotrackingEnabled={
|
|
||||||
camera.onvif.autotracking.enabled_in_config
|
|
||||||
}
|
}
|
||||||
transcriptionEnabled={
|
onClick={toggleFullscreen}
|
||||||
camera.audio_transcription.enabled_in_config
|
|
||||||
}
|
|
||||||
fullscreen={fullscreen}
|
|
||||||
streamName={streamName ?? ""}
|
|
||||||
setStreamName={setStreamName}
|
|
||||||
preferredLiveMode={preferredLiveMode}
|
|
||||||
playInBackground={playInBackground ?? false}
|
|
||||||
setPlayInBackground={setPlayInBackground}
|
|
||||||
showStats={showStats}
|
|
||||||
setShowStats={setShowStats}
|
|
||||||
isRestreamed={isRestreamed ?? false}
|
|
||||||
setLowBandwidth={setLowBandwidth}
|
|
||||||
supportsAudioOutput={supportsAudioOutput}
|
|
||||||
supports2WayTalk={supports2WayTalk}
|
|
||||||
cameraEnabled={cameraEnabled}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
)}
|
||||||
</TooltipProvider>
|
{!isIOS && !isFirefox && preferredLiveMode != "jsmpeg" && (
|
||||||
|
<CameraFeatureToggle
|
||||||
|
className="p-2 md:p-0"
|
||||||
|
variant={fullscreen ? "overlay" : "primary"}
|
||||||
|
Icon={LuPictureInPicture}
|
||||||
|
isActive={pip}
|
||||||
|
title={
|
||||||
|
pip
|
||||||
|
? t("button.close", { ns: "common" })
|
||||||
|
: t("button.pictureInPicture", { ns: "common" })
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
if (!pip) {
|
||||||
|
setPip(true);
|
||||||
|
} else {
|
||||||
|
document.exitPictureInPicture();
|
||||||
|
setPip(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={!cameraEnabled}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{supports2WayTalk && (
|
||||||
|
<CameraFeatureToggle
|
||||||
|
className="p-2 md:p-0"
|
||||||
|
variant={fullscreen ? "overlay" : "primary"}
|
||||||
|
Icon={mic ? FaMicrophone : FaMicrophoneSlash}
|
||||||
|
isActive={mic}
|
||||||
|
title={
|
||||||
|
mic
|
||||||
|
? t("twoWayTalk.disable", { ns: "views/live" })
|
||||||
|
: t("twoWayTalk.enable", { ns: "views/live" })
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
setMic(!mic);
|
||||||
|
if (!mic && !audio) {
|
||||||
|
setAudio(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={!cameraEnabled}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{supportsAudioOutput && preferredLiveMode != "jsmpeg" && (
|
||||||
|
<CameraFeatureToggle
|
||||||
|
className="p-2 md:p-0"
|
||||||
|
variant={fullscreen ? "overlay" : "primary"}
|
||||||
|
Icon={audio ? GiSpeaker : GiSpeakerOff}
|
||||||
|
isActive={audio ?? false}
|
||||||
|
title={
|
||||||
|
audio
|
||||||
|
? t("cameraAudio.disable", { ns: "views/live" })
|
||||||
|
: t("cameraAudio.enable", { ns: "views/live" })
|
||||||
|
}
|
||||||
|
onClick={() => setAudio(!audio)}
|
||||||
|
disabled={!cameraEnabled}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<FrigateCameraFeatures
|
||||||
|
camera={camera}
|
||||||
|
recordingEnabled={camera.record.enabled_in_config}
|
||||||
|
audioDetectEnabled={camera.audio.enabled_in_config}
|
||||||
|
autotrackingEnabled={camera.onvif.autotracking.enabled_in_config}
|
||||||
|
transcriptionEnabled={
|
||||||
|
camera.audio_transcription.enabled_in_config
|
||||||
|
}
|
||||||
|
fullscreen={fullscreen}
|
||||||
|
streamName={streamName ?? ""}
|
||||||
|
setStreamName={setStreamName}
|
||||||
|
preferredLiveMode={preferredLiveMode}
|
||||||
|
playInBackground={playInBackground ?? false}
|
||||||
|
setPlayInBackground={setPlayInBackground}
|
||||||
|
showStats={showStats}
|
||||||
|
setShowStats={setShowStats}
|
||||||
|
isRestreamed={isRestreamed ?? false}
|
||||||
|
setLowBandwidth={setLowBandwidth}
|
||||||
|
supportsAudioOutput={supportsAudioOutput}
|
||||||
|
supports2WayTalk={supports2WayTalk}
|
||||||
|
cameraEnabled={cameraEnabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="player-container" className="size-full" ref={containerRef}>
|
<div id="player-container" className="size-full" ref={containerRef}>
|
||||||
<TransformComponent
|
<TransformComponent
|
||||||
@ -707,27 +703,25 @@ function TooltipButton({
|
|||||||
...props
|
...props
|
||||||
}: TooltipButtonProps) {
|
}: TooltipButtonProps) {
|
||||||
return (
|
return (
|
||||||
<TooltipProvider>
|
<Tooltip>
|
||||||
<Tooltip>
|
<TooltipTrigger asChild>
|
||||||
<TooltipTrigger asChild>
|
<Button
|
||||||
<Button
|
aria-label={label}
|
||||||
aria-label={label}
|
onClick={onClick}
|
||||||
onClick={onClick}
|
onMouseDown={onMouseDown}
|
||||||
onMouseDown={onMouseDown}
|
onMouseUp={onMouseUp}
|
||||||
onMouseUp={onMouseUp}
|
onTouchStart={onTouchStart}
|
||||||
onTouchStart={onTouchStart}
|
onTouchEnd={onTouchEnd}
|
||||||
onTouchEnd={onTouchEnd}
|
className={className}
|
||||||
className={className}
|
{...props}
|
||||||
{...props}
|
>
|
||||||
>
|
{children}
|
||||||
{children}
|
</Button>
|
||||||
</Button>
|
</TooltipTrigger>
|
||||||
</TooltipTrigger>
|
<TooltipContent>
|
||||||
<TooltipContent>
|
<p>{label}</p>
|
||||||
<p>{label}</p>
|
</TooltipContent>
|
||||||
</TooltipContent>
|
</Tooltip>
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -961,59 +955,56 @@ function PtzControlPanel({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{ptz?.features?.includes("pt-r-fov") && (
|
{ptz?.features?.includes("pt-r-fov") && (
|
||||||
<TooltipProvider>
|
<Tooltip>
|
||||||
<Tooltip>
|
<TooltipTrigger asChild>
|
||||||
<TooltipTrigger asChild>
|
<Button
|
||||||
<Button
|
className={`${clickOverlay ? "text-selected" : "text-primary"}`}
|
||||||
className={`${clickOverlay ? "text-selected" : "text-primary"}`}
|
aria-label={t("ptz.move.clickMove.label")}
|
||||||
aria-label={t("ptz.move.clickMove.label")}
|
onClick={() => setClickOverlay(!clickOverlay)}
|
||||||
onClick={() => setClickOverlay(!clickOverlay)}
|
>
|
||||||
>
|
<TbViewfinder />
|
||||||
<TbViewfinder />
|
</Button>
|
||||||
</Button>
|
</TooltipTrigger>
|
||||||
</TooltipTrigger>
|
<TooltipContent>
|
||||||
<TooltipContent>
|
<p>
|
||||||
<p>
|
{clickOverlay
|
||||||
{clickOverlay
|
? t("ptz.move.clickMove.disable")
|
||||||
? t("ptz.move.clickMove.disable")
|
: t("ptz.move.clickMove.enable")}
|
||||||
: t("ptz.move.clickMove.enable")}
|
</p>
|
||||||
</p>
|
</TooltipContent>
|
||||||
</TooltipContent>
|
</Tooltip>
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
)}
|
)}
|
||||||
{(ptz?.presets?.length ?? 0) > 0 && (
|
{(ptz?.presets?.length ?? 0) > 0 && (
|
||||||
<TooltipProvider>
|
<DropdownMenu modal={!isDesktop}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<DropdownMenu modal={!isDesktop}>
|
<DropdownMenuTrigger asChild>
|
||||||
<DropdownMenuTrigger asChild>
|
<Button aria-label={t("ptz.presets")}>
|
||||||
<Button aria-label={t("ptz.presets")}>
|
<BsThreeDotsVertical />
|
||||||
<BsThreeDotsVertical />
|
</Button>
|
||||||
</Button>
|
</DropdownMenuTrigger>
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent
|
|
||||||
className="scrollbar-container max-h-[40dvh] overflow-y-auto"
|
|
||||||
onCloseAutoFocus={(e) => e.preventDefault()}
|
|
||||||
>
|
|
||||||
{ptz?.presets.map((preset) => (
|
|
||||||
<DropdownMenuItem
|
|
||||||
key={preset}
|
|
||||||
aria-label={preset}
|
|
||||||
className="cursor-pointer"
|
|
||||||
onSelect={() => sendPtz(`preset_${preset}`)}
|
|
||||||
>
|
|
||||||
{preset}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
))}
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("ptz.presets")}</p>
|
<p>{t("ptz.presets")}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
|
||||||
|
<DropdownMenuContent
|
||||||
|
className="scrollbar-container max-h-[40dvh] overflow-y-auto"
|
||||||
|
onCloseAutoFocus={(e) => e.preventDefault()}
|
||||||
|
>
|
||||||
|
{ptz?.presets.map((preset) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
key={preset}
|
||||||
|
aria-label={preset}
|
||||||
|
className="cursor-pointer"
|
||||||
|
onSelect={() => sendPtz(`preset_${preset}`)}
|
||||||
|
>
|
||||||
|
{preset}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -1401,9 +1392,11 @@ function FrigateCameraFeatures({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-full">
|
<SelectTrigger className="w-full">
|
||||||
{Object.keys(camera.live.streams).find(
|
<SelectValue>
|
||||||
(key) => camera.live.streams[key] === streamName,
|
{Object.keys(camera.live.streams).find(
|
||||||
)}
|
(key) => camera.live.streams[key] === streamName,
|
||||||
|
)}
|
||||||
|
</SelectValue>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@ -1733,9 +1726,11 @@ function FrigateCameraFeatures({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-full">
|
<SelectTrigger className="w-full">
|
||||||
{Object.keys(camera.live.streams).find(
|
<SelectValue>
|
||||||
(key) => camera.live.streams[key] === streamName,
|
{Object.keys(camera.live.streams).find(
|
||||||
)}
|
(key) => camera.live.streams[key] === streamName,
|
||||||
|
)}
|
||||||
|
</SelectValue>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user