Skip to content

Commit c38e017

Browse files
author
Brian Vaughn
committed
Highlight current search result in Timeline
1 parent 68f39c6 commit c38e017

File tree

5 files changed

+74
-11
lines changed

5 files changed

+74
-11
lines changed

packages/react-devtools-timeline/src/CanvasPage.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -174,12 +174,9 @@ function AutoSizedCanvas({
174174
const {searchIndex, searchResults} = useContext(TimelineSearchContext);
175175

176176
useLayoutEffect(() => {
177-
if (searchResults.length === 0) {
178-
return;
179-
}
180-
181-
const componentMeasure = searchResults[searchIndex];
182-
if (componentMeasure !== null) {
177+
const componentMeasure =
178+
searchResults.length > 0 ? searchResults[searchIndex] : null;
179+
if (componentMeasure != null) {
183180
const scrollState = moveStateToRange({
184181
state: viewState.horizontalScrollState,
185182
rangeStart: componentMeasure.timestamp,
@@ -191,8 +188,11 @@ function AutoSizedCanvas({
191188
});
192189

193190
viewState.updateHorizontalScrollState(scrollState);
191+
viewState.updateSearchResult({componentMeasure});
194192

195193
surfaceRef.current.displayIfNeeded();
194+
} else {
195+
viewState.updateSearchResult(null);
196196
}
197197
}, [searchIndex, searchResults, viewState]);
198198

@@ -360,6 +360,7 @@ function AutoSizedCanvas({
360360
surface,
361361
defaultFrame,
362362
data,
363+
viewState,
363364
);
364365
componentMeasuresViewRef.current = componentMeasuresView;
365366
componentMeasuresViewWrapper = createViewHelper(

packages/react-devtools-timeline/src/TimelineContext.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@
1010
import * as React from 'react';
1111
import {createContext, useMemo, useRef, useState} from 'react';
1212

13-
import type {HorizontalScrollStateChangeCallback, ViewState} from './types';
13+
import type {
14+
HorizontalScrollStateChangeCallback,
15+
SearchResult,
16+
SearchResultStateChangeCallback,
17+
ViewState,
18+
} from './types';
1419
import type {RefObject} from 'shared/ReactTypes';
1520

1621
export type Context = {|
@@ -34,17 +39,22 @@ function TimelineContextController({children}: Props) {
3439
// Recreate view state any time new profiling data is imported.
3540
const viewState = useMemo<ViewState>(() => {
3641
const horizontalScrollStateChangeCallbacks: Set<HorizontalScrollStateChangeCallback> = new Set();
42+
const searchResultStateChangeCallbacks: Set<SearchResultStateChangeCallback> = new Set();
3743

3844
const horizontalScrollState = {
3945
offset: 0,
4046
length: 0,
4147
};
4248

43-
return {
49+
const state: ViewState = {
4450
horizontalScrollState,
4551
onHorizontalScrollStateChange: callback => {
4652
horizontalScrollStateChangeCallbacks.add(callback);
4753
},
54+
onSearchResultStateChange: callback => {
55+
searchResultStateChangeCallbacks.add(callback);
56+
},
57+
searchResult: null,
4858
updateHorizontalScrollState: scrollState => {
4959
if (
5060
horizontalScrollState.offset === scrollState.offset &&
@@ -60,8 +70,17 @@ function TimelineContextController({children}: Props) {
6070
callback(scrollState);
6171
});
6272
},
73+
updateSearchResult: (searchResult: SearchResult | null) => {
74+
state.searchResult = searchResult;
75+
76+
searchResultStateChangeCallbacks.forEach(callback => {
77+
callback(searchResult);
78+
});
79+
},
6380
viewToMutableViewStateMap: new Map(),
6481
};
82+
83+
return state;
6584
}, [file]);
6685

6786
const value = useMemo(

packages/react-devtools-timeline/src/content-views/ComponentMeasuresView.js

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@
77
* @flow
88
*/
99

10-
import type {ReactComponentMeasure, ReactProfilerData} from '../types';
10+
import type {
11+
ReactComponentMeasure,
12+
ReactProfilerData,
13+
SearchResult,
14+
ViewState,
15+
} from '../types';
1116
import type {
1217
Interaction,
1318
IntrinsicSize,
@@ -31,20 +36,28 @@ import {
3136
rectIntersectsRect,
3237
intersectionOfRects,
3338
} from '../view-base';
34-
import {COLORS, NATIVE_EVENT_HEIGHT, BORDER_SIZE} from './constants';
39+
import {BORDER_SIZE, COLORS, NATIVE_EVENT_HEIGHT} from './constants';
3540

3641
const ROW_WITH_BORDER_HEIGHT = NATIVE_EVENT_HEIGHT + BORDER_SIZE;
3742

3843
export class ComponentMeasuresView extends View {
3944
_hoveredComponentMeasure: ReactComponentMeasure | null = null;
4045
_intrinsicSize: IntrinsicSize;
4146
_profilerData: ReactProfilerData;
47+
_searchResult: ReactComponentMeasure | null = null;
4248

4349
onHover: ((event: ReactComponentMeasure | null) => void) | null = null;
4450

45-
constructor(surface: Surface, frame: Rect, profilerData: ReactProfilerData) {
51+
constructor(
52+
surface: Surface,
53+
frame: Rect,
54+
profilerData: ReactProfilerData,
55+
viewState: ViewState,
56+
) {
4657
super(surface, frame);
4758

59+
viewState.onSearchResultStateChange(this._handleSearchResultStateChange);
60+
4861
this._profilerData = profilerData;
4962

5063
this._intrinsicSize = {
@@ -74,6 +87,7 @@ export class ComponentMeasuresView extends View {
7487
componentMeasure: ReactComponentMeasure,
7588
scaleFactor: number,
7689
showHoverHighlight: boolean,
90+
showSearchResultBorder: boolean,
7791
): boolean {
7892
const {frame} = this;
7993
const {
@@ -150,6 +164,11 @@ export class ComponentMeasuresView extends View {
150164
break;
151165
}
152166
}
167+
168+
if (showSearchResultBorder) {
169+
context.fillStyle = COLORS.SEARCH_RESULT_FILL;
170+
}
171+
153172
context.fillRect(
154173
drawableRect.origin.x,
155174
drawableRect.origin.y,
@@ -171,6 +190,7 @@ export class ComponentMeasuresView extends View {
171190
frame,
172191
_profilerData: {componentMeasures},
173192
_hoveredComponentMeasure,
193+
_searchResult,
174194
visibleArea,
175195
} = this;
176196

@@ -197,6 +217,7 @@ export class ComponentMeasuresView extends View {
197217
componentMeasure,
198218
scaleFactor,
199219
componentMeasure === _hoveredComponentMeasure,
220+
componentMeasure === _searchResult,
200221
) || didDrawMeasure;
201222
});
202223

@@ -256,6 +277,11 @@ export class ComponentMeasuresView extends View {
256277
onHover(null);
257278
}
258279

280+
_handleSearchResultStateChange = (searchResult: SearchResult | null) => {
281+
this._searchResult =
282+
searchResult === null ? null : searchResult.componentMeasure;
283+
};
284+
259285
handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
260286
switch (interaction.type) {
261287
case 'mousemove':

packages/react-devtools-timeline/src/content-views/constants.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export let COLORS = {
9191
SCROLL_CARET: '',
9292
SCRUBBER_BACKGROUND: '',
9393
SCRUBBER_BORDER: '',
94+
SEARCH_RESULT_FILL: '',
9495
TEXT_COLOR: '',
9596
TEXT_DIM_COLOR: '',
9697
TIME_MARKER_LABEL: '',
@@ -235,6 +236,9 @@ export function updateColorsToMatchTheme(element: Element): boolean {
235236
SCRUBBER_BACKGROUND: computedStyle.getPropertyValue(
236237
'--color-timeline-react-suspense-rejected',
237238
),
239+
SEARCH_RESULT_FILL: computedStyle.getPropertyValue(
240+
'--color-timeline-react-suspense-rejected',
241+
),
238242
SCRUBBER_BORDER: computedStyle.getPropertyValue(
239243
'--color-timeline-text-color',
240244
),

packages/react-devtools-timeline/src/types.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,10 +167,18 @@ export type FlamechartStackLayer = FlamechartStackFrame[];
167167

168168
export type Flamechart = FlamechartStackLayer[];
169169

170+
export type SearchResult = {|
171+
componentMeasure: ReactComponentMeasure | null,
172+
|};
173+
170174
export type HorizontalScrollStateChangeCallback = (
171175
scrollState: ScrollState,
172176
) => void;
173177

178+
export type SearchResultStateChangeCallback = (
179+
searchResult: SearchResult | null,
180+
) => void;
181+
174182
// Imperative view state that corresponds to profiler data.
175183
// This state lives outside of React's lifecycle
176184
// and should be erased/reset whenever new profiler data is loaded.
@@ -179,7 +187,12 @@ export type ViewState = {|
179187
onHorizontalScrollStateChange: (
180188
callback: HorizontalScrollStateChangeCallback,
181189
) => void,
190+
onSearchResultStateChange: (
191+
callback: SearchResultStateChangeCallback,
192+
) => void,
193+
searchResult: SearchResult | null,
182194
updateHorizontalScrollState: (scrollState: ScrollState) => void,
195+
updateSearchResult: (searchResult: SearchResult | null) => void,
183196
viewToMutableViewStateMap: Map<string, mixed>,
184197
|};
185198

0 commit comments

Comments
 (0)