Skip to content

Commit 67ff168

Browse files
committed
Add affordance for selected activity slice in Components tab
1 parent 2b94baf commit 67ff168

File tree

10 files changed

+205
-22
lines changed

10 files changed

+205
-22
lines changed

packages/react-devtools-shared/src/backend/fiber/renderer.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ import {
8686
TREE_OPERATION_SET_SUBTREE_MODE,
8787
TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS,
8888
TREE_OPERATION_UPDATE_TREE_BASE_DURATION,
89+
TREE_OPERATION_APPLIED_ACTIVITY_SLICE_CHANGE,
8990
SUSPENSE_TREE_OPERATION_ADD,
9091
SUSPENSE_TREE_OPERATION_REMOVE,
9192
SUSPENSE_TREE_OPERATION_REORDER_CHILDREN,
@@ -1562,6 +1563,12 @@ export function attach(
15621563
currentRoot = (null: any);
15631564
});
15641565
1566+
if (nextActivitySlice !== activitySlice) {
1567+
// Set the applied slice to 0 for now.
1568+
// When we find the applied instance during mount we will send the actual ID.
1569+
pushOperation(TREE_OPERATION_APPLIED_ACTIVITY_SLICE_CHANGE);
1570+
pushOperation(0);
1571+
}
15651572
applyComponentFilters(componentFilters, nextActivitySlice);
15661573
15671574
// Reset pseudo counters so that new path selections will be persisted.
@@ -3979,6 +3986,8 @@ export function attach(
39793986
newInstance = recordMount(fiber, reconcilingParent);
39803987
if (isActivitySliceEntry) {
39813988
activitySliceID = newInstance.id;
3989+
pushOperation(TREE_OPERATION_APPLIED_ACTIVITY_SLICE_CHANGE);
3990+
pushOperation(newInstance.id);
39823991
}
39833992
if (fiber.tag === SuspenseComponent || fiber.tag === HostRoot) {
39843993
newSuspenseNode = createSuspenseNode(newInstance);

packages/react-devtools-shared/src/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export const SUSPENSE_TREE_OPERATION_REMOVE = 9;
2929
export const SUSPENSE_TREE_OPERATION_REORDER_CHILDREN = 10;
3030
export const SUSPENSE_TREE_OPERATION_RESIZE = 11;
3131
export const SUSPENSE_TREE_OPERATION_SUSPENDERS = 12;
32+
export const TREE_OPERATION_APPLIED_ACTIVITY_SLICE_CHANGE = 13;
3233

3334
export const PROFILING_FLAG_BASIC_SUPPORT /*. */ = 0b001;
3435
export const PROFILING_FLAG_TIMELINE_SUPPORT /* */ = 0b010;

packages/react-devtools-shared/src/devtools/store.js

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
TREE_OPERATION_SET_SUBTREE_MODE,
2222
TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS,
2323
TREE_OPERATION_UPDATE_TREE_BASE_DURATION,
24+
TREE_OPERATION_APPLIED_ACTIVITY_SLICE_CHANGE,
2425
SUSPENSE_TREE_OPERATION_ADD,
2526
SUSPENSE_TREE_OPERATION_REMOVE,
2627
SUSPENSE_TREE_OPERATION_REORDER_CHILDREN,
@@ -119,7 +120,13 @@ export default class Store extends EventEmitter<{
119120
hookSettings: [$ReadOnly<DevToolsHookSettings>],
120121
hostInstanceSelected: [Element['id']],
121122
settingsUpdated: [$ReadOnly<DevToolsHookSettings>],
122-
mutated: [[Array<Element['id']>, Map<Element['id'], Element['id']>]],
123+
mutated: [
124+
[
125+
Array<Element['id']>,
126+
Map<Element['id'], Element['id']>,
127+
Element['id'] | null,
128+
],
129+
],
123130
recordChangeDescriptions: [],
124131
roots: [],
125132
rootSupportsBasicProfiling: [],
@@ -1096,7 +1103,7 @@ export default class Store extends EventEmitter<{
10961103
// The Tree context's search reducer expects an explicit list of ids for nodes that were added or removed.
10971104
// In this case, we can pass it empty arrays since nodes in a collapsed tree are still there (just hidden).
10981105
// Updating the selected search index later may require auto-expanding a collapsed subtree though.
1099-
this.emit('mutated', [[], new Map()]);
1106+
this.emit('mutated', [[], new Map(), null]);
11001107
}
11011108
}
11021109
}
@@ -1165,10 +1172,11 @@ export default class Store extends EventEmitter<{
11651172

11661173
const addedElementIDs: Array<number> = [];
11671174
// This is a mapping of removed ID -> parent ID:
1175+
// We'll use the parent ID to adjust selection if it gets deleted.
11681176
const removedElementIDs: Map<number, number> = new Map();
11691177
const removedSuspenseIDs: Map<SuspenseNode['id'], SuspenseNode['id']> =
11701178
new Map();
1171-
// We'll use the parent ID to adjust selection if it gets deleted.
1179+
let nextActivitySliceID = null;
11721180

11731181
let i = 2;
11741182

@@ -1874,6 +1882,11 @@ export default class Store extends EventEmitter<{
18741882

18751883
break;
18761884
}
1885+
case TREE_OPERATION_APPLIED_ACTIVITY_SLICE_CHANGE: {
1886+
i++;
1887+
nextActivitySliceID = operations[i++];
1888+
break;
1889+
}
18771890
default:
18781891
this._throwAndEmitError(
18791892
new UnsupportedBridgeOperationError(
@@ -1972,7 +1985,11 @@ export default class Store extends EventEmitter<{
19721985
console.groupEnd();
19731986
}
19741987

1975-
this.emit('mutated', [addedElementIDs, removedElementIDs]);
1988+
this.emit('mutated', [
1989+
addedElementIDs,
1990+
removedElementIDs,
1991+
nextActivitySliceID,
1992+
]);
19761993
};
19771994

19781995
// Certain backends save filters on a per-domain basis.
@@ -2140,7 +2157,7 @@ export default class Store extends EventEmitter<{
21402157

21412158
if (previousStatus !== status) {
21422159
// Propagate to subscribers, although tree state has not changed
2143-
this.emit('mutated', [[], new Map()]);
2160+
this.emit('mutated', [[], new Map(), null]);
21442161
}
21452162
}
21462163

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
.ActivitySlice {
2+
max-width: 100%;
3+
overflow-x: auto;
4+
flex: 1;
5+
display: flex;
6+
align-items: center;
7+
position: relative;
8+
}
9+
10+
.ActivitySliceButton {
11+
color: var(--color-button-active);
12+
font-family: var(--font-family-monospace);
13+
font-size: var(--font-size-monospace-normal);
14+
}
15+
16+
.Bar {
17+
display: flex;
18+
flex: 1 1 auto;
19+
overflow-x: auto;
20+
}
21+
22+
.VRule {
23+
flex: 0 0 auto;
24+
height: 20px;
25+
width: 1px;
26+
background-color: var(--color-border);
27+
margin: 0 0.5rem;
28+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
import * as React from 'react';
10+
import {startTransition, useContext} from 'react';
11+
import Button from '../Button';
12+
import ButtonIcon from '../ButtonIcon';
13+
import {StoreContext} from '../context';
14+
import {useChangeActivitySliceAction} from '../SuspenseTab/ActivityList';
15+
import {TreeDispatcherContext, TreeStateContext} from './TreeContext';
16+
import styles from './ActivitySlice.css';
17+
18+
export default function ActivitySlice(): React.Node {
19+
const dispatch = useContext(TreeDispatcherContext);
20+
const {activityID} = useContext(TreeStateContext);
21+
const store = useContext(StoreContext);
22+
23+
const activity =
24+
activityID === null ? null : store.getElementByID(activityID);
25+
const name = activity ? activity.nameProp : null;
26+
27+
const changeActivitySliceAction = useChangeActivitySliceAction();
28+
29+
return (
30+
<div className={styles.ActivitySlice}>
31+
<div className={styles.Bar}>
32+
<Button
33+
className={styles.ActivitySliceButton}
34+
onClick={dispatch.bind(null, {
35+
type: 'SELECT_ELEMENT_BY_ID',
36+
payload: activityID,
37+
})}>
38+
"{name || 'Unknown'}"
39+
</Button>
40+
</div>
41+
<div className={styles.VRule} />
42+
<Button
43+
onClick={startTransition.bind(
44+
null,
45+
changeActivitySliceAction.bind(null, null),
46+
)}
47+
title="Back to tree view">
48+
<ButtonIcon type="close" />
49+
</Button>
50+
</div>
51+
);
52+
}

packages/react-devtools-shared/src/devtools/views/Components/Tree.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {useExtensionComponentsPanelVisibility} from 'react-devtools-shared/src/f
4141
import {ElementTypeActivity} from 'react-devtools-shared/src/frontend/types';
4242
import {useChangeOwnerAction} from './OwnersListContext';
4343
import {useChangeActivitySliceAction} from '../SuspenseTab/ActivityList';
44+
import ActivitySlice from './ActivitySlice';
4445

4546
// Indent for each node at level N, compared to node at level N - 1.
4647
const INDENTATION_SIZE = 10;
@@ -75,6 +76,7 @@ function calculateInitialScrollOffset(
7576
export default function Tree(): React.Node {
7677
const dispatch = useContext(TreeDispatcherContext);
7778
const {
79+
activityID,
7880
numElements,
7981
ownerID,
8082
searchIndex,
@@ -458,7 +460,13 @@ export default function Tree(): React.Node {
458460
</Fragment>
459461
)}
460462
<Suspense fallback={<Loading />}>
461-
{ownerID !== null ? <OwnersStack /> : <ComponentSearchInput />}
463+
{ownerID !== null ? (
464+
<OwnersStack />
465+
) : activityID !== null ? (
466+
<ActivitySlice />
467+
) : (
468+
<ComponentSearchInput />
469+
)}
462470
</Suspense>
463471
{ownerID === null && (errors > 0 || warnings > 0) && (
464472
<React.Fragment>

packages/react-devtools-shared/src/devtools/views/Components/TreeContext.js

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ export type StateContext = {
5757
ownerID: number | null,
5858
ownerFlatTree: Array<Element> | null,
5959

60+
// Activity slice
61+
activityID: Element['id'] | null,
62+
6063
// Inspection element panel
6164
inspectedElementID: number | null,
6265
inspectedElementIndex: number | null,
@@ -70,7 +73,7 @@ type ACTION_GO_TO_PREVIOUS_SEARCH_RESULT = {
7073
};
7174
type ACTION_HANDLE_STORE_MUTATION = {
7275
type: 'HANDLE_STORE_MUTATION',
73-
payload: [Array<number>, Map<number, number>],
76+
payload: [Array<number>, Map<number, number>, null | Element['id']],
7477
};
7578
type ACTION_RESET_OWNER_STACK = {
7679
type: 'RESET_OWNER_STACK',
@@ -167,6 +170,9 @@ type State = {
167170
ownerID: number | null,
168171
ownerFlatTree: Array<Element> | null,
169172

173+
// Activity slice
174+
activityID: Element['id'] | null,
175+
170176
// Inspection element panel
171177
inspectedElementID: number | null,
172178
inspectedElementIndex: number | null,
@@ -794,6 +800,33 @@ function reduceOwnersState(store: Store, state: State, action: Action): State {
794800
};
795801
}
796802

803+
function reduceActivityState(
804+
store: Store,
805+
state: State,
806+
action: Action,
807+
): State {
808+
switch (action.type) {
809+
case 'HANDLE_STORE_MUTATION':
810+
let {activityID} = state;
811+
const [, , activitySliceIDChange] = action.payload;
812+
if (activitySliceIDChange === 0 && activityID !== null) {
813+
activityID = null;
814+
} else if (
815+
activitySliceIDChange !== null &&
816+
activitySliceIDChange !== activityID
817+
) {
818+
activityID = activitySliceIDChange;
819+
}
820+
if (activityID !== state.activityID) {
821+
return {
822+
...state,
823+
activityID,
824+
};
825+
}
826+
}
827+
return state;
828+
}
829+
797830
type Props = {
798831
children: React$Node,
799832

@@ -828,6 +861,9 @@ function getInitialState({
828861
ownerID: defaultOwnerID == null ? null : defaultOwnerID,
829862
ownerFlatTree: null,
830863

864+
// Activity slice
865+
activityID: null,
866+
831867
// Inspection element panel
832868
inspectedElementID:
833869
defaultInspectedElementID != null
@@ -882,6 +918,7 @@ function TreeContextController({
882918
state = reduceTreeState(store, state, action);
883919
state = reduceSearchState(store, state, action);
884920
state = reduceOwnersState(store, state, action);
921+
state = reduceActivityState(store, state, action);
885922

886923
// TODO(hoxyq): review
887924
// If the selected ID is in a collapsed subtree, reset the selected index to null.
@@ -950,13 +987,14 @@ function TreeContextController({
950987

951988
// Mutations to the underlying tree may impact this context (e.g. search results, selection state).
952989
useEffect(() => {
953-
const handleStoreMutated = ([addedElementIDs, removedElementIDs]: [
954-
Array<number>,
955-
Map<number, number>,
956-
]) => {
990+
const handleStoreMutated = ([
991+
addedElementIDs,
992+
removedElementIDs,
993+
activitySliceIDChange,
994+
]: [Array<number>, Map<number, number>, null | Element['id']]) => {
957995
dispatch({
958996
type: 'HANDLE_STORE_MUTATION',
959-
payload: [addedElementIDs, removedElementIDs],
997+
payload: [addedElementIDs, removedElementIDs, activitySliceIDChange],
960998
});
961999
};
9621000

@@ -967,7 +1005,7 @@ function TreeContextController({
9671005
// It would only impact the search state, which is unlikely to exist yet at this point.
9681006
dispatch({
9691007
type: 'HANDLE_STORE_MUTATION',
970-
payload: [[], new Map()],
1008+
payload: [[], new Map(), null],
9711009
});
9721010
}
9731011

packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
TREE_OPERATION_SET_SUBTREE_MODE,
1717
TREE_OPERATION_UPDATE_TREE_BASE_DURATION,
1818
TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS,
19+
TREE_OPERATION_APPLIED_ACTIVITY_SLICE_CHANGE,
1920
SUSPENSE_TREE_OPERATION_ADD,
2021
SUSPENSE_TREE_OPERATION_REMOVE,
2122
SUSPENSE_TREE_OPERATION_REORDER_CHILDREN,
@@ -474,6 +475,20 @@ function updateTree(
474475
break;
475476
}
476477

478+
case TREE_OPERATION_APPLIED_ACTIVITY_SLICE_CHANGE: {
479+
i++;
480+
const activitySliceIDChange = operations[i++];
481+
if (__DEBUG__) {
482+
debug(
483+
'Applied activity slice change',
484+
activitySliceIDChange === 0
485+
? 'Reset applied activity slice'
486+
: `Changed to activity slice ID ${activitySliceIDChange}`,
487+
);
488+
}
489+
break;
490+
}
491+
477492
default:
478493
throw Error(`Unsupported Bridge operation "${operation}"`);
479494
}

0 commit comments

Comments
 (0)