diff --git a/packages/replay-next/components/console/StackRenderer.tsx b/packages/replay-next/components/console/StackRenderer.tsx index e1335e552e6..ae9c403729a 100644 --- a/packages/replay-next/components/console/StackRenderer.tsx +++ b/packages/replay-next/components/console/StackRenderer.tsx @@ -1,8 +1,7 @@ import { CallStack, Frame, FrameId } from "@replayio/protocol"; -import React, { Suspense, memo, useContext } from "react"; +import { Suspense, memo } from "react"; import Loader from "replay-next/components/Loader"; -import { ReplayClientContext } from "shared/client/ReplayClientContext"; import Source from "./Source"; import styles from "./StackRenderer.module.css"; @@ -26,8 +25,6 @@ function StackRenderer({ frames, stack }: { frames: Frame[]; stack: CallStack }) } function StackFrameRenderer({ frameId, frames }: { frameId: FrameId; frames: Frame[] }) { - const client = useContext(ReplayClientContext); - const frame = frames.find(frame => frame.frameId === frameId); if (frame == null) { return null; diff --git a/src/devtools/client/debugger/src/components/SecondaryPanes/DependencyGraph/DependencyGraph.tsx b/src/devtools/client/debugger/src/components/SecondaryPanes/DependencyGraph/DependencyGraph.tsx index 8a20e41b1c5..0271f428c6b 100644 --- a/src/devtools/client/debugger/src/components/SecondaryPanes/DependencyGraph/DependencyGraph.tsx +++ b/src/devtools/client/debugger/src/components/SecondaryPanes/DependencyGraph/DependencyGraph.tsx @@ -1,7 +1,10 @@ -import { ExecutionPoint } from "@replayio/protocol"; +import { ExecutionPoint, Location } from "@replayio/protocol"; import { Suspense, useContext } from "react"; import { PanelLoader } from "replay-next/components/PanelLoader"; +import { sourcesByIdCache, sourcesByUrlCache } from "replay-next/src/suspense/SourcesCache"; +import { getSourceToDisplayForUrl } from "replay-next/src/utils/sources"; +import { suspendInParallel } from "replay-next/src/utils/suspense"; import { ReplayClientContext } from "shared/client/ReplayClientContext"; import { DependencyGraphMode } from "shared/client/types"; import { depGraphCache } from "ui/suspense/depGraphCache"; @@ -25,27 +28,48 @@ export function DependencyGraph(props: Props) { function DependencyGraphSuspends({ mode, point }: Props) { const replayClient = useContext(ReplayClientContext); - const depGraphValue = depGraphCache.read(replayClient, point ?? null, mode ?? null); + const [depGraphValue, sourcesById, sourcesByUrl] = suspendInParallel( + () => depGraphCache.read(replayClient, point ?? null, mode ?? null), + () => sourcesByIdCache.read(replayClient), + () => sourcesByUrlCache.read(replayClient) + ); + const valueDescending = depGraphValue?.slice().reverse(); return (
- {valueDescending?.map((entry, index) => ( - { + let location: Location | null = null; + if ("calleeLocation" in entry && entry.calleeLocation != null) { + const source = getSourceToDisplayForUrl( + sourcesById, + sourcesByUrl, + entry.calleeLocation.url + ); + if (source) { + location = { + ...entry.calleeLocation, + sourceId: source.sourceId, + }; } - /> - ))} + } + + return ( + + ); + })}
); } diff --git a/src/devtools/client/debugger/src/components/SecondaryPanes/DependencyGraph/Item.module.css b/src/devtools/client/debugger/src/components/SecondaryPanes/DependencyGraph/Item.module.css index 3dc9aa5b63c..4d33da2c674 100644 --- a/src/devtools/client/debugger/src/components/SecondaryPanes/DependencyGraph/Item.module.css +++ b/src/devtools/client/debugger/src/components/SecondaryPanes/DependencyGraph/Item.module.css @@ -9,10 +9,6 @@ background-color: var(--theme-selection-background-hover); cursor: pointer; } -.Item[data-selected] { - background-color: var(--theme-selection-background); - color: white; -} .Item[data-disabled] { color: var(--color-dim); } @@ -20,6 +16,11 @@ background-color: transparent; cursor: default; } +.Item[data-selected], +.Item[data-selected]:hover { + background-color: var(--theme-selection-background); + color: white; +} .NameColumn { overflow: hidden; @@ -41,5 +42,5 @@ text-overflow: ellipsis; } .Item[data-selected] .LocationColumn { - display: none; + color: white; } diff --git a/src/devtools/client/debugger/src/components/SecondaryPanes/DependencyGraph/Item.tsx b/src/devtools/client/debugger/src/components/SecondaryPanes/DependencyGraph/Item.tsx index f99a906b964..f2c6be45a56 100644 --- a/src/devtools/client/debugger/src/components/SecondaryPanes/DependencyGraph/Item.tsx +++ b/src/devtools/client/debugger/src/components/SecondaryPanes/DependencyGraph/Item.tsx @@ -1,40 +1,40 @@ -import { TimeStampedPoint } from "@replayio/protocol"; +import { Location, TimeStampedPoint } from "@replayio/protocol"; +import { selectDependencyGraphNode } from "devtools/client/debugger/src/components/SecondaryPanes/DependencyGraph/selectDependencyGraphNode"; +import { useIsCurrentItem } from "devtools/client/debugger/src/components/SecondaryPanes/DependencyGraph/useIsCurrentItem"; import { getRawSourceURL } from "devtools/client/debugger/src/utils/source"; import { getURL } from "devtools/client/debugger/src/utils/sources-tree"; import { formatTimestamp } from "replay-next/src/utils/time"; -import { seek } from "ui/actions/timeline"; import { useAppDispatch } from "ui/setup/hooks"; import { LocationWithUrl } from "ui/suspense/depGraphCache"; import styles from "./Item.module.css"; export function Item({ - isCurrent, name, location, timeStampedPoint, }: { name: string; - isCurrent?: boolean; - location: LocationWithUrl | null; + location: Location | LocationWithUrl | null; timeStampedPoint: TimeStampedPoint | null; }) { const dispatch = useAppDispatch(); - const onClick = () => { - timeStampedPoint && - dispatch( - seek({ - executionPoint: timeStampedPoint.point, - openSource: true, - time: timeStampedPoint.time, - }) - ); + const isCurrent = useIsCurrentItem(timeStampedPoint, location); + + const isDisabled = !location || !timeStampedPoint; + + const onClick = async () => { + if (!location || !timeStampedPoint) { + return; + } + + dispatch(selectDependencyGraphNode(location, timeStampedPoint)); }; let source = null; - if (location) { + if (location && "url" in location) { source = `${getRawSourceURL(getURL(location).filename)}:${location.line}`; } @@ -42,9 +42,8 @@ export function Item({
{name}
{timeStampedPoint && ( diff --git a/src/devtools/client/debugger/src/components/SecondaryPanes/DependencyGraph/ReactComponentStack.tsx b/src/devtools/client/debugger/src/components/SecondaryPanes/DependencyGraph/ReactComponentStack.tsx index 7887abac527..458fe607e6c 100644 --- a/src/devtools/client/debugger/src/components/SecondaryPanes/DependencyGraph/ReactComponentStack.tsx +++ b/src/devtools/client/debugger/src/components/SecondaryPanes/DependencyGraph/ReactComponentStack.tsx @@ -32,16 +32,15 @@ function ReactComponentStackSuspends({ timeStampedPoint }: Props) { return No frames to display; } - const firstItem = reactStackValue[0]; + const leafNode = reactStackValue[0]; return ( <> - {firstItem && ( + {leafNode && ( )} {reactStackValue?.map((entry, index) => ( diff --git a/src/devtools/client/debugger/src/components/SecondaryPanes/DependencyGraph/selectDependencyGraphNode.ts b/src/devtools/client/debugger/src/components/SecondaryPanes/DependencyGraph/selectDependencyGraphNode.ts new file mode 100644 index 00000000000..b10fb7d6547 --- /dev/null +++ b/src/devtools/client/debugger/src/components/SecondaryPanes/DependencyGraph/selectDependencyGraphNode.ts @@ -0,0 +1,27 @@ +import { Location, TimeStampedPoint } from "@replayio/protocol"; + +import { selectLocation } from "devtools/client/debugger/src/actions/sources"; +import { frameSelected, getThreadContext } from "devtools/client/debugger/src/selectors"; +import { pauseIdCache } from "replay-next/src/suspense/PauseCache"; +import { UIThunkAction } from "ui/actions"; + +export function selectDependencyGraphNode( + location: Location, + timeStampedPoint: TimeStampedPoint +): UIThunkAction { + return async (dispatch, getState, { replayClient }) => { + const pauseId = await pauseIdCache.readAsync( + replayClient, + timeStampedPoint.point, + timeStampedPoint.time + ); + + const context = getThreadContext(getState()); + + // TODO Is this legit? + const frameId = "0"; + + dispatch(frameSelected({ cx: context, pauseId, frameId })); + dispatch(selectLocation(context, location)); + }; +} diff --git a/src/devtools/client/debugger/src/components/SecondaryPanes/DependencyGraph/useIsCurrentItem.ts b/src/devtools/client/debugger/src/components/SecondaryPanes/DependencyGraph/useIsCurrentItem.ts new file mode 100644 index 00000000000..ad57abafe19 --- /dev/null +++ b/src/devtools/client/debugger/src/components/SecondaryPanes/DependencyGraph/useIsCurrentItem.ts @@ -0,0 +1,24 @@ +import { Location, TimeStampedPoint } from "@replayio/protocol"; + +import { getSelectedFrameId } from "devtools/client/debugger/src/selectors"; +import { comparePosition } from "devtools/client/debugger/src/utils/location"; +import { getPointAndTimeForPauseId } from "replay-next/src/suspense/PauseCache"; +import { getSelectedLocation } from "ui/reducers/sources"; +import { useAppSelector } from "ui/setup/hooks"; + +export function useIsCurrentItem( + timeStampedPoint: TimeStampedPoint | null, + location: Location | null +) { + const { pauseId } = useAppSelector(getSelectedFrameId) ?? {}; + const selectedLocation = useAppSelector(getSelectedLocation); + + const [selectedExecutionPoint] = pauseId ? getPointAndTimeForPauseId(pauseId) : []; + + const isMatchingExecutionPoint = + selectedExecutionPoint != null && timeStampedPoint?.point === selectedExecutionPoint; + const isMatchingLocation = + location && selectedLocation && comparePosition(location, selectedLocation); + + return isMatchingExecutionPoint && isMatchingLocation; +} diff --git a/src/devtools/client/debugger/src/utils/location.ts b/src/devtools/client/debugger/src/utils/location.ts index a9359f13efe..37fe20933fb 100644 --- a/src/devtools/client/debugger/src/utils/location.ts +++ b/src/devtools/client/debugger/src/utils/location.ts @@ -4,7 +4,7 @@ import type { Location, SourceLocation } from "@replayio/protocol"; import sortBy from "lodash/sortBy"; -export function comparePosition(a: Location, b: Location) { +export function comparePosition(a: Partial, b: Partial) { return a && b && a.line == b.line && a.column == b.column; }