* scroll minimap to keep it in view

* remove console log

* change ref

* rebase to dev

* rebase to dev

* rebase to dev

* fix history flexbox and live extra scrollbar

* remove extra class
This commit is contained in:
Josh Hawkins 2024-02-22 21:15:50 -06:00 committed by GitHub
parent f84d2db406
commit a6aa5328aa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 204 additions and 139 deletions

View File

@ -1,7 +1,7 @@
import { useEventUtils } from "@/hooks/use-event-utils"; import { useEventUtils } from "@/hooks/use-event-utils";
import { useSegmentUtils } from "@/hooks/use-segment-utils"; import { useSegmentUtils } from "@/hooks/use-segment-utils";
import { ReviewSegment, ReviewSeverity } from "@/types/review"; import { ReviewSegment, ReviewSeverity } from "@/types/review";
import React, { useMemo } from "react"; import React, { useEffect, useMemo, useRef } from "react";
type EventSegmentProps = { type EventSegmentProps = {
events: ReviewSegment[]; events: ReviewSegment[];
@ -19,6 +19,7 @@ type MinimapSegmentProps = {
isLastSegmentInMinimap: boolean; isLastSegmentInMinimap: boolean;
alignedMinimapStartTime: number; alignedMinimapStartTime: number;
alignedMinimapEndTime: number; alignedMinimapEndTime: number;
firstMinimapSegmentRef: React.MutableRefObject<HTMLDivElement | null>;
}; };
type TickSegmentProps = { type TickSegmentProps = {
@ -41,11 +42,15 @@ function MinimapBounds({
isLastSegmentInMinimap, isLastSegmentInMinimap,
alignedMinimapStartTime, alignedMinimapStartTime,
alignedMinimapEndTime, alignedMinimapEndTime,
firstMinimapSegmentRef,
}: MinimapSegmentProps) { }: MinimapSegmentProps) {
return ( return (
<> <>
{isFirstSegmentInMinimap && ( {isFirstSegmentInMinimap && (
<div className="absolute inset-0 -bottom-5 w-full flex items-center justify-center text-xs text-primary font-medium z-20 text-center text-[8px]"> <div
className="absolute inset-0 -bottom-5 w-full flex items-center justify-center text-xs text-primary font-medium z-20 text-center text-[8px] scroll-mt-8"
ref={firstMinimapSegmentRef}
>
{new Date(alignedMinimapStartTime * 1000).toLocaleTimeString([], { {new Date(alignedMinimapStartTime * 1000).toLocaleTimeString([], {
hour: "2-digit", hour: "2-digit",
minute: "2-digit", minute: "2-digit",
@ -179,6 +184,19 @@ export function EventSegment({
return showMinimap && segmentTime === alignedMinimapEndTime; return showMinimap && segmentTime === alignedMinimapEndTime;
}, [showMinimap, segmentTime, alignedMinimapEndTime]); }, [showMinimap, segmentTime, alignedMinimapEndTime]);
const firstMinimapSegmentRef = useRef<HTMLDivElement>(null);
useEffect(() => {
// Check if the first segment is out of view
const firstSegment = firstMinimapSegmentRef.current;
if (firstSegment && showMinimap && isFirstSegmentInMinimap) {
firstSegment.scrollIntoView({
behavior: "smooth",
block: "center",
});
}
}, [showMinimap, isFirstSegmentInMinimap, events, segmentDuration]);
const segmentClasses = `flex flex-row ${ const segmentClasses = `flex flex-row ${
showMinimap showMinimap
? isInMinimapRange ? isInMinimapRange
@ -212,6 +230,7 @@ export function EventSegment({
isLastSegmentInMinimap={isLastSegmentInMinimap} isLastSegmentInMinimap={isLastSegmentInMinimap}
alignedMinimapStartTime={alignedMinimapStartTime} alignedMinimapStartTime={alignedMinimapStartTime}
alignedMinimapEndTime={alignedMinimapEndTime} alignedMinimapEndTime={alignedMinimapEndTime}
firstMinimapSegmentRef={firstMinimapSegmentRef}
/> />
<Tick <Tick

View File

@ -1,4 +1,4 @@
import { useCallback } from "react"; import { useCallback, useEffect } from "react";
interface DragHandlerProps { interface DragHandlerProps {
contentRef: React.RefObject<HTMLElement>; contentRef: React.RefObject<HTMLElement>;
@ -128,6 +128,17 @@ function useDraggableHandler({
] ]
); );
useEffect(() => {
// TODO: determine when we want to do this
const handlebar = scrollTimeRef.current;
if (handlebar && showHandlebar) {
// handlebar.scrollIntoView({
// behavior: "smooth",
// block: "center",
// });
}
}, []);
return { handleMouseDown, handleMouseUp, handleMouseMove }; return { handleMouseDown, handleMouseUp, handleMouseMove };
} }

View File

@ -79,7 +79,7 @@ function Live() {
}, []); }, []);
return ( return (
<div className="w-full h-full overflow-scroll px-2"> <div className="w-full h-full overflow-y-scroll px-2">
{events && events.length > 0 && ( {events && events.length > 0 && (
<ScrollArea> <ScrollArea>
<TooltipProvider> <TooltipProvider>

View File

@ -61,7 +61,8 @@ function eventsToScrubberItems(events: Event[]): ScrubberItem[] {
} }
const generateRandomEvent = (): ReviewSegment => { const generateRandomEvent = (): ReviewSegment => {
const start_time = Math.floor(Date.now() / 1000) - Math.random() * 60 * 60; const start_time =
Math.floor(Date.now() / 1000) - 10800 - Math.random() * 60 * 60;
const end_time = Math.floor(start_time + Math.random() * 60 * 10); const end_time = Math.floor(start_time + Math.random() * 60 * 10);
const severities: ReviewSeverity[] = [ const severities: ReviewSeverity[] = [
"significant_motion", "significant_motion",
@ -123,6 +124,23 @@ function UIPlayground() {
setMockEvents(initialEvents); setMockEvents(initialEvents);
}, []); }, []);
// Calculate minimap start and end times based on events
const minimapStartTime = useMemo(() => {
if (mockEvents && mockEvents.length > 0) {
return Math.min(...mockEvents.map((event) => event.start_time));
}
return Math.floor(Date.now() / 1000); // Default to current time if no events
}, [events]);
const minimapEndTime = useMemo(() => {
if (mockEvents && mockEvents.length > 0) {
return Math.max(
...mockEvents.map((event) => event.end_time ?? event.start_time)
);
}
return Math.floor(Date.now() / 1000); // Default to current time if no events
}, [events]);
const [zoomLevel, setZoomLevel] = useState(0); const [zoomLevel, setZoomLevel] = useState(0);
const [zoomSettings, setZoomSettings] = useState({ const [zoomSettings, setZoomSettings] = useState({
segmentDuration: 60, segmentDuration: 60,
@ -150,8 +168,17 @@ function UIPlayground() {
setZoomSettings(possibleZoomLevels[nextZoomLevel]); setZoomSettings(possibleZoomLevels[nextZoomLevel]);
} }
const [isDragging, setIsDragging] = useState(false);
const handleDraggingChange = (dragging: boolean) => {
setIsDragging(dragging);
};
return ( return (
<> <>
<div className="w-full h-full">
<div className="flex h-full">
<div className="flex-1 content-start gap-2 overflow-y-auto no-scrollbar mt-4 mr-5">
<Heading as="h2">UI Playground</Heading> <Heading as="h2">UI Playground</Heading>
<Heading as="h4" className="my-5"> <Heading as="h4" className="my-5">
@ -186,13 +213,14 @@ function UIPlayground() {
</div> </div>
)} )}
<div className="flex">
<div className="flex-grow">
<div ref={contentRef}> <div ref={contentRef}>
<Heading as="h4" className="my-5"> <Heading as="h4" className="my-5">
Timeline Timeline
</Heading> </Heading>
<p className="text-small">Handlebar timestamp: {handlebarTime}</p> <p className="text-small">Handlebar timestamp: {handlebarTime}</p>
<p className="text-small">
Handlebar is dragging: {isDragging ? "yes" : "no"}
</p>
<p> <p>
<Button onClick={handleZoomOut} disabled={zoomLevel === 0}> <Button onClick={handleZoomOut} disabled={zoomLevel === 0}>
Zoom Out Zoom Out
@ -229,7 +257,8 @@ function UIPlayground() {
</div> </div>
</div> </div>
</div> </div>
<div className="flex-none">
<div className="w-[100px] overflow-y-auto no-scrollbar">
<EventReviewTimeline <EventReviewTimeline
segmentDuration={zoomSettings.segmentDuration} // seconds per segment segmentDuration={zoomSettings.segmentDuration} // seconds per segment
timestampSpread={zoomSettings.timestampSpread} // minutes between each major timestamp timestampSpread={zoomSettings.timestampSpread} // minutes between each major timestamp
@ -238,15 +267,17 @@ function UIPlayground() {
showHandlebar // show / hide the handlebar showHandlebar // show / hide the handlebar
handlebarTime={handlebarTime} // set the time of the handlebar handlebarTime={handlebarTime} // set the time of the handlebar
setHandlebarTime={setHandlebarTime} // expose handler to set the handlebar time setHandlebarTime={setHandlebarTime} // expose handler to set the handlebar time
onHandlebarDraggingChange={handleDraggingChange} // function for state of handlebar dragging
showMinimap // show / hide the minimap showMinimap // show / hide the minimap
minimapStartTime={Math.floor(Date.now() / 1000) - 35 * 60} // start time of the minimap - the earlier time (eg 1:00pm) minimapStartTime={minimapStartTime} // start time of the minimap - the earlier time (eg 1:00pm)
minimapEndTime={Math.floor(Date.now() / 1000) - 21 * 60} // end of the minimap - the later time (eg 3:00pm) minimapEndTime={minimapEndTime} // end of the minimap - the later time (eg 3:00pm)
events={mockEvents} // events, including new has_been_reviewed and severity properties events={mockEvents} // events, including new has_been_reviewed and severity properties
severityType={"alert"} // choose the severity type for the middle line - all other severity types are to the right severityType={"alert"} // choose the severity type for the middle line - all other severity types are to the right
contentRef={contentRef} // optional content ref where previews are, can be used for observing/scrolling later contentRef={contentRef} // optional content ref where previews are, can be used for observing/scrolling later
/> />
</div> </div>
</div> </div>
</div>
</> </>
); );
} }

View File

@ -195,8 +195,8 @@ export default function DesktopEventView({
} }
return ( return (
<div className="relative w-full h-full"> <div className="flex flex-col w-full h-full">
<div className="absolute flex justify-between left-0 top-0 right-0"> <div className="flex justify-between mb-2">
<ToggleGroup <ToggleGroup
type="single" type="single"
defaultValue="alert" defaultValue="alert"
@ -261,14 +261,17 @@ export default function DesktopEventView({
</Button> </Button>
)} )}
<div className="flex h-full overflow-hidden">
<div <div
ref={contentRef} ref={contentRef}
className="absolute left-0 top-12 bottom-0 right-28 flex flex-wrap content-start gap-2 overflow-y-auto no-scrollbar" className="flex flex-1 flex-wrap content-start gap-2 overflow-y-auto no-scrollbar"
> >
{currentItems ? ( {currentItems ? (
currentItems.map((value, segIdx) => { currentItems.map((value, segIdx) => {
const lastRow = segIdx == reviewItems[severity].length - 1; const lastRow = segIdx == reviewItems[severity].length - 1;
const relevantPreview = Object.values(relevantPreviews || []).find( const relevantPreview = Object.values(
relevantPreviews || []
).find(
(preview) => (preview) =>
preview.camera == value.camera && preview.camera == value.camera &&
preview.start < value.start_time && preview.start < value.start_time &&
@ -297,7 +300,7 @@ export default function DesktopEventView({
<div ref={lastReviewRef} /> <div ref={lastReviewRef} />
)} )}
</div> </div>
<div className="absolute top-12 right-0 bottom-0"> <div className="md:w-[100px] overflow-y-auto no-scrollbar">
<EventReviewTimeline <EventReviewTimeline
segmentDuration={60} segmentDuration={60}
timestampSpread={15} timestampSpread={15}
@ -312,6 +315,7 @@ export default function DesktopEventView({
/> />
</div> </div>
</div> </div>
</div>
); );
} }