2023-12-21 03:37:35 +03:00
|
|
|
import { useEffect, useRef, useState } from "react";
|
|
|
|
|
import {
|
|
|
|
|
Timeline as VisTimeline,
|
|
|
|
|
TimelineGroup,
|
|
|
|
|
TimelineItem,
|
|
|
|
|
TimelineOptions,
|
2023-12-31 16:35:15 +03:00
|
|
|
DateType,
|
|
|
|
|
IdType,
|
2023-12-21 03:37:35 +03:00
|
|
|
} from "vis-timeline";
|
|
|
|
|
import type { DataGroup, DataItem, TimelineEvents } from "vis-timeline/types";
|
|
|
|
|
import "./scrubber.css";
|
|
|
|
|
|
|
|
|
|
export type TimelineEventsWithMissing =
|
|
|
|
|
| TimelineEvents
|
|
|
|
|
| "dragover"
|
|
|
|
|
| "markerchange"
|
|
|
|
|
| "markerchanged";
|
|
|
|
|
|
|
|
|
|
export type TimelineEventHandler =
|
|
|
|
|
| "currentTimeTickHandler"
|
|
|
|
|
| "clickHandler"
|
|
|
|
|
| "contextmenuHandler"
|
|
|
|
|
| "doubleClickHandler"
|
|
|
|
|
| "dragoverHandler"
|
|
|
|
|
| "dropHandler"
|
|
|
|
|
| "mouseOverHandler"
|
|
|
|
|
| "mouseDownHandler"
|
|
|
|
|
| "mouseUpHandler"
|
|
|
|
|
| "mouseMoveHandler"
|
|
|
|
|
| "groupDraggedHandler"
|
|
|
|
|
| "changedHandler"
|
|
|
|
|
| "rangechangeHandler"
|
|
|
|
|
| "rangechangedHandler"
|
|
|
|
|
| "selectHandler"
|
|
|
|
|
| "itemoverHandler"
|
|
|
|
|
| "itemoutHandler"
|
|
|
|
|
| "timechangeHandler"
|
|
|
|
|
| "timechangedHandler"
|
|
|
|
|
| "markerchangeHandler"
|
|
|
|
|
| "markerchangedHandler";
|
|
|
|
|
|
|
|
|
|
type EventHandler = {
|
|
|
|
|
(properties: any): void;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type TimelineEventsHandlers = Partial<
|
|
|
|
|
Record<TimelineEventHandler, EventHandler>
|
|
|
|
|
>;
|
|
|
|
|
|
|
|
|
|
export type ScrubberItem = TimelineItem;
|
|
|
|
|
|
|
|
|
|
const domEvents: TimelineEventsWithMissing[] = [
|
|
|
|
|
"currentTimeTick",
|
|
|
|
|
"click",
|
|
|
|
|
"contextmenu",
|
|
|
|
|
"doubleClick",
|
|
|
|
|
"dragover",
|
|
|
|
|
"drop",
|
|
|
|
|
"mouseOver",
|
|
|
|
|
"mouseDown",
|
|
|
|
|
"mouseUp",
|
|
|
|
|
"mouseMove",
|
|
|
|
|
"groupDragged",
|
|
|
|
|
"changed",
|
|
|
|
|
"rangechange",
|
|
|
|
|
"rangechanged",
|
|
|
|
|
"select",
|
|
|
|
|
"itemover",
|
|
|
|
|
"itemout",
|
|
|
|
|
"timechange",
|
|
|
|
|
"timechanged",
|
|
|
|
|
"markerchange",
|
|
|
|
|
"markerchanged",
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
type ActivityScrubberProps = {
|
2023-12-31 16:35:15 +03:00
|
|
|
className?: string;
|
|
|
|
|
items?: TimelineItem[];
|
2024-01-01 18:37:07 +03:00
|
|
|
timeBars?: { time: DateType; id: IdType }[];
|
2023-12-21 03:37:35 +03:00
|
|
|
groups?: TimelineGroup[];
|
|
|
|
|
options?: TimelineOptions;
|
|
|
|
|
} & TimelineEventsHandlers;
|
|
|
|
|
|
|
|
|
|
function ActivityScrubber({
|
2023-12-31 16:35:15 +03:00
|
|
|
className,
|
2023-12-21 03:37:35 +03:00
|
|
|
items,
|
2023-12-31 16:35:15 +03:00
|
|
|
timeBars,
|
2023-12-21 03:37:35 +03:00
|
|
|
groups,
|
|
|
|
|
options,
|
|
|
|
|
...eventHandlers
|
|
|
|
|
}: ActivityScrubberProps) {
|
|
|
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
const timelineRef = useRef<{ timeline: VisTimeline | null }>({
|
|
|
|
|
timeline: null,
|
|
|
|
|
});
|
|
|
|
|
const [currentTime, setCurrentTime] = useState(Date.now());
|
2024-01-01 18:37:07 +03:00
|
|
|
const [_, setCustomTimes] = useState<
|
|
|
|
|
{ id: IdType; time: DateType }[]
|
|
|
|
|
>([]);
|
2023-12-21 03:37:35 +03:00
|
|
|
|
|
|
|
|
const defaultOptions: TimelineOptions = {
|
|
|
|
|
width: "100%",
|
|
|
|
|
maxHeight: "350px",
|
|
|
|
|
stack: true,
|
|
|
|
|
showMajorLabels: true,
|
|
|
|
|
showCurrentTime: false,
|
|
|
|
|
zoomMin: 10 * 1000, // 10 seconds
|
|
|
|
|
// start: new Date(currentTime - 60 * 1 * 60 * 1000), // 1 hour ago
|
|
|
|
|
end: currentTime,
|
|
|
|
|
max: currentTime,
|
|
|
|
|
format: {
|
|
|
|
|
minorLabels: {
|
|
|
|
|
minute: "h:mma",
|
|
|
|
|
hour: "ha",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const intervalId = setInterval(() => {
|
|
|
|
|
setCurrentTime(Date.now());
|
|
|
|
|
}, 60000); // Update every minute
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
clearInterval(intervalId);
|
|
|
|
|
};
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const divElement = containerRef.current;
|
|
|
|
|
if (!divElement) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-31 16:35:15 +03:00
|
|
|
const timelineOptions: TimelineOptions = {
|
|
|
|
|
...defaultOptions,
|
|
|
|
|
...options,
|
|
|
|
|
};
|
|
|
|
|
|
2023-12-21 03:37:35 +03:00
|
|
|
const timelineInstance = new VisTimeline(
|
|
|
|
|
divElement,
|
|
|
|
|
items as DataItem[],
|
|
|
|
|
groups as DataGroup[],
|
2023-12-31 16:35:15 +03:00
|
|
|
timelineOptions
|
2023-12-21 03:37:35 +03:00
|
|
|
);
|
|
|
|
|
|
2023-12-31 16:35:15 +03:00
|
|
|
if (timeBars) {
|
|
|
|
|
timeBars.forEach((bar) => {
|
|
|
|
|
timelineInstance.addCustomTime(bar.time, bar.id);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-21 03:37:35 +03:00
|
|
|
domEvents.forEach((event) => {
|
|
|
|
|
const eventHandler = eventHandlers[`${event}Handler`];
|
|
|
|
|
if (typeof eventHandler === "function") {
|
|
|
|
|
timelineInstance.on(event, eventHandler);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
timelineRef.current.timeline = timelineInstance;
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
timelineInstance.destroy();
|
|
|
|
|
};
|
2023-12-31 16:35:15 +03:00
|
|
|
}, [containerRef]);
|
2023-12-21 03:37:35 +03:00
|
|
|
|
2024-01-01 18:37:07 +03:00
|
|
|
// need to keep custom times in sync
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!timelineRef.current.timeline || timeBars == undefined) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setCustomTimes((prevTimes) => {
|
|
|
|
|
if (prevTimes.length == 0 && timeBars.length == 0) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prevTimes
|
|
|
|
|
.filter((x) => timeBars.find((y) => x.id == y.id) == undefined)
|
|
|
|
|
.forEach((time) => {
|
|
|
|
|
try {
|
|
|
|
|
timelineRef.current.timeline?.removeCustomTime(time.id);
|
|
|
|
|
} catch {}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
timeBars.forEach((time) => {
|
|
|
|
|
try {
|
|
|
|
|
const existing = timelineRef.current.timeline?.getCustomTime(time.id);
|
|
|
|
|
|
|
|
|
|
if (existing != time.time) {
|
|
|
|
|
timelineRef.current.timeline?.setCustomTime(time.time, time.id);
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
timelineRef.current.timeline?.addCustomTime(time.time, time.id);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return timeBars;
|
|
|
|
|
});
|
|
|
|
|
}, [timeBars, timelineRef]);
|
|
|
|
|
|
2023-12-31 16:35:15 +03:00
|
|
|
return (
|
|
|
|
|
<div className={className || ""}>
|
|
|
|
|
<div ref={containerRef} />
|
|
|
|
|
</div>
|
|
|
|
|
);
|
2023-12-21 03:37:35 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default ActivityScrubber;
|