mirror of
https://github.com/facebook/react.git
synced 2026-02-21 19:31:52 +00:00
[DevTools] Double click a Suspense Rect to jump to its position in the timeline (#34642)
When you double click it will hide or show by jumping to the selected index or one step before the selected. Let's you go from a suspense boundary into the timeline to find its position. I also highlight the step in the timeline when you hover the rect. This only works if it's in the selected root but all of those should be merged into one single timeline. One thing that's weird about the SuspenseNodes now is that they sometimes gets deleted but not always when they're resupended. Nested ones maybe? This means that if you double click to hide it, you can't double click again to show it. This seems like an unrelated bug that we should fix. We could potentially repurpose the existing "Suspend" button in the toolbar to do this too, or maybe add another icon there.
This commit is contained in:
committed by
GitHub
parent
8674c3ba28
commit
d15d7fd79e
@@ -98,6 +98,18 @@ function SuspenseRects({
|
||||
});
|
||||
}
|
||||
|
||||
function handleDoubleClick(event: SyntheticMouseEvent) {
|
||||
if (event.defaultPrevented) {
|
||||
// Already clicked on an inner rect
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
suspenseTreeDispatch({
|
||||
type: 'TOGGLE_TIMELINE_FOR_ID',
|
||||
payload: suspenseID,
|
||||
});
|
||||
}
|
||||
|
||||
function handlePointerOver(event: SyntheticPointerEvent) {
|
||||
if (event.defaultPrevented) {
|
||||
// Already hovered an inner rect
|
||||
@@ -105,6 +117,10 @@ function SuspenseRects({
|
||||
}
|
||||
event.preventDefault();
|
||||
highlightHostInstance(suspenseID);
|
||||
suspenseTreeDispatch({
|
||||
type: 'HOVER_TIMELINE_FOR_ID',
|
||||
payload: suspenseID,
|
||||
});
|
||||
}
|
||||
|
||||
function handlePointerLeave(event: SyntheticPointerEvent) {
|
||||
@@ -114,6 +130,10 @@ function SuspenseRects({
|
||||
}
|
||||
event.preventDefault();
|
||||
clearHighlightHostInstance();
|
||||
suspenseTreeDispatch({
|
||||
type: 'HOVER_TIMELINE_FOR_ID',
|
||||
payload: -1,
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Use the nearest Suspense boundary
|
||||
@@ -137,6 +157,7 @@ function SuspenseRects({
|
||||
rect={rect}
|
||||
data-highlighted={selected}
|
||||
onClick={handleClick}
|
||||
onDoubleClick={handleDoubleClick}
|
||||
onPointerOver={handlePointerOver}
|
||||
onPointerLeave={handlePointerLeave}
|
||||
// Reach-UI tooltip will go out of bounds of parent scroll container.
|
||||
|
||||
@@ -51,6 +51,8 @@
|
||||
background: var(--color-background-selected);
|
||||
}
|
||||
|
||||
.SuspenseScrubberStepHighlight > .SuspenseScrubberBead,
|
||||
.SuspenseScrubberStepHighlight > .SuspenseScrubberBeadSelected,
|
||||
.SuspenseScrubberStep:hover > .SuspenseScrubberBead,
|
||||
.SuspenseScrubberStep:hover > .SuspenseScrubberBeadSelected {
|
||||
height: 0.75rem;
|
||||
|
||||
@@ -18,6 +18,7 @@ export default function SuspenseScrubber({
|
||||
min,
|
||||
max,
|
||||
value,
|
||||
highlight,
|
||||
onBlur,
|
||||
onChange,
|
||||
onFocus,
|
||||
@@ -27,6 +28,7 @@ export default function SuspenseScrubber({
|
||||
min: number,
|
||||
max: number,
|
||||
value: number,
|
||||
highlight: number,
|
||||
onBlur: () => void,
|
||||
onChange: (index: number) => void,
|
||||
onFocus: () => void,
|
||||
@@ -53,7 +55,12 @@ export default function SuspenseScrubber({
|
||||
steps.push(
|
||||
<div
|
||||
key={index}
|
||||
className={styles.SuspenseScrubberStep}
|
||||
className={
|
||||
styles.SuspenseScrubberStep +
|
||||
(highlight === index
|
||||
? ' ' + styles.SuspenseScrubberStepHighlight
|
||||
: '')
|
||||
}
|
||||
onPointerDown={handlePress.bind(null, index)}
|
||||
onMouseEnter={onHoverSegment.bind(null, index)}>
|
||||
<div
|
||||
|
||||
@@ -33,6 +33,7 @@ function SuspenseTimelineInput() {
|
||||
selectedRootID: rootID,
|
||||
timeline,
|
||||
timelineIndex,
|
||||
hoveredTimelineIndex,
|
||||
playing,
|
||||
} = useContext(SuspenseTreeStateContext);
|
||||
|
||||
@@ -202,6 +203,7 @@ function SuspenseTimelineInput() {
|
||||
min={min}
|
||||
max={max}
|
||||
value={timelineIndex}
|
||||
highlight={hoveredTimelineIndex}
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
onFocus={handleFocus}
|
||||
|
||||
@@ -31,6 +31,7 @@ export type SuspenseTreeState = {
|
||||
selectedSuspenseID: SuspenseNode['id'] | null,
|
||||
timeline: $ReadOnlyArray<SuspenseNode['id']>,
|
||||
timelineIndex: number | -1,
|
||||
hoveredTimelineIndex: number | -1,
|
||||
uniqueSuspendersOnly: boolean,
|
||||
playing: boolean,
|
||||
};
|
||||
@@ -72,6 +73,14 @@ type ACTION_SUSPENSE_PLAY_PAUSE = {
|
||||
type ACTION_SUSPENSE_PLAY_TICK = {
|
||||
type: 'SUSPENSE_PLAY_TICK',
|
||||
};
|
||||
type ACTION_TOGGLE_TIMELINE_FOR_ID = {
|
||||
type: 'TOGGLE_TIMELINE_FOR_ID',
|
||||
payload: SuspenseNode['id'],
|
||||
};
|
||||
type ACTION_HOVER_TIMELINE_FOR_ID = {
|
||||
type: 'HOVER_TIMELINE_FOR_ID',
|
||||
payload: SuspenseNode['id'],
|
||||
};
|
||||
|
||||
export type SuspenseTreeAction =
|
||||
| ACTION_SUSPENSE_TREE_MUTATION
|
||||
@@ -81,7 +90,9 @@ export type SuspenseTreeAction =
|
||||
| ACTION_SUSPENSE_SET_TIMELINE_INDEX
|
||||
| ACTION_SUSPENSE_SKIP_TIMELINE_INDEX
|
||||
| ACTION_SUSPENSE_PLAY_PAUSE
|
||||
| ACTION_SUSPENSE_PLAY_TICK;
|
||||
| ACTION_SUSPENSE_PLAY_TICK
|
||||
| ACTION_TOGGLE_TIMELINE_FOR_ID
|
||||
| ACTION_HOVER_TIMELINE_FOR_ID;
|
||||
export type SuspenseTreeDispatch = (action: SuspenseTreeAction) => void;
|
||||
|
||||
const SuspenseTreeStateContext: ReactContext<SuspenseTreeState> =
|
||||
@@ -122,6 +133,7 @@ function getInitialState(store: Store): SuspenseTreeState {
|
||||
selectedRootID,
|
||||
timeline: [],
|
||||
timelineIndex: -1,
|
||||
hoveredTimelineIndex: -1,
|
||||
uniqueSuspendersOnly,
|
||||
playing: false,
|
||||
};
|
||||
@@ -144,6 +156,7 @@ function getInitialState(store: Store): SuspenseTreeState {
|
||||
selectedRootID,
|
||||
timeline,
|
||||
timelineIndex,
|
||||
hoveredTimelineIndex: -1,
|
||||
uniqueSuspendersOnly,
|
||||
playing: false,
|
||||
};
|
||||
@@ -397,6 +410,47 @@ function SuspenseTreeContextController({children}: Props): React.Node {
|
||||
playing: nextPlaying,
|
||||
};
|
||||
}
|
||||
case 'TOGGLE_TIMELINE_FOR_ID': {
|
||||
const suspenseID = action.payload;
|
||||
const timelineIndexForSuspenseID =
|
||||
state.timeline.indexOf(suspenseID);
|
||||
if (timelineIndexForSuspenseID === -1) {
|
||||
// This boundary is no longer in the timeline.
|
||||
return state;
|
||||
}
|
||||
const nextTimelineIndex =
|
||||
timelineIndexForSuspenseID === 0
|
||||
? // For roots, there's no toggling. It's always just jump to beginning.
|
||||
0
|
||||
: // For boundaries, we'll either jump to before or after its reveal depending
|
||||
// on if we're currently displaying it or not according to the timeline.
|
||||
state.timelineIndex < timelineIndexForSuspenseID
|
||||
? // We're currently before this suspense boundary has been revealed so we
|
||||
// should jump ahead to reveal it.
|
||||
timelineIndexForSuspenseID
|
||||
: // Otherwise, if we're currently showing it, jump to right before to hide it.
|
||||
timelineIndexForSuspenseID - 1;
|
||||
const nextSelectedSuspenseID = state.timeline[nextTimelineIndex];
|
||||
const nextLineage = store.getSuspenseLineage(
|
||||
nextSelectedSuspenseID,
|
||||
);
|
||||
return {
|
||||
...state,
|
||||
lineage: nextLineage,
|
||||
selectedSuspenseID: nextSelectedSuspenseID,
|
||||
timelineIndex: nextTimelineIndex,
|
||||
playing: false, // pause
|
||||
};
|
||||
}
|
||||
case 'HOVER_TIMELINE_FOR_ID': {
|
||||
const suspenseID = action.payload;
|
||||
const timelineIndexForSuspenseID =
|
||||
state.timeline.indexOf(suspenseID);
|
||||
return {
|
||||
...state,
|
||||
hoveredTimelineIndex: timelineIndexForSuspenseID,
|
||||
};
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unrecognized action "${action.type}"`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user