More intelligent timeline scrolling (#10209)

* more intelligent timeline scrolling

* keep as div
This commit is contained in:
Josh Hawkins 2024-03-03 10:32:35 -06:00 committed by GitHub
parent 8645545ef4
commit 312dc95156
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 35 additions and 22 deletions

14
web/package-lock.json generated
View File

@ -52,6 +52,7 @@
"react-transition-group": "^4.4.5", "react-transition-group": "^4.4.5",
"react-use-websocket": "^4.7.0", "react-use-websocket": "^4.7.0",
"recoil": "^0.7.7", "recoil": "^0.7.7",
"scroll-into-view-if-needed": "^3.1.0",
"sonner": "^1.4.0", "sonner": "^1.4.0",
"sort-by": "^1.2.0", "sort-by": "^1.2.0",
"strftime": "^0.10.2", "strftime": "^0.10.2",
@ -3761,6 +3762,11 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/compute-scroll-into-view": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.0.tgz",
"integrity": "sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg=="
},
"node_modules/concat-map": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -7213,6 +7219,14 @@
"loose-envify": "^1.1.0" "loose-envify": "^1.1.0"
} }
}, },
"node_modules/scroll-into-view-if-needed": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz",
"integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==",
"dependencies": {
"compute-scroll-into-view": "^3.0.2"
}
},
"node_modules/semver": { "node_modules/semver": {
"version": "7.5.4", "version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",

View File

@ -57,6 +57,7 @@
"react-transition-group": "^4.4.5", "react-transition-group": "^4.4.5",
"react-use-websocket": "^4.7.0", "react-use-websocket": "^4.7.0",
"recoil": "^0.7.7", "recoil": "^0.7.7",
"scroll-into-view-if-needed": "^3.1.0",
"sonner": "^1.4.0", "sonner": "^1.4.0",
"sort-by": "^1.2.0", "sort-by": "^1.2.0",
"strftime": "^0.10.2", "strftime": "^0.10.2",

View File

@ -71,7 +71,7 @@ export default function ReviewFilterGroup({
); );
return ( return (
<div className="mr-2"> <div>
<CamerasFilterButton <CamerasFilterButton
allCameras={filterValues.cameras} allCameras={filterValues.cameras}
selectedCameras={filter?.cameras} selectedCameras={filter?.cameras}
@ -246,7 +246,7 @@ function GeneralFilterButton({
return ( return (
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button size="sm" className="mx-1" variant="secondary"> <Button size="sm" className="ml-1" variant="secondary">
<FaFilter className="md:mr-[10px] text-muted-foreground" /> <FaFilter className="md:mr-[10px] text-muted-foreground" />
<div className="hidden md:block">Filter</div> <div className="hidden md:block">Filter</div>
</Button> </Button>

View File

@ -16,6 +16,7 @@ import {
HoverCardTrigger, HoverCardTrigger,
} from "../ui/hover-card"; } from "../ui/hover-card";
import { HoverCardPortal } from "@radix-ui/react-hover-card"; import { HoverCardPortal } from "@radix-ui/react-hover-card";
import scrollIntoView from "scroll-into-view-if-needed";
type EventSegmentProps = { type EventSegmentProps = {
events: ReviewSegment[]; events: ReviewSegment[];
@ -225,20 +226,14 @@ export function EventSegment({
const firstMinimapSegmentRef = useRef<HTMLDivElement>(null); const firstMinimapSegmentRef = useRef<HTMLDivElement>(null);
let debounceTimer: ReturnType<typeof setTimeout>;
function debounceScrollIntoView(element: HTMLElement) {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
element.scrollIntoView({ behavior: "smooth", block: "center" });
}, 100);
}
useEffect(() => { useEffect(() => {
// Check if the first segment is out of view // Check if the first segment is out of view
const firstSegment = firstMinimapSegmentRef.current; const firstSegment = firstMinimapSegmentRef.current;
if (firstSegment && showMinimap && isFirstSegmentInMinimap) { if (firstSegment && showMinimap && isFirstSegmentInMinimap) {
debounceScrollIntoView(firstSegment); scrollIntoView(firstSegment, {
scrollMode: "if-needed",
behavior: "smooth",
});
} }
// we know that these deps are correct // we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@ -276,7 +271,10 @@ export function EventSegment({
`[data-segment-start="${startTimestamp - segmentDuration}"]`, `[data-segment-start="${startTimestamp - segmentDuration}"]`,
); );
if (element instanceof HTMLElement) { if (element instanceof HTMLElement) {
debounceScrollIntoView(element); scrollIntoView(element, {
scrollMode: "if-needed",
behavior: "smooth",
});
element.classList.add( element.classList.add(
`outline-severity_${severityType}`, `outline-severity_${severityType}`,
`shadow-severity_${severityType}`, `shadow-severity_${severityType}`,

View File

@ -1,4 +1,5 @@
import { useCallback, useEffect } from "react"; import { useCallback, useEffect } from "react";
import scrollIntoView from "scroll-into-view-if-needed";
type DragHandlerProps = { type DragHandlerProps = {
contentRef: React.RefObject<HTMLElement>; contentRef: React.RefObject<HTMLElement>;
@ -75,6 +76,10 @@ function useDraggableHandler({
minute: "2-digit", minute: "2-digit",
...(segmentDuration < 60 && { second: "2-digit" }), ...(segmentDuration < 60 && { second: "2-digit" }),
}); });
scrollIntoView(thumb, {
scrollMode: "if-needed",
behavior: "smooth",
});
} }
}); });
if (setHandlebarTime) { if (setHandlebarTime) {
@ -167,11 +172,6 @@ function useDraggableHandler({
scrolled; scrolled;
updateHandlebarPosition(newHandlePosition - segmentHeight, handlebarTime); updateHandlebarPosition(newHandlePosition - segmentHeight, handlebarTime);
scrollTimeRef.current.scrollIntoView({
behavior: "smooth",
block: "center",
});
} }
// we know that these deps are correct // we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps

View File

@ -247,12 +247,12 @@ export default function EventView({
return ( return (
<div className="flex flex-col size-full"> <div className="flex flex-col size-full">
<div className="h-10 relative flex justify-between items-center mb-2"> <div className="h-10 relative flex justify-between items-center m-2">
{isMobile && ( {isMobile && (
<Logo className="absolute inset-y-0 inset-x-1/2 -translate-x-1/2 h-8" /> <Logo className="absolute inset-y-0 inset-x-1/2 -translate-x-1/2 h-8" />
)} )}
<ToggleGroup <ToggleGroup
className="*:px-3 *:py4 *:rounded-2xl" className="*:px-3 *:py-4 *:rounded-2xl"
type="single" type="single"
defaultValue="alert" defaultValue="alert"
size="sm" size="sm"
@ -315,7 +315,7 @@ export default function EventView({
{!isValidating && currentItems == null && ( {!isValidating && currentItems == null && (
<div className="size-full flex flex-col justify-center items-center"> <div className="size-full flex flex-col justify-center items-center">
<LuFolderCheck className="size-16" /> <LuFolderCheck className="size-16" />
There are no {severity} items to review There are no {severity.replace(/_/g, " ")} items to review
</div> </div>
)} )}

View File

@ -120,7 +120,7 @@ export default function LiveDashboardView({
)} )}
<div <div
className={`mt-4 grid ${layout == "grid" ? "grid-cols-2 xl:grid-cols-3 3xl:grid-cols-4" : ""} gap-2 md:gap-4 *:rounded-2xl *:bg-black`} className={`my-4 grid ${layout == "grid" ? "grid-cols-2 xl:grid-cols-3 3xl:grid-cols-4" : ""} gap-2 md:gap-4 *:rounded-2xl *:bg-black`}
> >
{cameras.map((camera) => { {cameras.map((camera) => {
let grow; let grow;