use flexbox for recording view

This commit is contained in:
Josh Hawkins 2025-10-25 17:05:57 -05:00
parent d890e0478e
commit 4717bf15ce

View File

@ -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 && (