stay in fullscreen when navigating to a camera (#11666)

This commit is contained in:
Josh Hawkins 2024-05-31 07:58:33 -05:00 committed by GitHub
parent 8c325801ef
commit a3d116e70e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 102 additions and 100 deletions

View File

@ -39,7 +39,7 @@ type HlsVideoPlayerProps = {
onPlaying?: () => void;
setFullResolution?: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
onUploadFrame?: (playTime: number) => Promise<AxiosResponse> | undefined;
setFullscreen?: (full: boolean) => void;
toggleFullscreen?: () => void;
};
export default function HlsVideoPlayer({
videoRef,
@ -53,7 +53,7 @@ export default function HlsVideoPlayer({
onPlaying,
setFullResolution,
onUploadFrame,
setFullscreen,
toggleFullscreen,
}: HlsVideoPlayerProps) {
const { data: config } = useSWR<FrigateConfig>("config");
@ -224,7 +224,7 @@ export default function HlsVideoPlayer({
}
}}
fullscreen={fullscreen}
setFullscreen={setFullscreen}
toggleFullscreen={toggleFullscreen}
/>
<TransformComponent
wrapperStyle={{

View File

@ -68,7 +68,7 @@ type VideoControlsProps = {
onSeek: (diff: number) => void;
onSetPlaybackRate: (rate: number) => void;
onUploadFrame?: () => void;
setFullscreen?: (full: boolean) => void;
toggleFullscreen?: () => void;
};
export default function VideoControls({
className,
@ -88,7 +88,7 @@ export default function VideoControls({
onSeek,
onSetPlaybackRate,
onUploadFrame,
setFullscreen,
toggleFullscreen,
}: VideoControlsProps) {
// layout
@ -160,8 +160,8 @@ export default function VideoControls({
}
break;
case "f":
if (setFullscreen && down && !repeat) {
setFullscreen(!fullscreen);
if (toggleFullscreen && down && !repeat) {
toggleFullscreen();
}
break;
case "m":
@ -178,7 +178,7 @@ export default function VideoControls({
},
// only update when preview only changes
// eslint-disable-next-line react-hooks/exhaustive-deps
[video, isPlaying, fullscreen, setFullscreen, onSeek],
[video, isPlaying, fullscreen, toggleFullscreen, onSeek],
);
useKeyboardListener(
hotKeys
@ -287,11 +287,8 @@ export default function VideoControls({
onUploadFrame={onUploadFrame}
/>
)}
{features.fullscreen && setFullscreen && (
<div
className="cursor-pointer"
onClick={() => setFullscreen(!fullscreen)}
>
{features.fullscreen && toggleFullscreen && (
<div className="cursor-pointer" onClick={toggleFullscreen}>
{fullscreen ? <FaCompress /> : <FaExpand />}
</div>
)}

View File

@ -29,7 +29,7 @@ type DynamicVideoPlayerProps = {
onTimestampUpdate?: (timestamp: number) => void;
onClipEnded?: () => void;
setFullResolution: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
setFullscreen: (full: boolean) => void;
toggleFullscreen: () => void;
};
export default function DynamicVideoPlayer({
className,
@ -44,7 +44,7 @@ export default function DynamicVideoPlayer({
onTimestampUpdate,
onClipEnded,
setFullResolution,
setFullscreen,
toggleFullscreen,
}: DynamicVideoPlayerProps) {
const apiHost = useApiHost();
const { data: config } = useSWR<FrigateConfig>("config");
@ -207,7 +207,7 @@ export default function DynamicVideoPlayer({
}}
setFullResolution={setFullResolution}
onUploadFrame={onUploadFrameToPlus}
setFullscreen={setFullscreen}
toggleFullscreen={toggleFullscreen}
/>
<PreviewPlayer
className={cn(

View File

@ -1,3 +1,4 @@
import { useFullscreen } from "@/hooks/use-fullscreen";
import {
useHashState,
usePersistedOverlayState,
@ -6,7 +7,7 @@ import { FrigateConfig } from "@/types/frigateConfig";
import LiveBirdseyeView from "@/views/live/LiveBirdseyeView";
import LiveCameraView from "@/views/live/LiveCameraView";
import LiveDashboardView from "@/views/live/LiveDashboardView";
import { useEffect, useMemo } from "react";
import { useEffect, useMemo, useRef } from "react";
import useSWR from "swr";
function Live() {
@ -20,6 +21,12 @@ function Live() {
"default" as string,
);
// fullscreen
const mainRef = useRef<HTMLDivElement | null>(null);
const { fullscreen, toggleFullscreen } = useFullscreen(mainRef);
// document title
useEffect(() => {
@ -78,21 +85,31 @@ function Live() {
[cameras, selectedCameraName],
);
if (selectedCameraName == "birdseye") {
return <LiveBirdseyeView />;
}
if (selectedCamera) {
return <LiveCameraView config={config} camera={selectedCamera} />;
}
return (
<div className="size-full" ref={mainRef}>
{selectedCameraName === "birdseye" ? (
<LiveBirdseyeView
fullscreen={fullscreen}
toggleFullscreen={toggleFullscreen}
/>
) : selectedCamera ? (
<LiveCameraView
config={config}
camera={selectedCamera}
fullscreen={fullscreen}
toggleFullscreen={toggleFullscreen}
/>
) : (
<LiveDashboardView
cameras={cameras}
cameraGroup={cameraGroup}
includeBirdseye={includesBirdseye}
onSelectCamera={setSelectedCameraName}
fullscreen={fullscreen}
toggleFullscreen={toggleFullscreen}
/>
)}
</div>
);
}

View File

@ -45,6 +45,7 @@ import { VideoResolutionType } from "@/types/live";
import { ASPECT_VERTICAL_LAYOUT, ASPECT_WIDE_LAYOUT } from "@/types/record";
import { useResizeObserver } from "@/hooks/resize-observer";
import { cn } from "@/lib/utils";
import { useFullscreen } from "@/hooks/use-fullscreen";
const SEGMENT_DURATION = 30;
@ -227,32 +228,7 @@ export function RecordingView({
// fullscreen
const [fullscreen, setFullscreen] = useState(false);
const onToggleFullscreen = useCallback(
(full: boolean) => {
if (full) {
mainLayoutRef.current?.requestFullscreen();
} else {
document.exitFullscreen();
}
},
[mainLayoutRef],
);
useEffect(() => {
if (mainLayoutRef.current == null) {
return;
}
const fsListener = () => {
setFullscreen(document.fullscreenElement != null);
};
document.addEventListener("fullscreenchange", fsListener);
return () => {
document.removeEventListener("fullscreenchange", fsListener);
};
}, [mainLayoutRef]);
const { fullscreen, toggleFullscreen } = useFullscreen(mainLayoutRef);
// layout
@ -535,7 +511,7 @@ export function RecordingView({
}}
isScrubbing={scrubbing || exportMode == "timeline"}
setFullResolution={setFullResolution}
setFullscreen={onToggleFullscreen}
toggleFullscreen={toggleFullscreen}
/>
</div>
{isDesktop && (

View File

@ -40,8 +40,6 @@ import {
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
import { useFullscreen } from "@/hooks/use-fullscreen";
import { toast } from "sonner";
import { Toaster } from "@/components/ui/sonner";
type DraggableGridLayoutProps = {
@ -55,6 +53,8 @@ type DraggableGridLayoutProps = {
visibleCameras: string[];
isEditMode: boolean;
setIsEditMode: React.Dispatch<React.SetStateAction<boolean>>;
fullscreen: boolean;
toggleFullscreen: () => void;
};
export default function DraggableGridLayout({
cameras,
@ -67,6 +67,8 @@ export default function DraggableGridLayout({
visibleCameras,
isEditMode,
setIsEditMode,
fullscreen,
toggleFullscreen,
}: DraggableGridLayoutProps) {
const { data: config } = useSWR<FrigateConfig>("config");
const birdseyeConfig = useMemo(() => config?.birdseye, [config]);
@ -289,20 +291,6 @@ export default function DraggableGridLayout({
}
}, [containerRef, containerHeight]);
// fullscreen state
const { fullscreen, toggleFullscreen, error, clearError } =
useFullscreen(gridContainerRef);
useEffect(() => {
if (error !== null) {
toast.error(`Error attempting fullscreen mode: ${error}`, {
position: "top-center",
});
clearError();
}
}, [error, clearError]);
const cellHeight = useMemo(() => {
const aspectRatio = 16 / 9;
// subtract container margin, 1 camera takes up at least 4 rows
@ -463,6 +451,7 @@ export default function DraggableGridLayout({
</Tooltip>
{!isEditMode && (
<>
{!fullscreen && (
<Tooltip>
<TooltipTrigger asChild>
<div
@ -478,6 +467,7 @@ export default function DraggableGridLayout({
{isEditMode ? "Exit Editing" : "Edit Camera Group"}
</TooltipContent>
</Tooltip>
)}
<Tooltip>
<TooltipTrigger asChild>
<div

View File

@ -4,7 +4,6 @@ import BirdseyeLivePlayer from "@/components/player/BirdseyeLivePlayer";
import { Button } from "@/components/ui/button";
import { TooltipProvider } from "@/components/ui/tooltip";
import { useResizeObserver } from "@/hooks/resize-observer";
import { useFullscreen } from "@/hooks/use-fullscreen";
import { FrigateConfig } from "@/types/frigateConfig";
import { useMemo, useRef } from "react";
import {
@ -19,7 +18,15 @@ import { useNavigate } from "react-router-dom";
import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
import useSWR from "swr";
export default function LiveBirdseyeView() {
type LiveBirdseyeViewProps = {
fullscreen: boolean;
toggleFullscreen: () => void;
};
export default function LiveBirdseyeView({
fullscreen,
toggleFullscreen,
}: LiveBirdseyeViewProps) {
const { data: config } = useSWR<FrigateConfig>("config");
const navigate = useNavigate();
const { isPortrait } = useMobileOrientation();
@ -28,10 +35,6 @@ export default function LiveBirdseyeView() {
const [{ width: windowWidth, height: windowHeight }] =
useResizeObserver(window);
// fullscreen state
const { fullscreen, toggleFullscreen } = useFullscreen(mainRef);
// playback state
const cameraAspectRatio = useMemo(() => {

View File

@ -72,15 +72,18 @@ import {
import { useNavigate } from "react-router-dom";
import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
import useSWR from "swr";
import { useFullscreen } from "@/hooks/use-fullscreen";
type LiveCameraViewProps = {
config?: FrigateConfig;
camera: CameraConfig;
fullscreen: boolean;
toggleFullscreen: () => void;
};
export default function LiveCameraView({
config,
camera,
fullscreen,
toggleFullscreen,
}: LiveCameraViewProps) {
const navigate = useNavigate();
const { isPortrait } = useMobileOrientation();
@ -175,9 +178,7 @@ export default function LiveCameraView({
[clickOverlayRef, clickOverlay, sendPtz],
);
// fullscreen / pip state
const { fullscreen, toggleFullscreen } = useFullscreen(mainRef);
// pip state
useEffect(() => {
setPip(document.pictureInPictureElement != null);
@ -314,6 +315,18 @@ export default function LiveCameraView({
<div
className={`flex flex-row items-center gap-2 *:rounded-lg ${isMobile ? "landscape:flex-col" : ""}`}
>
{fullscreen && (
<Button
className="bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500 text-primary"
size="sm"
onClick={() => navigate(-1)}
>
<IoMdArrowRoundBack className="size-5 text-secondary-foreground" />
{isDesktop && (
<div className="text-secondary-foreground">Back</div>
)}
</Button>
)}
{!isIOS && (
<CameraFeatureToggle
className="p-2 md:p-0"

View File

@ -30,12 +30,16 @@ type LiveDashboardViewProps = {
cameraGroup?: string;
includeBirdseye: boolean;
onSelectCamera: (camera: string) => void;
fullscreen: boolean;
toggleFullscreen: () => void;
};
export default function LiveDashboardView({
cameras,
cameraGroup,
includeBirdseye,
onSelectCamera,
fullscreen,
toggleFullscreen,
}: LiveDashboardViewProps) {
const { data: config } = useSWR<FrigateConfig>("config");
@ -214,7 +218,7 @@ export default function LiveDashboardView({
</div>
)}
{events && events.length > 0 && (
{!fullscreen && events && events.length > 0 && (
<ScrollArea>
<TooltipProvider>
<div className="flex items-center gap-2 px-1">
@ -281,6 +285,8 @@ export default function LiveDashboardView({
visibleCameras={visibleCameras}
isEditMode={isEditMode}
setIsEditMode={setIsEditMode}
fullscreen={fullscreen}
toggleFullscreen={toggleFullscreen}
/>
)}
</div>