mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-12 01:57:36 +03:00
use flexbox for recording view
This commit is contained in:
parent
d890e0478e
commit
4717bf15ce
@ -11,6 +11,7 @@ import DetailStream from "@/components/timeline/DetailStream";
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
||||||
import { useOverlayState } from "@/hooks/use-overlay-state";
|
import { useOverlayState } from "@/hooks/use-overlay-state";
|
||||||
|
import { useResizeObserver } from "@/hooks/resize-observer";
|
||||||
import { ExportMode } from "@/types/filter";
|
import { ExportMode } from "@/types/filter";
|
||||||
import { FrigateConfig } from "@/types/frigateConfig";
|
import { FrigateConfig } from "@/types/frigateConfig";
|
||||||
import { Preview } from "@/types/preview";
|
import { Preview } from "@/types/preview";
|
||||||
@ -31,12 +32,7 @@ import {
|
|||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import {
|
import { isDesktop, isMobile } from "react-device-detect";
|
||||||
isDesktop,
|
|
||||||
isMobile,
|
|
||||||
isMobileOnly,
|
|
||||||
isTablet,
|
|
||||||
} from "react-device-detect";
|
|
||||||
import { IoMdArrowRoundBack } from "react-icons/io";
|
import { IoMdArrowRoundBack } from "react-icons/io";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
@ -55,7 +51,6 @@ import {
|
|||||||
RecordingSegment,
|
RecordingSegment,
|
||||||
RecordingStartingPoint,
|
RecordingStartingPoint,
|
||||||
} from "@/types/record";
|
} from "@/types/record";
|
||||||
import { useResizeObserver } from "@/hooks/resize-observer";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { useFullscreen } from "@/hooks/use-fullscreen";
|
import { useFullscreen } from "@/hooks/use-fullscreen";
|
||||||
import { useTimezone } from "@/hooks/use-date-utils";
|
import { useTimezone } from "@/hooks/use-date-utils";
|
||||||
@ -399,49 +394,47 @@ export function RecordingView({
|
|||||||
}
|
}
|
||||||
}, [mainCameraAspect]);
|
}, [mainCameraAspect]);
|
||||||
|
|
||||||
const [{ width: mainWidth, height: mainHeight }] =
|
// use a resize observer to determine whether to use w-full or h-full based on container aspect ratio
|
||||||
|
const [{ width: containerWidth, height: containerHeight }] =
|
||||||
useResizeObserver(cameraLayoutRef);
|
useResizeObserver(cameraLayoutRef);
|
||||||
|
const [{ width: previewRowWidth, height: previewRowHeight }] =
|
||||||
|
useResizeObserver(previewRowRef);
|
||||||
|
|
||||||
const mainCameraStyle = useMemo(() => {
|
const useHeightBased = useMemo(() => {
|
||||||
if (isMobile || mainCameraAspect != "normal" || !config) {
|
if (!containerWidth || !containerHeight) {
|
||||||
return undefined;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const camera = config.cameras[mainCamera];
|
const cameraAspectRatio = getCameraAspect(mainCamera);
|
||||||
|
if (!cameraAspectRatio) {
|
||||||
if (!camera) {
|
return false;
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const aspect = getCameraAspect(mainCamera);
|
// Calculate available space for camera after accounting for preview row
|
||||||
|
// For tall cameras: preview row is side-by-side (takes width)
|
||||||
|
// For wide/normal cameras: preview row is stacked (takes height)
|
||||||
|
const availableWidth =
|
||||||
|
mainCameraAspect == "tall" && previewRowWidth
|
||||||
|
? containerWidth - previewRowWidth
|
||||||
|
: containerWidth;
|
||||||
|
const availableHeight =
|
||||||
|
mainCameraAspect != "tall" && previewRowHeight
|
||||||
|
? containerHeight - previewRowHeight
|
||||||
|
: containerHeight;
|
||||||
|
|
||||||
if (!aspect) {
|
const availableAspectRatio = availableWidth / availableHeight;
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const availableHeight = mainHeight - 112;
|
// If available space is wider than camera aspect, constrain by height (h-full)
|
||||||
|
// If available space is taller than camera aspect, constrain by width (w-full)
|
||||||
let percent;
|
return availableAspectRatio >= cameraAspectRatio;
|
||||||
if (mainWidth / availableHeight < aspect) {
|
|
||||||
percent = 100;
|
|
||||||
} else {
|
|
||||||
const availableWidth = aspect * availableHeight;
|
|
||||||
percent =
|
|
||||||
(mainWidth < availableWidth
|
|
||||||
? mainWidth / availableWidth
|
|
||||||
: availableWidth / mainWidth) * 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
width: `${Math.round(percent)}%`,
|
|
||||||
};
|
|
||||||
}, [
|
}, [
|
||||||
config,
|
containerWidth,
|
||||||
mainCameraAspect,
|
containerHeight,
|
||||||
mainWidth,
|
previewRowWidth,
|
||||||
mainHeight,
|
previewRowHeight,
|
||||||
mainCamera,
|
|
||||||
getCameraAspect,
|
getCameraAspect,
|
||||||
|
mainCamera,
|
||||||
|
mainCameraAspect,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const previewRowOverflows = useMemo(() => {
|
const previewRowOverflows = useMemo(() => {
|
||||||
@ -685,19 +678,17 @@ export function RecordingView({
|
|||||||
<div
|
<div
|
||||||
ref={mainLayoutRef}
|
ref={mainLayoutRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-full justify-center overflow-hidden",
|
"flex flex-1 overflow-hidden",
|
||||||
isDesktop ? "" : "flex-col gap-2 landscape:flex-row",
|
isDesktop ? "flex-row" : "flex-col gap-2 landscape:flex-row",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
ref={cameraLayoutRef}
|
ref={cameraLayoutRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-1 flex-wrap",
|
"flex flex-1 flex-wrap overflow-hidden",
|
||||||
isDesktop
|
isDesktop
|
||||||
? timelineType === "detail"
|
? "min-w-0 px-4"
|
||||||
? "md:w-[40%] lg:w-[70%] xl:w-full"
|
: "portrait:max-h-[50dvh] portrait:flex-shrink-0 portrait:flex-grow-0 portrait:basis-auto",
|
||||||
: "w-[80%]"
|
|
||||||
: "",
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@ -711,37 +702,25 @@ export function RecordingView({
|
|||||||
<div
|
<div
|
||||||
key={mainCamera}
|
key={mainCamera}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative",
|
"relative flex max-h-full min-h-0 min-w-0 max-w-full items-center justify-center",
|
||||||
isDesktop
|
isDesktop
|
||||||
? cn(
|
? // Desktop: dynamically switch between w-full and h-full based on
|
||||||
"flex justify-center px-4",
|
// container vs camera aspect ratio to ensure proper fitting
|
||||||
mainCameraAspect == "tall"
|
useHeightBased
|
||||||
? "h-[50%] md:h-[60%] lg:h-[75%] xl:h-[90%]"
|
? "h-full"
|
||||||
: mainCameraAspect == "wide"
|
: "w-full"
|
||||||
? "w-full"
|
|
||||||
: "",
|
|
||||||
)
|
|
||||||
: cn(
|
: cn(
|
||||||
"pt-2 portrait:w-full",
|
"flex-shrink-0 pt-2",
|
||||||
isMobileOnly &&
|
mainCameraAspect == "wide"
|
||||||
(mainCameraAspect == "wide"
|
? "aspect-wide"
|
||||||
? "aspect-wide landscape:w-full"
|
: mainCameraAspect == "tall"
|
||||||
: "aspect-video landscape:h-[94%] landscape:xl:h-[65%]"),
|
? "aspect-tall"
|
||||||
isTablet &&
|
: "aspect-video",
|
||||||
(mainCameraAspect == "wide"
|
"portrait:w-full landscape:h-full",
|
||||||
? "aspect-wide landscape:w-full"
|
|
||||||
: mainCameraAspect == "normal"
|
|
||||||
? "landscape:w-full"
|
|
||||||
: "aspect-video landscape:h-[100%]"),
|
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
width: mainCameraStyle ? mainCameraStyle.width : undefined,
|
aspectRatio: getCameraAspect(mainCamera),
|
||||||
aspectRatio: isDesktop
|
|
||||||
? mainCameraAspect == "tall"
|
|
||||||
? getCameraAspect(mainCamera)
|
|
||||||
: undefined
|
|
||||||
: Math.max(1, getCameraAspect(mainCamera) ?? 0),
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isDesktop && (
|
{isDesktop && (
|
||||||
@ -782,10 +761,10 @@ export function RecordingView({
|
|||||||
<div
|
<div
|
||||||
ref={previewRowRef}
|
ref={previewRowRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
"scrollbar-container flex gap-2 overflow-auto",
|
"scrollbar-container flex flex-shrink-0 gap-2 overflow-auto",
|
||||||
mainCameraAspect == "tall"
|
mainCameraAspect == "tall"
|
||||||
? "h-full w-72 flex-col"
|
? "ml-2 h-full w-72 min-w-72 flex-col"
|
||||||
: `h-28 w-full`,
|
: "h-28 min-h-28 w-full",
|
||||||
previewRowOverflows ? "" : "items-center justify-center",
|
previewRowOverflows ? "" : "items-center justify-center",
|
||||||
timelineType == "detail" && isDesktop && "mt-4",
|
timelineType == "detail" && isDesktop && "mt-4",
|
||||||
)}
|
)}
|
||||||
@ -971,10 +950,23 @@ function Timeline({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative",
|
"relative overflow-hidden",
|
||||||
isDesktop
|
isDesktop
|
||||||
? `${timelineType == "timeline" ? "w-[100px]" : timelineType == "detail" ? "w-[30%] min-w-[350px]" : "w-60"} no-scrollbar overflow-y-auto`
|
? cn(
|
||||||
: `overflow-hidden portrait:flex-grow ${timelineType == "timeline" ? "landscape:w-[100px]" : timelineType == "detail" && isDesktop ? "flex-1" : "landscape:w-[300px]"} `,
|
"no-scrollbar overflow-y-auto",
|
||||||
|
timelineType == "timeline"
|
||||||
|
? "w-[100px] flex-shrink-0"
|
||||||
|
: timelineType == "detail"
|
||||||
|
? "min-w-[20rem] max-w-[30%] flex-shrink-0 flex-grow-0 basis-[30rem] md:min-w-[20rem] md:max-w-[25%] lg:min-w-[30rem] lg:max-w-[33%]"
|
||||||
|
: "w-60 flex-shrink-0",
|
||||||
|
)
|
||||||
|
: cn(
|
||||||
|
timelineType == "timeline"
|
||||||
|
? "portrait:flex-grow landscape:w-[100px] landscape:flex-shrink-0"
|
||||||
|
: timelineType == "detail"
|
||||||
|
? "portrait:flex-grow landscape:w-[19rem] landscape:flex-shrink-0"
|
||||||
|
: "portrait:flex-grow landscape:w-[19rem] landscape:flex-shrink-0",
|
||||||
|
),
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user