Skip to content

Commit

Permalink
timeline performance improvemets
Browse files Browse the repository at this point in the history
  • Loading branch information
roflcoopter committed Feb 6, 2024
1 parent 6bc1fc7 commit ff72a68
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 88 deletions.
2 changes: 2 additions & 0 deletions frontend/src/components/events/timeline/ActivityLine.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export const ActivityLine = memo(
theme.palette[cameraEvent.type]
}, ${theme.palette[cameraEvent.type]}) no-repeat center/6px 100%`,
overflow: "hidden",
transition: "background 0.5s",
...style,
}}
/>
Expand All @@ -89,6 +90,7 @@ export const ActivityLine = memo(
width: "6px",
flexShrink: 0,
background: `linear-gradient(${background}, ${background}) no-repeat center/${thickness}px 100%`,
transition: "background 0.2s linear",
...style,
}}
/>
Expand Down
168 changes: 85 additions & 83 deletions frontend/src/components/events/timeline/HoverLine.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Box from "@mui/material/Box";
import dayjs from "dayjs";
import DOMPurify from "dompurify";
import { useEffect, useRef } from "react";
import { memo, useEffect, useRef } from "react";

import { dateToTimestamp, getTimeFromDate } from "lib/helpers";

Expand All @@ -10,93 +10,95 @@ import { SCALE } from "./TimelineTable";
/*
For performance reasons, we update the calculated time with innerHTML instead of using state.
*/
export const HoverLine = ({
containerRef,
startRef,
endRef,
}: {
containerRef: React.MutableRefObject<HTMLDivElement | null>;
startRef: React.MutableRefObject<number>;
endRef: React.MutableRefObject<number>;
}) => {
const ref = useRef<HTMLInputElement | null>(null);
export const HoverLine = memo(
({
containerRef,
startRef,
endRef,
}: {
containerRef: React.MutableRefObject<HTMLDivElement | null>;
startRef: React.MutableRefObject<number>;
endRef: React.MutableRefObject<number>;
}) => {
const ref = useRef<HTMLInputElement | null>(null);

// Listen to mouse move event on the container
useEffect(() => {
const container = containerRef.current;
if (!container) return () => {};
// Listen to mouse move event on the container
useEffect(() => {
const container = containerRef.current;
if (!container) return () => {};

const onMouseMove = (e: MouseEvent) => {
const bounds = container.getBoundingClientRect();
const y = e.clientY - bounds.top;
if (y === 0) {
return;
}
const top = e.clientY;
// Calculate the percentage of cursor position within the container
const percentage = y / bounds.height;
const onMouseMove = (e: MouseEvent) => {
const bounds = container.getBoundingClientRect();
const y = e.clientY - bounds.top;
if (y === 0) {
return;
}
const top = e.clientY;
// Calculate the percentage of cursor position within the container
const percentage = y / bounds.height;

// First time tick is preceded by a margin of half the time tick height
// so to get the correct time we add 1/3 of the scale (not entirely sure why 1/3 works)
const _start = startRef.current * 1000 + (SCALE * 1000) / 3;
// Last time tick is followed by a margin of half the time tick height
// so we subtract half the scale to get the correct time
const _end = endRef.current * 1000 - (SCALE * 1000) / 2;
// Calculate the time difference in milliseconds between start and end dates
const timeDifference = _end - _start;
// First time tick is preceded by a margin of half the time tick height
// so to get the correct time we add 1/3 of the scale (not entirely sure why 1/3 works)
const _start = startRef.current * 1000 + (SCALE * 1000) / 3;
// Last time tick is followed by a margin of half the time tick height
// so we subtract half the scale to get the correct time
const _end = endRef.current * 1000 - (SCALE * 1000) / 2;
// Calculate the time difference in milliseconds between start and end dates
const timeDifference = _end - _start;

// Calculate the time corresponding to the cursor position
const dateAtCursor = new Date(_start + percentage * timeDifference);
if (dateToTimestamp(dateAtCursor) > dayjs().unix()) {
return;
}
const timeAtCursor = getTimeFromDate(dateAtCursor);
// Calculate the time corresponding to the cursor position
const dateAtCursor = new Date(_start + percentage * timeDifference);
if (dateToTimestamp(dateAtCursor) > dayjs().unix()) {
return;
}
const timeAtCursor = getTimeFromDate(dateAtCursor);

// Position the line and display the time
if (ref.current) {
ref.current.style.top = `${top}px`;
ref.current.style.width = `${bounds.width}px`;
}
if (ref.current && ref.current.innerHTML !== timeAtCursor) {
ref.current.innerHTML = DOMPurify.sanitize(timeAtCursor);
}
};
// Position the line and display the time
if (ref.current) {
ref.current.style.top = `${top}px`;
ref.current.style.width = `${bounds.width}px`;
}
if (ref.current && ref.current.innerHTML !== timeAtCursor) {
ref.current.innerHTML = DOMPurify.sanitize(timeAtCursor);
}
};

const onMouseEnter = (_e: MouseEvent) => {
if (ref.current) {
ref.current.style.display = "block";
}
};
const onMouseLeave = (_e: MouseEvent) => {
if (ref.current) {
ref.current.style.display = "none";
}
};
const onMouseEnter = (_e: MouseEvent) => {
if (ref.current) {
ref.current.style.display = "block";
}
};
const onMouseLeave = (_e: MouseEvent) => {
if (ref.current) {
ref.current.style.display = "none";
}
};

container.addEventListener("mousemove", onMouseMove);
container.addEventListener("mouseenter", onMouseEnter);
container.addEventListener("mouseleave", onMouseLeave);
return () => {
container.removeEventListener("mousemove", onMouseMove);
container.removeEventListener("mouseenter", onMouseEnter);
container.removeEventListener("mouseleave", onMouseLeave);
};
}, [containerRef, endRef, ref, startRef]);
container.addEventListener("mousemove", onMouseMove);
container.addEventListener("mouseenter", onMouseEnter);
container.addEventListener("mouseleave", onMouseLeave);
return () => {
container.removeEventListener("mousemove", onMouseMove);
container.removeEventListener("mouseenter", onMouseEnter);
container.removeEventListener("mouseleave", onMouseLeave);
};
}, [containerRef, endRef, ref, startRef]);

return (
<Box
ref={ref}
sx={(theme) => ({
display: "none",
pointerEvents: "none",
position: "absolute",
width: "350px",
height: "1px",
backgroundColor: theme.palette.divider,
textShadow: "rgba(0, 0, 0, 0.88) 0px 0px 4px",
fontSize: "0.7rem",
zIndex: 100,
})}
></Box>
);
};
return (
<Box
ref={ref}
sx={(theme) => ({
display: "none",
pointerEvents: "none",
position: "absolute",
width: "350px",
height: "1px",
backgroundColor: theme.palette.divider,
textShadow: "rgba(0, 0, 0, 0.88) 0px 0px 4px",
fontSize: "0.7rem",
zIndex: 100,
})}
></Box>
);
},
);
45 changes: 40 additions & 5 deletions frontend/src/components/events/timeline/TimelineTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type TimelineItem = {
timedEvent: null | types.CameraMotionEvent | types.CameraRecordingEvent;
snapshotEvent: null | types.CameraObjectEvent;
availableTimespan: null | types.HlsAvailableTimespan;
timespanVariant: "first" | "middle" | "last" | "round" | null;
};

const round = (num: number) => Math.ceil(num / SCALE) * SCALE;
Expand Down Expand Up @@ -129,6 +130,7 @@ const useInitialTimeline = (
timedEvent: null,
snapshotEvent: null,
availableTimespan: null,
timespanVariant: null,
});
timeTick -= SCALE;
}
Expand Down Expand Up @@ -163,6 +165,7 @@ const useAddTicks = (
timedEvent: null,
snapshotEvent: null,
availableTimespan: null,
timespanVariant: null,
});
}
return newItems;
Expand Down Expand Up @@ -215,21 +218,25 @@ const useUpdateTimeline = (
newItems[indexStart] = {
...newItems[indexStart],
availableTimespan: timespan,
timespanVariant: "round",
};
return;
}
newItems[indexStart] = {
...newItems[indexStart],
availableTimespan: timespan,
timespanVariant: "first",
};
newItems[indexEnd] = {
...newItems[indexEnd],
availableTimespan: timespan,
timespanVariant: "last",
};
for (let i = indexStart + 1; i < indexEnd; i++) {
newItems[i] = {
...newItems[i],
availableTimespan: timespan,
timespanVariant: "middle",
};
}
});
Expand Down Expand Up @@ -293,12 +300,41 @@ const useUpdateTimeline = (
}, [eventsData, availableTimespansData]);
};

const itemEqual = (
prevItem: Readonly<ItemProps>,
nextItem: Readonly<ItemProps>,
) =>
prevItem.item.time === nextItem.item.time &&
prevItem.item.timespanVariant === nextItem.item.timespanVariant &&
prevItem.item.timedEvent?.start_timestamp ===
nextItem.item.timedEvent?.start_timestamp &&
prevItem.item.timedEvent?.end_timestamp ===
nextItem.item.timedEvent?.end_timestamp &&
prevItem.item.snapshotEvent?.timestamp ===
nextItem.item.snapshotEvent?.timestamp;

type ItemProps = {
startRef: React.MutableRefObject<number>;
virtualItem: VirtualItem;
item: TimelineItem;
};
const Item = memo(({ startRef, virtualItem, item }: ItemProps): JSX.Element => {
const Item = memo(
({ startRef, virtualItem, item }: ItemProps): JSX.Element => (
<>
<TimeTick key={`tick-${item.time}`} time={item.time} />
{activityLine(startRef, item, virtualItem.index)}
{objectEvent(item)}
</>
),
itemEqual,
);

type RowProps = {
startRef: React.MutableRefObject<number>;
virtualItem: VirtualItem;
item: TimelineItem;
};
const Row = memo(({ startRef, virtualItem, item }: RowProps): JSX.Element => {
const [hover, setHover] = useState(false);

return (
Expand All @@ -325,14 +361,13 @@ const Item = memo(({ startRef, virtualItem, item }: ItemProps): JSX.Element => {
height: `${virtualItem.size}px`,
width: "100%",
transform: `translateY(${virtualItem.start}px)`,
transition: "transform 0.2s linear",
zIndex:
// eslint-disable-next-line no-nested-ternary
item.snapshotEvent && hover ? 999 : item.snapshotEvent ? 998 : 1,
}}
>
<TimeTick key={`tick-${item.time}`} time={item.time} />
{activityLine(startRef, item, virtualItem.index)}
{objectEvent(item)}
<Item startRef={startRef} virtualItem={virtualItem} item={item} />
</div>
);
});
Expand Down Expand Up @@ -360,7 +395,7 @@ const VirtualList = memo(
}}
>
{rowVirtualizer.getVirtualItems().map((virtualItem) => (
<Item
<Row
key={`item-${items[virtualItem.index].time}`}
startRef={startRef}
virtualItem={virtualItem}
Expand Down

0 comments on commit ff72a68

Please sign in to comment.