mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	UI fixes (#9986)
* 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:
		
							parent
							
								
									f84d2db406
								
							
						
					
					
						commit
						a6aa5328aa
					
				| @ -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 | ||||||
|  | |||||||
| @ -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 }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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> | ||||||
|  | |||||||
| @ -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> | ||||||
|     </> |     </> | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|  | |||||||
| @ -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> | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user