mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-12 22:25:24 +03:00
only render when visible
This commit is contained in:
parent
753d05189a
commit
bc037bb577
@ -196,13 +196,16 @@ export function EventSegment({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [startTimestamp]);
|
}, [startTimestamp]);
|
||||||
|
|
||||||
const [segmentVisible, setSegmentVisible] = useState(false);
|
const [segmentRendered, setSegmentRendered] = useState(false);
|
||||||
const segmentObserverRef = useRef<IntersectionObserver | null>(null);
|
const segmentObserverRef = useRef<IntersectionObserver | null>(null);
|
||||||
const segmentRef = useRef<HTMLDivElement>(null);
|
const segmentRef = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const segmentObserver = new IntersectionObserver(
|
const segmentObserver = new IntersectionObserver(
|
||||||
([entry]) => {
|
([entry]) => {
|
||||||
setSegmentVisible(entry.isIntersecting);
|
if (entry.isIntersecting && !segmentRendered) {
|
||||||
|
setSegmentRendered(true);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ threshold: 0 },
|
{ threshold: 0 },
|
||||||
);
|
);
|
||||||
@ -218,7 +221,18 @@ export function EventSegment({
|
|||||||
segmentObserverRef.current.disconnect();
|
segmentObserverRef.current.disconnect();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, [segmentRendered]);
|
||||||
|
|
||||||
|
if (!segmentRendered) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={segmentKey}
|
||||||
|
ref={segmentRef}
|
||||||
|
data-segment-id={segmentKey}
|
||||||
|
className={`segment ${segmentClasses}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -229,67 +243,63 @@ export function EventSegment({
|
|||||||
onClick={segmentClick}
|
onClick={segmentClick}
|
||||||
onTouchEnd={(event) => handleTouchStart(event, segmentClick)}
|
onTouchEnd={(event) => handleTouchStart(event, segmentClick)}
|
||||||
>
|
>
|
||||||
{segmentVisible && (
|
{showMinimap && (
|
||||||
<>
|
<MinimapBounds
|
||||||
{showMinimap && (
|
isFirstSegmentInMinimap={isFirstSegmentInMinimap}
|
||||||
<MinimapBounds
|
isLastSegmentInMinimap={isLastSegmentInMinimap}
|
||||||
isFirstSegmentInMinimap={isFirstSegmentInMinimap}
|
alignedMinimapStartTime={alignedMinimapStartTime}
|
||||||
isLastSegmentInMinimap={isLastSegmentInMinimap}
|
alignedMinimapEndTime={alignedMinimapEndTime}
|
||||||
alignedMinimapStartTime={alignedMinimapStartTime}
|
firstMinimapSegmentRef={firstMinimapSegmentRef}
|
||||||
alignedMinimapEndTime={alignedMinimapEndTime}
|
dense={dense}
|
||||||
firstMinimapSegmentRef={firstMinimapSegmentRef}
|
/>
|
||||||
dense={dense}
|
)}
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Tick timestamp={timestamp} timestampSpread={timestampSpread} />
|
<Tick timestamp={timestamp} timestampSpread={timestampSpread} />
|
||||||
|
|
||||||
<Timestamp
|
<Timestamp
|
||||||
isFirstSegmentInMinimap={isFirstSegmentInMinimap}
|
isFirstSegmentInMinimap={isFirstSegmentInMinimap}
|
||||||
isLastSegmentInMinimap={isLastSegmentInMinimap}
|
isLastSegmentInMinimap={isLastSegmentInMinimap}
|
||||||
timestamp={timestamp}
|
timestamp={timestamp}
|
||||||
timestampSpread={timestampSpread}
|
timestampSpread={timestampSpread}
|
||||||
segmentKey={segmentKey}
|
segmentKey={segmentKey}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{severity.map((severityValue: number, index: number) => (
|
{severity.map((severityValue: number, index: number) => (
|
||||||
<React.Fragment key={index}>
|
<React.Fragment key={index}>
|
||||||
{severityValue === displaySeverityType && (
|
{severityValue === displaySeverityType && (
|
||||||
<HoverCard openDelay={200} closeDelay={100}>
|
<HoverCard openDelay={200} closeDelay={100}>
|
||||||
<HoverCardTrigger asChild>
|
<HoverCardTrigger asChild>
|
||||||
<div className="absolute left-1/2 z-10 h-[8px] w-[20px] -translate-x-1/2 transform cursor-pointer md:w-[40px]">
|
<div className="absolute left-1/2 z-10 h-[8px] w-[20px] -translate-x-1/2 transform cursor-pointer md:w-[40px]">
|
||||||
<div className="flex w-[20px] flex-row justify-center md:w-[40px]">
|
<div className="flex w-[20px] flex-row justify-center md:w-[40px]">
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<div
|
<div
|
||||||
className="absolute left-1/2 z-10 ml-[2px] h-[8px] w-[8px] -translate-x-1/2 transform cursor-pointer"
|
className="absolute left-1/2 z-10 ml-[2px] h-[8px] w-[8px] -translate-x-1/2 transform cursor-pointer"
|
||||||
data-severity={severityValue}
|
data-severity={severityValue}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
key={`${segmentKey}_${index}_primary_data`}
|
key={`${segmentKey}_${index}_primary_data`}
|
||||||
className={`h-[8px] w-full bg-gradient-to-r ${roundBottomPrimary ? "rounded-bl-full rounded-br-full" : ""} ${roundTopPrimary ? "rounded-tl-full rounded-tr-full" : ""} ${severityColors[severityValue]}`}
|
className={`h-[8px] w-full bg-gradient-to-r ${roundBottomPrimary ? "rounded-bl-full rounded-br-full" : ""} ${roundTopPrimary ? "rounded-tl-full rounded-tr-full" : ""} ${severityColors[severityValue]}`}
|
||||||
></div>
|
></div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</HoverCardTrigger>
|
</div>
|
||||||
<HoverCardPortal>
|
</div>
|
||||||
<HoverCardContent
|
</HoverCardTrigger>
|
||||||
className="w-[250px] rounded-lg p-2 md:rounded-2xl"
|
<HoverCardPortal>
|
||||||
side="left"
|
<HoverCardContent
|
||||||
>
|
className="w-[250px] rounded-lg p-2 md:rounded-2xl"
|
||||||
<img
|
side="left"
|
||||||
className="rounded-lg"
|
>
|
||||||
src={`${apiHost}${eventThumbnail.replace("/media/frigate/", "")}`}
|
<img
|
||||||
/>
|
className="rounded-lg"
|
||||||
</HoverCardContent>
|
src={`${apiHost}${eventThumbnail.replace("/media/frigate/", "")}`}
|
||||||
</HoverCardPortal>
|
/>
|
||||||
</HoverCard>
|
</HoverCardContent>
|
||||||
)}
|
</HoverCardPortal>
|
||||||
</React.Fragment>
|
</HoverCard>
|
||||||
))}
|
)}
|
||||||
</>
|
</React.Fragment>
|
||||||
)}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import { useEffect, useCallback, useMemo, useRef, RefObject } from "react";
|
import { useCallback, useMemo, useRef, RefObject } from "react";
|
||||||
import MotionSegment from "./MotionSegment";
|
import MotionSegment from "./MotionSegment";
|
||||||
import { useTimelineUtils } from "@/hooks/use-timeline-utils";
|
import { useTimelineUtils } from "@/hooks/use-timeline-utils";
|
||||||
import { MotionData, ReviewSegment, ReviewSeverity } from "@/types/review";
|
import { MotionData, ReviewSegment, ReviewSeverity } from "@/types/review";
|
||||||
import ReviewTimeline from "./ReviewTimeline";
|
import ReviewTimeline from "./ReviewTimeline";
|
||||||
import { isDesktop } from "react-device-detect";
|
|
||||||
import { useMotionSegmentUtils } from "@/hooks/use-motion-segment-utils";
|
import { useMotionSegmentUtils } from "@/hooks/use-motion-segment-utils";
|
||||||
|
|
||||||
export type MotionReviewTimelineProps = {
|
export type MotionReviewTimelineProps = {
|
||||||
@ -165,42 +164,6 @@ export function MotionReviewTimeline({
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const segmentsObserver = useRef<IntersectionObserver | null>(null);
|
|
||||||
useEffect(() => {
|
|
||||||
if (selectedTimelineRef.current && segments && isDesktop) {
|
|
||||||
segmentsObserver.current = new IntersectionObserver(
|
|
||||||
(entries) => {
|
|
||||||
entries.forEach((entry) => {
|
|
||||||
if (entry.isIntersecting) {
|
|
||||||
const segmentId = entry.target.getAttribute("data-segment-id");
|
|
||||||
|
|
||||||
const segmentElements =
|
|
||||||
internalTimelineRef.current?.querySelectorAll(
|
|
||||||
`[data-segment-id="${segmentId}"] .motion-segment`,
|
|
||||||
);
|
|
||||||
segmentElements?.forEach((segmentElement) => {
|
|
||||||
segmentElement.classList.remove("hidden");
|
|
||||||
segmentElement.classList.add("animate-in");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
{ threshold: 0 },
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get all segment divs and observe each one
|
|
||||||
const segmentDivs =
|
|
||||||
selectedTimelineRef.current.querySelectorAll(".segment.has-data");
|
|
||||||
segmentDivs.forEach((segmentDiv) => {
|
|
||||||
segmentsObserver.current?.observe(segmentDiv);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
segmentsObserver.current?.disconnect();
|
|
||||||
};
|
|
||||||
}, [selectedTimelineRef, segments]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReviewTimeline
|
<ReviewTimeline
|
||||||
timelineRef={selectedTimelineRef}
|
timelineRef={selectedTimelineRef}
|
||||||
|
|||||||
@ -1,11 +1,17 @@
|
|||||||
import { useTimelineUtils } from "@/hooks/use-timeline-utils";
|
import { useTimelineUtils } from "@/hooks/use-timeline-utils";
|
||||||
import { useEventSegmentUtils } from "@/hooks/use-event-segment-utils";
|
import { useEventSegmentUtils } from "@/hooks/use-event-segment-utils";
|
||||||
import { ReviewSegment } from "@/types/review";
|
import { ReviewSegment } from "@/types/review";
|
||||||
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
import scrollIntoView from "scroll-into-view-if-needed";
|
import scrollIntoView from "scroll-into-view-if-needed";
|
||||||
import { MinimapBounds, Tick, Timestamp } from "./segment-metadata";
|
import { MinimapBounds, Tick, Timestamp } from "./segment-metadata";
|
||||||
import { useMotionSegmentUtils } from "@/hooks/use-motion-segment-utils";
|
import { useMotionSegmentUtils } from "@/hooks/use-motion-segment-utils";
|
||||||
import { isDesktop, isMobile } from "react-device-detect";
|
import { isMobile } from "react-device-detect";
|
||||||
import useTapUtils from "@/hooks/use-tap-utils";
|
import useTapUtils from "@/hooks/use-tap-utils";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
@ -139,11 +145,6 @@ export function MotionSegment({
|
|||||||
: ""
|
: ""
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
const animationClassesSecondHalf = `motion-segment ${secondHalfSegmentWidth > 0 ? "hidden" : ""}
|
|
||||||
zoom-in-[0.2] ${secondHalfSegmentWidth < 5 ? "duration-200" : "duration-1000"}`;
|
|
||||||
const animationClassesFirstHalf = `motion-segment ${firstHalfSegmentWidth > 0 ? "hidden" : ""}
|
|
||||||
zoom-in-[0.2] ${firstHalfSegmentWidth < 5 ? "duration-200" : "duration-1000"}`;
|
|
||||||
|
|
||||||
const severityColorsBg: { [key: number]: string } = {
|
const severityColorsBg: { [key: number]: string } = {
|
||||||
1: reviewed
|
1: reviewed
|
||||||
? "from-severity_significant_motion-dimmed/10 to-severity_significant_motion/10"
|
? "from-severity_significant_motion-dimmed/10 to-severity_significant_motion/10"
|
||||||
@ -162,6 +163,44 @@ export function MotionSegment({
|
|||||||
}
|
}
|
||||||
}, [segmentTime, setHandlebarTime]);
|
}, [segmentTime, setHandlebarTime]);
|
||||||
|
|
||||||
|
const [segmentRendered, setSegmentRendered] = useState(false);
|
||||||
|
const segmentObserverRef = useRef<IntersectionObserver | null>(null);
|
||||||
|
const segmentRef = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const segmentObserver = new IntersectionObserver(
|
||||||
|
([entry]) => {
|
||||||
|
if (entry.isIntersecting && !segmentRendered) {
|
||||||
|
setSegmentRendered(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ threshold: 0 },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (segmentRef.current) {
|
||||||
|
segmentObserver.observe(segmentRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
segmentObserverRef.current = segmentObserver;
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (segmentObserverRef.current) {
|
||||||
|
segmentObserverRef.current.disconnect();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [segmentRendered]);
|
||||||
|
|
||||||
|
if (!segmentRendered) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={segmentKey}
|
||||||
|
ref={segmentRef}
|
||||||
|
data-segment-id={segmentKey}
|
||||||
|
className={`segment ${segmentClasses}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{(((firstHalfSegmentWidth > 0 || secondHalfSegmentWidth > 0) &&
|
{(((firstHalfSegmentWidth > 0 || secondHalfSegmentWidth > 0) &&
|
||||||
@ -171,6 +210,7 @@ export function MotionSegment({
|
|||||||
<div
|
<div
|
||||||
key={segmentKey}
|
key={segmentKey}
|
||||||
data-segment-id={segmentKey}
|
data-segment-id={segmentKey}
|
||||||
|
ref={segmentRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
"segment",
|
"segment",
|
||||||
{
|
{
|
||||||
@ -221,7 +261,6 @@ export function MotionSegment({
|
|||||||
key={`${segmentKey}_motion_data_1`}
|
key={`${segmentKey}_motion_data_1`}
|
||||||
data-motion-value={secondHalfSegmentWidth}
|
data-motion-value={secondHalfSegmentWidth}
|
||||||
className={cn(
|
className={cn(
|
||||||
isDesktop && animationClassesSecondHalf,
|
|
||||||
"h-[2px]",
|
"h-[2px]",
|
||||||
"rounded-full",
|
"rounded-full",
|
||||||
secondHalfSegmentWidth
|
secondHalfSegmentWidth
|
||||||
@ -241,7 +280,6 @@ export function MotionSegment({
|
|||||||
key={`${segmentKey}_motion_data_2`}
|
key={`${segmentKey}_motion_data_2`}
|
||||||
data-motion-value={firstHalfSegmentWidth}
|
data-motion-value={firstHalfSegmentWidth}
|
||||||
className={cn(
|
className={cn(
|
||||||
isDesktop && animationClassesFirstHalf,
|
|
||||||
"h-[2px]",
|
"h-[2px]",
|
||||||
"rounded-full",
|
"rounded-full",
|
||||||
firstHalfSegmentWidth
|
firstHalfSegmentWidth
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user