Skip to content
51 changes: 48 additions & 3 deletions apps/webapp/app/components/primitives/Timeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
createContext,
useCallback,
useContext,
useEffect,
useRef,
useState,
} from "react";
Expand All @@ -16,12 +17,21 @@ interface MousePosition {
y: number;
}
const MousePositionContext = createContext<MousePosition | undefined>(undefined);
export function MousePositionProvider({ children }: { children: ReactNode }) {
export function MousePositionProvider({
children,
recalculateTrigger,
}: {
children: ReactNode;
recalculateTrigger?: unknown;
}) {
const ref = useRef<HTMLDivElement>(null);
const [position, setPosition] = useState<MousePosition | undefined>(undefined);
const lastMouseCoordsRef = useRef<{ clientX: number; clientY: number } | null>(null);

const handleMouseMove = useCallback(
(e: React.MouseEvent) => {
lastMouseCoordsRef.current = { clientX: e.clientX, clientY: e.clientY };

if (!ref.current) {
setPosition(undefined);
return;
Expand All @@ -41,11 +51,41 @@ export function MousePositionProvider({ children }: { children: ReactNode }) {
[ref.current]
);

// Recalculate position when trigger changes (e.g., panel opens/closes)
// Use requestAnimationFrame to wait for the DOM layout to complete
useEffect(() => {
if (!lastMouseCoordsRef.current) {
return;
}

const rafId = requestAnimationFrame(() => {
if (!ref.current || !lastMouseCoordsRef.current) {
return;
}

const { top, left, width, height } = ref.current.getBoundingClientRect();
const x = (lastMouseCoordsRef.current.clientX - left) / width;
const y = (lastMouseCoordsRef.current.clientY - top) / height;

if (x < 0 || x > 1 || y < 0 || y > 1) {
setPosition(undefined);
return;
}

setPosition({ x, y });
});

return () => cancelAnimationFrame(rafId);
}, [recalculateTrigger]);

return (
<div
ref={ref}
onMouseEnter={handleMouseMove}
onMouseLeave={() => setPosition(undefined)}
onMouseLeave={() => {
lastMouseCoordsRef.current = null;
setPosition(undefined);
}}
onMouseMove={handleMouseMove}
style={{ width: "100%", height: "100%" }}
>
Expand Down Expand Up @@ -83,6 +123,8 @@ export type RootProps = {
maxWidth: number;
children?: ReactNode;
className?: string;
/** When this value changes, recalculate the mouse position (useful when panels resize) */
recalculateTrigger?: unknown;
};

/** The main element that determines the dimensions for all sub-elements */
Expand All @@ -94,6 +136,7 @@ export function Root({
maxWidth,
children,
className,
recalculateTrigger,
}: RootProps) {
const pixelWidth = calculatePixelWidth(minWidth, maxWidth, scale);

Expand All @@ -106,7 +149,9 @@ export function Root({
width: `${pixelWidth}px`,
}}
>
<MousePositionProvider>{children}</MousePositionProvider>
<MousePositionProvider recalculateTrigger={recalculateTrigger}>
{children}
</MousePositionProvider>
</div>
</TimelineContext.Provider>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,7 @@ function TasksTreeView({
treeScrollRef={treeScrollRef}
virtualizer={virtualizer}
toggleNodeSelection={toggleNodeSelection}
selectedId={selectedId}
/>
</ResizablePanel>
</ResizablePanelGroup>
Expand Down Expand Up @@ -973,7 +974,7 @@ function TasksTreeView({

type TimelineViewProps = Pick<
TasksTreeViewProps,
"totalDuration" | "rootSpanStatus" | "events" | "rootStartedAt" | "queuedDuration"
"totalDuration" | "rootSpanStatus" | "events" | "rootStartedAt" | "queuedDuration" | "selectedId"
> & {
scale: number;
parentRef: React.RefObject<HTMLDivElement>;
Expand Down Expand Up @@ -1004,6 +1005,7 @@ function TimelineView({
showDurations,
treeScrollRef,
queuedDuration,
selectedId,
}: TimelineViewProps) {
const timelineContainerRef = useRef<HTMLDivElement>(null);
const initialTimelineDimensions = useInitialDimensions(timelineContainerRef);
Expand Down Expand Up @@ -1042,6 +1044,7 @@ function TimelineView({
className="h-full overflow-hidden"
minWidth={minTimelineWidth}
maxWidth={maxTimelineWidth}
recalculateTrigger={selectedId}
>
{/* Follows the cursor */}
<CurrentTimeIndicator
Expand Down