mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-07-26 13:47:03 +02:00
UI tweaks and bugfixes (#11692)
* UI tweaks and bugfixes * fix linter complaints in unmodified files
This commit is contained in:
parent
7031c47fb2
commit
1e80342c41
@ -2,6 +2,7 @@ import { ReviewSegment } from "@/types/review";
|
|||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { LuRefreshCcw } from "react-icons/lu";
|
import { LuRefreshCcw } from "react-icons/lu";
|
||||||
import { MutableRefObject, useMemo } from "react";
|
import { MutableRefObject, useMemo } from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
type NewReviewDataProps = {
|
type NewReviewDataProps = {
|
||||||
className: string;
|
className: string;
|
||||||
@ -29,11 +30,12 @@ export default function NewReviewData({
|
|||||||
<div className={className}>
|
<div className={className}>
|
||||||
<div className="pointer-events-auto mr-[65px] flex items-center justify-center md:mr-[115px]">
|
<div className="pointer-events-auto mr-[65px] flex items-center justify-center md:mr-[115px]">
|
||||||
<Button
|
<Button
|
||||||
className={`${
|
className={cn(
|
||||||
hasUpdate
|
hasUpdate
|
||||||
? "duration-500 animate-in slide-in-from-top"
|
? "duration-500 animate-in slide-in-from-top"
|
||||||
: "invisible"
|
: "invisible",
|
||||||
} mx-auto mt-5 bg-gray-400 text-center text-white`}
|
"mx-auto mt-5 bg-gray-400 text-center text-white",
|
||||||
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
pullLatestData();
|
pullLatestData();
|
||||||
if (contentRef.current) {
|
if (contentRef.current) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { LuX } from "react-icons/lu";
|
import { LuX } from "react-icons/lu";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { FaCompactDisc } from "react-icons/fa";
|
import { FaCompactDisc } from "react-icons/fa";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
type SaveExportOverlayProps = {
|
type SaveExportOverlayProps = {
|
||||||
className: string;
|
className: string;
|
||||||
@ -17,9 +18,11 @@ export default function SaveExportOverlay({
|
|||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<div
|
<div
|
||||||
className={`pointer-events-auto flex items-center justify-center gap-2 rounded-lg px-2 ${
|
className={cn(
|
||||||
show ? "duration-500 animate-in slide-in-from-top" : "invisible"
|
"pointer-events-auto flex items-center justify-center gap-2 rounded-lg px-2",
|
||||||
} mx-auto mt-5 text-center`}
|
show ? "duration-500 animate-in slide-in-from-top" : "invisible",
|
||||||
|
"mx-auto mt-5 text-center",
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center gap-1"
|
className="flex items-center gap-1"
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
import { FrigateConfig } from "@/types/frigateConfig";
|
||||||
|
import useSWR from "swr";
|
||||||
|
|
||||||
type MinimapSegmentProps = {
|
type MinimapSegmentProps = {
|
||||||
isFirstSegmentInMinimap: boolean;
|
isFirstSegmentInMinimap: boolean;
|
||||||
isLastSegmentInMinimap: boolean;
|
isLastSegmentInMinimap: boolean;
|
||||||
@ -28,6 +31,8 @@ export function MinimapBounds({
|
|||||||
firstMinimapSegmentRef,
|
firstMinimapSegmentRef,
|
||||||
dense,
|
dense,
|
||||||
}: MinimapSegmentProps) {
|
}: MinimapSegmentProps) {
|
||||||
|
const { data: config } = useSWR<FrigateConfig>("config");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isFirstSegmentInMinimap && (
|
{isFirstSegmentInMinimap && (
|
||||||
@ -36,6 +41,7 @@ export function MinimapBounds({
|
|||||||
ref={firstMinimapSegmentRef}
|
ref={firstMinimapSegmentRef}
|
||||||
>
|
>
|
||||||
{new Date(alignedMinimapStartTime * 1000).toLocaleTimeString([], {
|
{new Date(alignedMinimapStartTime * 1000).toLocaleTimeString([], {
|
||||||
|
hour12: config?.ui.time_format != "24hour",
|
||||||
hour: "2-digit",
|
hour: "2-digit",
|
||||||
minute: "2-digit",
|
minute: "2-digit",
|
||||||
...(!dense && { month: "short", day: "2-digit" }),
|
...(!dense && { month: "short", day: "2-digit" }),
|
||||||
@ -46,6 +52,7 @@ export function MinimapBounds({
|
|||||||
{isLastSegmentInMinimap && (
|
{isLastSegmentInMinimap && (
|
||||||
<div className="pointer-events-none absolute inset-0 -top-3 z-20 flex w-full select-none items-center justify-center text-center text-[10px] font-medium text-primary">
|
<div className="pointer-events-none absolute inset-0 -top-3 z-20 flex w-full select-none items-center justify-center text-center text-[10px] font-medium text-primary">
|
||||||
{new Date(alignedMinimapEndTime * 1000).toLocaleTimeString([], {
|
{new Date(alignedMinimapEndTime * 1000).toLocaleTimeString([], {
|
||||||
|
hour12: config?.ui.time_format != "24hour",
|
||||||
hour: "2-digit",
|
hour: "2-digit",
|
||||||
minute: "2-digit",
|
minute: "2-digit",
|
||||||
...(!dense && { month: "short", day: "2-digit" }),
|
...(!dense && { month: "short", day: "2-digit" }),
|
||||||
@ -83,6 +90,8 @@ export function Timestamp({
|
|||||||
timestampSpread,
|
timestampSpread,
|
||||||
segmentKey,
|
segmentKey,
|
||||||
}: TimestampSegmentProps) {
|
}: TimestampSegmentProps) {
|
||||||
|
const { data: config } = useSWR<FrigateConfig>("config");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="absolute left-[15px] z-10 h-[8px]">
|
<div className="absolute left-[15px] z-10 h-[8px]">
|
||||||
{!isFirstSegmentInMinimap && !isLastSegmentInMinimap && (
|
{!isFirstSegmentInMinimap && !isLastSegmentInMinimap && (
|
||||||
@ -93,6 +102,7 @@ export function Timestamp({
|
|||||||
{timestamp.getMinutes() % timestampSpread === 0 &&
|
{timestamp.getMinutes() % timestampSpread === 0 &&
|
||||||
timestamp.getSeconds() === 0 &&
|
timestamp.getSeconds() === 0 &&
|
||||||
timestamp.toLocaleTimeString([], {
|
timestamp.toLocaleTimeString([], {
|
||||||
|
hour12: config?.ui.time_format != "24hour",
|
||||||
hour: "2-digit",
|
hour: "2-digit",
|
||||||
minute: "2-digit",
|
minute: "2-digit",
|
||||||
})}
|
})}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { ReactNode, useCallback, useEffect, useMemo, useState } from "react";
|
import { ReactNode, useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import scrollIntoView from "scroll-into-view-if-needed";
|
import scrollIntoView from "scroll-into-view-if-needed";
|
||||||
import { useTimelineUtils } from "./use-timeline-utils";
|
import { useTimelineUtils } from "./use-timeline-utils";
|
||||||
|
import { FrigateConfig } from "@/types/frigateConfig";
|
||||||
|
import useSWR from "swr";
|
||||||
|
|
||||||
type DraggableElementProps = {
|
type DraggableElementProps = {
|
||||||
contentRef: React.RefObject<HTMLElement>;
|
contentRef: React.RefObject<HTMLElement>;
|
||||||
@ -49,6 +51,8 @@ function useDraggableElement({
|
|||||||
dense,
|
dense,
|
||||||
timelineSegments,
|
timelineSegments,
|
||||||
}: DraggableElementProps) {
|
}: DraggableElementProps) {
|
||||||
|
const { data: config } = useSWR<FrigateConfig>("config");
|
||||||
|
|
||||||
const [clientYPosition, setClientYPosition] = useState<number | null>(null);
|
const [clientYPosition, setClientYPosition] = useState<number | null>(null);
|
||||||
const [initialClickAdjustment, setInitialClickAdjustment] = useState(0);
|
const [initialClickAdjustment, setInitialClickAdjustment] = useState(0);
|
||||||
const [elementScrollIntoView, setElementScrollIntoView] = useState(true);
|
const [elementScrollIntoView, setElementScrollIntoView] = useState(true);
|
||||||
@ -170,6 +174,7 @@ function useDraggableElement({
|
|||||||
draggableElementTimeRef.current.textContent = new Date(
|
draggableElementTimeRef.current.textContent = new Date(
|
||||||
segmentStartTime * 1000,
|
segmentStartTime * 1000,
|
||||||
).toLocaleTimeString([], {
|
).toLocaleTimeString([], {
|
||||||
|
hour12: config?.ui.time_format != "24hour",
|
||||||
hour: "2-digit",
|
hour: "2-digit",
|
||||||
minute: "2-digit",
|
minute: "2-digit",
|
||||||
...(segmentDuration < 60 && !dense && { second: "2-digit" }),
|
...(segmentDuration < 60 && !dense && { second: "2-digit" }),
|
||||||
@ -196,6 +201,7 @@ function useDraggableElement({
|
|||||||
setDraggableElementTime,
|
setDraggableElementTime,
|
||||||
setDraggableElementPosition,
|
setDraggableElementPosition,
|
||||||
dense,
|
dense,
|
||||||
|
config,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ import {
|
|||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { isDesktop, isMobile } from "react-device-detect";
|
import { isDesktop, isMobile, isTablet } from "react-device-detect";
|
||||||
import { LuFolderCheck } from "react-icons/lu";
|
import { LuFolderCheck } from "react-icons/lu";
|
||||||
import { MdCircle } from "react-icons/md";
|
import { MdCircle } from "react-icons/md";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
@ -48,6 +48,7 @@ import { Skeleton } from "@/components/ui/skeleton";
|
|||||||
import scrollIntoView from "scroll-into-view-if-needed";
|
import scrollIntoView from "scroll-into-view-if-needed";
|
||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
type EventViewProps = {
|
type EventViewProps = {
|
||||||
reviewItems?: SegmentedReviewData;
|
reviewItems?: SegmentedReviewData;
|
||||||
@ -866,7 +867,12 @@ function MotionReview({
|
|||||||
<div className="no-scrollbar flex flex-1 flex-wrap content-start gap-2 overflow-y-auto md:gap-4">
|
<div className="no-scrollbar flex flex-1 flex-wrap content-start gap-2 overflow-y-auto md:gap-4">
|
||||||
<div
|
<div
|
||||||
ref={contentRef}
|
ref={contentRef}
|
||||||
className="no-scrollbar grid w-full gap-2 overflow-auto px-1 sm:grid-cols-2 md:mx-2 md:gap-4 xl:grid-cols-3 3xl:grid-cols-4"
|
className={cn(
|
||||||
|
"no-scrollbar grid w-full grid-cols-1",
|
||||||
|
(reviewCameras.length > 3 || isTablet || isDesktop) &&
|
||||||
|
"grid-cols-2",
|
||||||
|
"gap-2 overflow-auto px-1 md:mx-2 md:gap-4 xl:grid-cols-3 3xl:grid-cols-4",
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{reviewCameras.map((camera) => {
|
{reviewCameras.map((camera) => {
|
||||||
let grow;
|
let grow;
|
||||||
@ -874,13 +880,12 @@ function MotionReview({
|
|||||||
const aspectRatio = camera.detect.width / camera.detect.height;
|
const aspectRatio = camera.detect.width / camera.detect.height;
|
||||||
if (aspectRatio > 2) {
|
if (aspectRatio > 2) {
|
||||||
grow = "aspect-wide";
|
grow = "aspect-wide";
|
||||||
spans = "sm:col-span-2";
|
spans = reviewCameras.length > 3 && "col-span-2";
|
||||||
} else if (aspectRatio < 1) {
|
} else if (aspectRatio < 1) {
|
||||||
grow = "md:h-full aspect-tall";
|
grow = "md:h-full aspect-tall";
|
||||||
spans = "md:row-span-2";
|
spans = "row-span-2";
|
||||||
} else {
|
} else {
|
||||||
grow = "aspect-video";
|
grow = "aspect-video";
|
||||||
spans = "";
|
|
||||||
}
|
}
|
||||||
const detectionType = getDetectionType(camera.name);
|
const detectionType = getDetectionType(camera.name);
|
||||||
return (
|
return (
|
||||||
|
@ -243,9 +243,17 @@ export default function LiveDashboardView({
|
|||||||
>
|
>
|
||||||
{includeBirdseye && birdseyeConfig?.enabled && (
|
{includeBirdseye && birdseyeConfig?.enabled && (
|
||||||
<div
|
<div
|
||||||
style={{
|
className={(() => {
|
||||||
aspectRatio: birdseyeConfig.width / birdseyeConfig.height,
|
const aspectRatio =
|
||||||
}}
|
birdseyeConfig.width / birdseyeConfig.height;
|
||||||
|
if (aspectRatio > 2) {
|
||||||
|
return `${mobileLayout == "grid" && "col-span-2"} aspect-wide`;
|
||||||
|
} else if (aspectRatio < 1) {
|
||||||
|
return `${mobileLayout == "grid" && "row-span-2 md:h-full"} aspect-tall`;
|
||||||
|
} else {
|
||||||
|
return "aspect-video";
|
||||||
|
}
|
||||||
|
})()}
|
||||||
ref={birdseyeContainerRef}
|
ref={birdseyeContainerRef}
|
||||||
>
|
>
|
||||||
<BirdseyeLivePlayer
|
<BirdseyeLivePlayer
|
||||||
@ -260,9 +268,9 @@ export default function LiveDashboardView({
|
|||||||
let grow;
|
let grow;
|
||||||
const aspectRatio = camera.detect.width / camera.detect.height;
|
const aspectRatio = camera.detect.width / camera.detect.height;
|
||||||
if (aspectRatio > 2) {
|
if (aspectRatio > 2) {
|
||||||
grow = `${mobileLayout == "grid" ? "col-span-2" : ""} aspect-wide`;
|
grow = `${mobileLayout == "grid" && "col-span-2"} aspect-wide`;
|
||||||
} else if (aspectRatio < 1) {
|
} else if (aspectRatio < 1) {
|
||||||
grow = `${mobileLayout == "grid" ? "row-span-2 aspect-tall md:h-full" : ""} aspect-tall`;
|
grow = `${mobileLayout == "grid" && "row-span-2 md:h-full"} aspect-tall`;
|
||||||
} else {
|
} else {
|
||||||
grow = "aspect-video";
|
grow = "aspect-video";
|
||||||
}
|
}
|
||||||
|
@ -445,7 +445,7 @@ export default function GeneralMetrics({
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className=" mt-4 grid grid-cols-1 gap-2 sm:grid-cols-2">
|
<div className="mt-4 grid grid-cols-1 gap-2 sm:grid-cols-2">
|
||||||
{statsHistory.length != 0 ? (
|
{statsHistory.length != 0 ? (
|
||||||
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
|
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
|
||||||
<div className="mb-5">GPU Usage</div>
|
<div className="mb-5">GPU Usage</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user