mirror of
https://github.com/hanzoai/react.git
synced 2026-09-24 17:02:27 +00:00
DevTools: merge element fields in TreeStateContext (#31956)
Stacked on https://github.com/facebook/react/pull/31892, see commit on top. For some reason, there were 2 fields different fields for essentially same thing: `selectedElementID` and `inspectedElementID`. Basically, the change is: ``` selectedElementID -> inspectedElementID selectedElementIndex -> inspectedElementIndex ``` I have a theory that it was due to previously used async approach around element inspection, and the whole `InspectedElementView` was wrapped in `Suspense`.
This commit is contained in:
@@ -119,7 +119,7 @@ test.describe('Components', () => {
|
||||
runOnlyForReactRange('>=16.8');
|
||||
|
||||
// Select the first list item in DevTools.
|
||||
await devToolsUtils.selectElement(page, 'ListItem', 'List\nApp');
|
||||
await devToolsUtils.selectElement(page, 'ListItem', 'List\nApp', true);
|
||||
|
||||
// Then read the inspected values.
|
||||
const sourceText = await page.evaluate(() => {
|
||||
@@ -127,7 +127,7 @@ test.describe('Components', () => {
|
||||
const container = document.getElementById('devtools');
|
||||
|
||||
const source = findAllNodes(container, [
|
||||
createTestNameSelector('InspectedElementView-Source'),
|
||||
createTestNameSelector('InspectedElementView-FormattedSourceString'),
|
||||
])[0];
|
||||
|
||||
return source.innerText;
|
||||
@@ -237,35 +237,35 @@ test.describe('Components', () => {
|
||||
}
|
||||
|
||||
await focusComponentSearch();
|
||||
page.keyboard.insertText('List');
|
||||
await page.keyboard.insertText('List');
|
||||
let count = await getComponentSearchResultsCount();
|
||||
expect(count).toBe('1 | 4');
|
||||
|
||||
page.keyboard.insertText('Item');
|
||||
await page.keyboard.insertText('Item');
|
||||
count = await getComponentSearchResultsCount();
|
||||
expect(count).toBe('1 | 3');
|
||||
|
||||
page.keyboard.press('Enter');
|
||||
await page.keyboard.press('Enter');
|
||||
count = await getComponentSearchResultsCount();
|
||||
expect(count).toBe('2 | 3');
|
||||
|
||||
page.keyboard.press('Enter');
|
||||
await page.keyboard.press('Enter');
|
||||
count = await getComponentSearchResultsCount();
|
||||
expect(count).toBe('3 | 3');
|
||||
|
||||
page.keyboard.press('Enter');
|
||||
await page.keyboard.press('Enter');
|
||||
count = await getComponentSearchResultsCount();
|
||||
expect(count).toBe('1 | 3');
|
||||
|
||||
page.keyboard.press('Shift+Enter');
|
||||
await page.keyboard.press('Shift+Enter');
|
||||
count = await getComponentSearchResultsCount();
|
||||
expect(count).toBe('3 | 3');
|
||||
|
||||
page.keyboard.press('Shift+Enter');
|
||||
await page.keyboard.press('Shift+Enter');
|
||||
count = await getComponentSearchResultsCount();
|
||||
expect(count).toBe('2 | 3');
|
||||
|
||||
page.keyboard.press('Shift+Enter');
|
||||
await page.keyboard.press('Shift+Enter');
|
||||
count = await getComponentSearchResultsCount();
|
||||
expect(count).toBe('1 | 3');
|
||||
});
|
||||
|
||||
@@ -27,7 +27,12 @@ async function getElementCount(page, displayName) {
|
||||
}, displayName);
|
||||
}
|
||||
|
||||
async function selectElement(page, displayName, waitForOwnersText) {
|
||||
async function selectElement(
|
||||
page,
|
||||
displayName,
|
||||
waitForOwnersText,
|
||||
waitForSourceLoaded = false
|
||||
) {
|
||||
await page.evaluate(listItemText => {
|
||||
const {createTestNameSelector, createTextSelector, findAllNodes} =
|
||||
window.REACT_DOM_DEVTOOLS;
|
||||
@@ -69,6 +74,20 @@ async function selectElement(page, displayName, waitForOwnersText) {
|
||||
{titleText: displayName, ownersListText: waitForOwnersText}
|
||||
);
|
||||
}
|
||||
|
||||
if (waitForSourceLoaded) {
|
||||
await page.waitForFunction(() => {
|
||||
const {createTestNameSelector, findAllNodes} = window.REACT_DOM_DEVTOOLS;
|
||||
const container = document.getElementById('devtools');
|
||||
|
||||
const sourceStringBlock = findAllNodes(container, [
|
||||
createTestNameSelector('InspectedElementView-FormattedSourceString'),
|
||||
])[0];
|
||||
|
||||
// Wait for a new source line to be fetched
|
||||
return sourceStringBlock != null && sourceStringBlock.innerText != null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -115,16 +115,15 @@ describe('InspectedElement', () => {
|
||||
|
||||
const Contexts = ({
|
||||
children,
|
||||
defaultSelectedElementID = null,
|
||||
defaultSelectedElementIndex = null,
|
||||
defaultInspectedElementID = null,
|
||||
defaultInspectedElementIndex = null,
|
||||
}) => (
|
||||
<BridgeContext.Provider value={bridge}>
|
||||
<StoreContext.Provider value={store}>
|
||||
<SettingsContextController>
|
||||
<TreeContextController
|
||||
defaultSelectedElementID={defaultSelectedElementID}
|
||||
defaultSelectedElementIndex={defaultSelectedElementIndex}
|
||||
defaultInspectedElementID={defaultSelectedElementID}>
|
||||
defaultInspectedElementID={defaultInspectedElementID}
|
||||
defaultInspectedElementIndex={defaultInspectedElementIndex}>
|
||||
<InspectedElementContextController>
|
||||
{children}
|
||||
</InspectedElementContextController>
|
||||
@@ -167,8 +166,8 @@ describe('InspectedElement', () => {
|
||||
testRendererInstance.update(
|
||||
<ErrorBoundary>
|
||||
<Contexts
|
||||
defaultSelectedElementID={id}
|
||||
defaultSelectedElementIndex={index}>
|
||||
defaultInspectedElementID={id}
|
||||
defaultInspectedElementIndex={index}>
|
||||
<React.Suspense fallback={null}>
|
||||
<Suspender id={id} index={index} />
|
||||
</React.Suspense>
|
||||
@@ -355,7 +354,7 @@ describe('InspectedElement', () => {
|
||||
const {index, shouldHaveLegacyContext} = cases[i];
|
||||
|
||||
// HACK: Recreate TestRenderer instance because we rely on default state values
|
||||
// from props like defaultSelectedElementID and it's easier to reset here than
|
||||
// from props like defaultInspectedElementID and it's easier to reset here than
|
||||
// to read the TreeDispatcherContext and update the selected ID that way.
|
||||
// We're testing the inspected values here, not the context wiring, so that's ok.
|
||||
withErrorsOrWarningsIgnored(
|
||||
@@ -2069,7 +2068,7 @@ describe('InspectedElement', () => {
|
||||
}, false);
|
||||
|
||||
// HACK: Recreate TestRenderer instance because we rely on default state values
|
||||
// from props like defaultSelectedElementID and it's easier to reset here than
|
||||
// from props like defaultInspectedElementID and it's easier to reset here than
|
||||
// to read the TreeDispatcherContext and update the selected ID that way.
|
||||
// We're testing the inspected values here, not the context wiring, so that's ok.
|
||||
withErrorsOrWarningsIgnored(
|
||||
@@ -2129,7 +2128,7 @@ describe('InspectedElement', () => {
|
||||
}, false);
|
||||
|
||||
// HACK: Recreate TestRenderer instance because we rely on default state values
|
||||
// from props like defaultSelectedElementID and it's easier to reset here than
|
||||
// from props like defaultInspectedElementID and it's easier to reset here than
|
||||
// to read the TreeDispatcherContext and update the selected ID that way.
|
||||
// We're testing the inspected values here, not the context wiring, so that's ok.
|
||||
withErrorsOrWarningsIgnored(
|
||||
@@ -2408,8 +2407,8 @@ describe('InspectedElement', () => {
|
||||
await utils.actAsync(() => {
|
||||
root = TestRenderer.create(
|
||||
<Contexts
|
||||
defaultSelectedElementID={id}
|
||||
defaultSelectedElementIndex={index}>
|
||||
defaultInspectedElementID={id}
|
||||
defaultInspectedElementIndex={index}>
|
||||
<React.Suspense fallback={null}>
|
||||
<Suspender target={id} />
|
||||
</React.Suspense>
|
||||
@@ -3101,6 +3100,7 @@ describe('InspectedElement', () => {
|
||||
|
||||
await utils.actAsync(() => {
|
||||
store.componentFilters = [utils.createDisplayNameFilter('Wrapper')];
|
||||
jest.runOnlyPendingTimers();
|
||||
}, false);
|
||||
|
||||
expect(state).toMatchInlineSnapshot(`
|
||||
@@ -3120,6 +3120,7 @@ describe('InspectedElement', () => {
|
||||
|
||||
await utils.actAsync(() => {
|
||||
store.componentFilters = [];
|
||||
jest.runOnlyPendingTimers();
|
||||
}, false);
|
||||
expect(state).toMatchInlineSnapshot(`
|
||||
✕ 0, ⚠ 2
|
||||
|
||||
@@ -69,14 +69,14 @@ describe('ProfilerContext', () => {
|
||||
|
||||
const Contexts = ({
|
||||
children = null,
|
||||
defaultSelectedElementID = null,
|
||||
defaultSelectedElementIndex = null,
|
||||
defaultInspectedElementID = null,
|
||||
defaultInspectedElementIndex = null,
|
||||
}: any) => (
|
||||
<BridgeContext.Provider value={bridge}>
|
||||
<StoreContext.Provider value={store}>
|
||||
<TreeContextController
|
||||
defaultSelectedElementID={defaultSelectedElementID}
|
||||
defaultSelectedElementIndex={defaultSelectedElementIndex}>
|
||||
defaultInspectedElementID={defaultInspectedElementID}
|
||||
defaultInspectedElementIndex={defaultInspectedElementIndex}>
|
||||
<ProfilerContextController>{children}</ProfilerContextController>
|
||||
</TreeContextController>
|
||||
</StoreContext.Provider>
|
||||
@@ -225,8 +225,8 @@ describe('ProfilerContext', () => {
|
||||
await utils.actAsync(() =>
|
||||
TestRenderer.create(
|
||||
<Contexts
|
||||
defaultSelectedElementID={store.getElementIDAtIndex(3)}
|
||||
defaultSelectedElementIndex={3}>
|
||||
defaultInspectedElementID={store.getElementIDAtIndex(3)}
|
||||
defaultInspectedElementIndex={3}>
|
||||
<ContextReader />
|
||||
</Contexts>,
|
||||
),
|
||||
@@ -276,8 +276,8 @@ describe('ProfilerContext', () => {
|
||||
await utils.actAsync(() =>
|
||||
TestRenderer.create(
|
||||
<Contexts
|
||||
defaultSelectedElementID={store.getElementIDAtIndex(3)}
|
||||
defaultSelectedElementIndex={3}>
|
||||
defaultInspectedElementID={store.getElementIDAtIndex(3)}
|
||||
defaultInspectedElementIndex={3}>
|
||||
<ContextReader />
|
||||
</Contexts>,
|
||||
),
|
||||
@@ -323,8 +323,8 @@ describe('ProfilerContext', () => {
|
||||
await utils.actAsync(() =>
|
||||
TestRenderer.create(
|
||||
<Contexts
|
||||
defaultSelectedElementID={store.getElementIDAtIndex(3)}
|
||||
defaultSelectedElementIndex={3}>
|
||||
defaultInspectedElementID={store.getElementIDAtIndex(3)}
|
||||
defaultInspectedElementIndex={3}>
|
||||
<ContextReader />
|
||||
</Contexts>,
|
||||
),
|
||||
@@ -374,8 +374,8 @@ describe('ProfilerContext', () => {
|
||||
await utils.actAsync(() =>
|
||||
TestRenderer.create(
|
||||
<Contexts
|
||||
defaultSelectedElementID={store.getElementIDAtIndex(3)}
|
||||
defaultSelectedElementIndex={3}>
|
||||
defaultInspectedElementID={store.getElementIDAtIndex(3)}
|
||||
defaultInspectedElementIndex={3}>
|
||||
<ContextReader />
|
||||
</Contexts>,
|
||||
),
|
||||
@@ -415,11 +415,12 @@ describe('ProfilerContext', () => {
|
||||
|
||||
let context: Context = ((null: any): Context);
|
||||
let dispatch: DispatcherContext = ((null: any): DispatcherContext);
|
||||
let selectedElementID = null;
|
||||
let inspectedElementID = null;
|
||||
function ContextReader() {
|
||||
context = React.useContext(ProfilerContext);
|
||||
dispatch = React.useContext(TreeDispatcherContext);
|
||||
selectedElementID = React.useContext(TreeStateContext).selectedElementID;
|
||||
inspectedElementID =
|
||||
React.useContext(TreeStateContext).inspectedElementID;
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -428,13 +429,15 @@ describe('ProfilerContext', () => {
|
||||
// Select an element within the second root.
|
||||
await utils.actAsync(() =>
|
||||
TestRenderer.create(
|
||||
<Contexts defaultSelectedElementID={id} defaultSelectedElementIndex={3}>
|
||||
<Contexts
|
||||
defaultInspectedElementID={id}
|
||||
defaultInspectedElementIndex={3}>
|
||||
<ContextReader />
|
||||
</Contexts>,
|
||||
),
|
||||
);
|
||||
|
||||
expect(selectedElementID).toBe(id);
|
||||
expect(inspectedElementID).toBe(id);
|
||||
|
||||
// Profile and record more updates to both roots
|
||||
await utils.actAsync(() => store.profilerStore.startProfiling());
|
||||
@@ -448,7 +451,7 @@ describe('ProfilerContext', () => {
|
||||
utils.act(() => dispatch({type: 'SELECT_ELEMENT_AT_INDEX', payload: 0}));
|
||||
|
||||
// Verify that the initial Profiler root selection is maintained.
|
||||
expect(selectedElementID).toBe(otherID);
|
||||
expect(inspectedElementID).toBe(otherID);
|
||||
expect(context).not.toBeNull();
|
||||
expect(context.rootID).toBe(store.getRootIDForElement(id));
|
||||
});
|
||||
@@ -484,11 +487,12 @@ describe('ProfilerContext', () => {
|
||||
|
||||
let context: Context = ((null: any): Context);
|
||||
let dispatch: DispatcherContext = ((null: any): DispatcherContext);
|
||||
let selectedElementID = null;
|
||||
let inspectedElementID = null;
|
||||
function ContextReader() {
|
||||
context = React.useContext(ProfilerContext);
|
||||
dispatch = React.useContext(TreeDispatcherContext);
|
||||
selectedElementID = React.useContext(TreeStateContext).selectedElementID;
|
||||
inspectedElementID =
|
||||
React.useContext(TreeStateContext).inspectedElementID;
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -497,13 +501,15 @@ describe('ProfilerContext', () => {
|
||||
// Select an element within the second root.
|
||||
await utils.actAsync(() =>
|
||||
TestRenderer.create(
|
||||
<Contexts defaultSelectedElementID={id} defaultSelectedElementIndex={3}>
|
||||
<Contexts
|
||||
defaultInspectedElementID={id}
|
||||
defaultInspectedElementIndex={3}>
|
||||
<ContextReader />
|
||||
</Contexts>,
|
||||
),
|
||||
);
|
||||
|
||||
expect(selectedElementID).toBe(id);
|
||||
expect(inspectedElementID).toBe(id);
|
||||
|
||||
// Profile and record more updates to both roots
|
||||
await utils.actAsync(() => store.profilerStore.startProfiling());
|
||||
@@ -517,7 +523,7 @@ describe('ProfilerContext', () => {
|
||||
utils.act(() => dispatch({type: 'SELECT_ELEMENT_AT_INDEX', payload: 0}));
|
||||
|
||||
// Verify that the initial Profiler root selection is maintained.
|
||||
expect(selectedElementID).toBe(otherID);
|
||||
expect(inspectedElementID).toBe(otherID);
|
||||
expect(context).not.toBeNull();
|
||||
expect(context.rootID).toBe(store.getRootIDForElement(id));
|
||||
});
|
||||
@@ -553,10 +559,11 @@ describe('ProfilerContext', () => {
|
||||
`);
|
||||
|
||||
let context: Context = ((null: any): Context);
|
||||
let selectedElementID = null;
|
||||
let inspectedElementID = null;
|
||||
function ContextReader() {
|
||||
context = React.useContext(ProfilerContext);
|
||||
selectedElementID = React.useContext(TreeStateContext).selectedElementID;
|
||||
inspectedElementID =
|
||||
React.useContext(TreeStateContext).inspectedElementID;
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -567,14 +574,14 @@ describe('ProfilerContext', () => {
|
||||
</Contexts>,
|
||||
),
|
||||
);
|
||||
expect(selectedElementID).toBeNull();
|
||||
expect(inspectedElementID).toBeNull();
|
||||
|
||||
// Select an element in the Profiler tab and verify that the selection is synced to the Components tab.
|
||||
await utils.actAsync(() => context.selectFiber(parentID, 'Parent'));
|
||||
expect(selectedElementID).toBe(parentID);
|
||||
expect(inspectedElementID).toBe(parentID);
|
||||
|
||||
// Select an unmounted element and verify no Components tab selection doesn't change.
|
||||
await utils.actAsync(() => context.selectFiber(childID, 'Child'));
|
||||
expect(selectedElementID).toBe(parentID);
|
||||
expect(inspectedElementID).toBe(parentID);
|
||||
});
|
||||
});
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@ export function printStore(
|
||||
if (state === null) {
|
||||
return '';
|
||||
}
|
||||
return state.selectedElementIndex === index ? `→` : ' ';
|
||||
return state.inspectedElementIndex === index ? `→` : ' ';
|
||||
}
|
||||
|
||||
function printErrorsAndWarnings(element: Element): string {
|
||||
|
||||
@@ -33,7 +33,7 @@ type Props = {
|
||||
|
||||
export default function Element({data, index, style}: Props): React.Node {
|
||||
const store = useContext(StoreContext);
|
||||
const {ownerFlatTree, ownerID, selectedElementID} =
|
||||
const {ownerFlatTree, ownerID, inspectedElementID} =
|
||||
useContext(TreeStateContext);
|
||||
const dispatch = useContext(TreeDispatcherContext);
|
||||
|
||||
@@ -46,7 +46,7 @@ export default function Element({data, index, style}: Props): React.Node {
|
||||
|
||||
const {isNavigatingWithKeyboard, onElementMouseEnter, treeFocused} = data;
|
||||
const id = element === null ? null : element.id;
|
||||
const isSelected = selectedElementID === id;
|
||||
const isSelected = inspectedElementID === id;
|
||||
|
||||
const errorsAndWarningsSubscription = useMemo(
|
||||
() => ({
|
||||
|
||||
Vendored
+7
-3
@@ -28,7 +28,7 @@ function InspectedElementSourcePanel({
|
||||
symbolicatedSourcePromise,
|
||||
}: Props): React.Node {
|
||||
return (
|
||||
<div data-testname="InspectedElementView-Source">
|
||||
<div>
|
||||
<div className={styles.SourceHeaderRow}>
|
||||
<div className={styles.SourceHeader}>source</div>
|
||||
|
||||
@@ -84,7 +84,9 @@ function FormattedSourceString({source, symbolicatedSourcePromise}: Props) {
|
||||
const {sourceURL, line} = source;
|
||||
|
||||
return (
|
||||
<div className={styles.SourceOneLiner}>
|
||||
<div
|
||||
className={styles.SourceOneLiner}
|
||||
data-testname="InspectedElementView-FormattedSourceString">
|
||||
{formatSourceForDisplay(sourceURL, line)}
|
||||
</div>
|
||||
);
|
||||
@@ -93,7 +95,9 @@ function FormattedSourceString({source, symbolicatedSourcePromise}: Props) {
|
||||
const {sourceURL, line} = symbolicatedSource;
|
||||
|
||||
return (
|
||||
<div className={styles.SourceOneLiner}>
|
||||
<div
|
||||
className={styles.SourceOneLiner}
|
||||
data-testname="InspectedElementView-FormattedSourceString">
|
||||
{formatSourceForDisplay(sourceURL, line)}
|
||||
</div>
|
||||
);
|
||||
|
||||
+9
-9
@@ -100,10 +100,10 @@ function NativeStyleContextController({children}: Props): React.Node {
|
||||
[store],
|
||||
);
|
||||
|
||||
// It's very important that this context consumes selectedElementID and not NativeStyleID.
|
||||
// It's very important that this context consumes inspectedElementID and not NativeStyleID.
|
||||
// Otherwise the effect that sends the "inspect" message across the bridge-
|
||||
// would itself be blocked by the same render that suspends (waiting for the data).
|
||||
const {selectedElementID} = useContext<StateContext>(TreeStateContext);
|
||||
const {inspectedElementID} = useContext<StateContext>(TreeStateContext);
|
||||
|
||||
const [currentStyleAndLayout, setCurrentStyleAndLayout] =
|
||||
useState<StyleAndLayoutFrontend | null>(null);
|
||||
@@ -128,7 +128,7 @@ function NativeStyleContextController({children}: Props): React.Node {
|
||||
resource.write(element, styleAndLayout);
|
||||
|
||||
// Schedule update with React if the currently-selected element has been invalidated.
|
||||
if (id === selectedElementID) {
|
||||
if (id === inspectedElementID) {
|
||||
setCurrentStyleAndLayout(styleAndLayout);
|
||||
}
|
||||
}
|
||||
@@ -141,15 +141,15 @@ function NativeStyleContextController({children}: Props): React.Node {
|
||||
'NativeStyleEditor_styleAndLayout',
|
||||
onStyleAndLayout,
|
||||
);
|
||||
}, [bridge, currentStyleAndLayout, selectedElementID, store]);
|
||||
}, [bridge, currentStyleAndLayout, inspectedElementID, store]);
|
||||
|
||||
// This effect handler polls for updates on the currently selected element.
|
||||
useEffect(() => {
|
||||
if (selectedElementID === null) {
|
||||
if (inspectedElementID === null) {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
const rendererID = store.getRendererIDForElement(selectedElementID);
|
||||
const rendererID = store.getRendererIDForElement(inspectedElementID);
|
||||
|
||||
let timeoutID: TimeoutID | null = null;
|
||||
|
||||
@@ -158,7 +158,7 @@ function NativeStyleContextController({children}: Props): React.Node {
|
||||
|
||||
if (rendererID !== null) {
|
||||
bridge.send('NativeStyleEditor_measure', {
|
||||
id: selectedElementID,
|
||||
id: inspectedElementID,
|
||||
rendererID,
|
||||
});
|
||||
}
|
||||
@@ -170,7 +170,7 @@ function NativeStyleContextController({children}: Props): React.Node {
|
||||
|
||||
const onStyleAndLayout = ({id}: StyleAndLayoutBackend) => {
|
||||
// If this is the element we requested, wait a little bit and then ask for another update.
|
||||
if (id === selectedElementID) {
|
||||
if (id === inspectedElementID) {
|
||||
if (timeoutID !== null) {
|
||||
clearTimeout(timeoutID);
|
||||
}
|
||||
@@ -190,7 +190,7 @@ function NativeStyleContextController({children}: Props): React.Node {
|
||||
clearTimeout(timeoutID);
|
||||
}
|
||||
};
|
||||
}, [bridge, selectedElementID, store]);
|
||||
}, [bridge, inspectedElementID, store]);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({getStyleAndLayout}),
|
||||
|
||||
+5
-5
@@ -28,19 +28,19 @@ export default function SelectedTreeHighlight(_: {}): React.Node {
|
||||
const {lineHeight} = useContext(SettingsContext);
|
||||
const store = useContext(StoreContext);
|
||||
const treeFocused = useContext(TreeFocusedContext);
|
||||
const {ownerID, selectedElementID} = useContext(TreeStateContext);
|
||||
const {ownerID, inspectedElementID} = useContext(TreeStateContext);
|
||||
|
||||
const subscription = useMemo(
|
||||
() => ({
|
||||
getCurrentValue: () => {
|
||||
if (
|
||||
selectedElementID === null ||
|
||||
store.isInsideCollapsedSubTree(selectedElementID)
|
||||
inspectedElementID === null ||
|
||||
store.isInsideCollapsedSubTree(inspectedElementID)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const element = store.getElementByID(selectedElementID);
|
||||
const element = store.getElementByID(inspectedElementID);
|
||||
if (
|
||||
element === null ||
|
||||
element.isCollapsed ||
|
||||
@@ -83,7 +83,7 @@ export default function SelectedTreeHighlight(_: {}): React.Node {
|
||||
};
|
||||
},
|
||||
}),
|
||||
[selectedElementID, store],
|
||||
[inspectedElementID, store],
|
||||
);
|
||||
const data = useSubscription<Data | null>(subscription);
|
||||
|
||||
|
||||
@@ -54,8 +54,8 @@ export default function Tree(): React.Node {
|
||||
ownerID,
|
||||
searchIndex,
|
||||
searchResults,
|
||||
selectedElementID,
|
||||
selectedElementIndex,
|
||||
inspectedElementID,
|
||||
inspectedElementIndex,
|
||||
} = useContext(TreeStateContext);
|
||||
const bridge = useContext(BridgeContext);
|
||||
const store = useContext(StoreContext);
|
||||
@@ -84,11 +84,11 @@ export default function Tree(): React.Node {
|
||||
// Using a callback ref accounts for this case...
|
||||
const listCallbackRef = useCallback(
|
||||
(list: $FlowFixMe) => {
|
||||
if (list != null && selectedElementIndex !== null) {
|
||||
list.scrollToItem(selectedElementIndex, 'smart');
|
||||
if (list != null && inspectedElementIndex !== null) {
|
||||
list.scrollToItem(inspectedElementIndex, 'smart');
|
||||
}
|
||||
},
|
||||
[selectedElementIndex],
|
||||
[inspectedElementIndex],
|
||||
);
|
||||
|
||||
// Picking an element in the inspector should put focus into the tree.
|
||||
@@ -133,8 +133,8 @@ export default function Tree(): React.Node {
|
||||
case 'ArrowLeft':
|
||||
event.preventDefault();
|
||||
element =
|
||||
selectedElementID !== null
|
||||
? store.getElementByID(selectedElementID)
|
||||
inspectedElementID !== null
|
||||
? store.getElementByID(inspectedElementID)
|
||||
: null;
|
||||
if (element !== null) {
|
||||
if (event.altKey) {
|
||||
@@ -153,8 +153,8 @@ export default function Tree(): React.Node {
|
||||
case 'ArrowRight':
|
||||
event.preventDefault();
|
||||
element =
|
||||
selectedElementID !== null
|
||||
? store.getElementByID(selectedElementID)
|
||||
inspectedElementID !== null
|
||||
? store.getElementByID(inspectedElementID)
|
||||
: null;
|
||||
if (element !== null) {
|
||||
if (event.altKey) {
|
||||
@@ -202,7 +202,7 @@ export default function Tree(): React.Node {
|
||||
return () => {
|
||||
container.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [dispatch, selectedElementID, store]);
|
||||
}, [dispatch, inspectedElementID, store]);
|
||||
|
||||
// Focus management.
|
||||
const handleBlur = useCallback(() => setTreeFocused(false), []);
|
||||
@@ -213,15 +213,15 @@ export default function Tree(): React.Node {
|
||||
switch (event.key) {
|
||||
case 'Enter':
|
||||
case ' ':
|
||||
if (selectedElementID !== null) {
|
||||
dispatch({type: 'SELECT_OWNER', payload: selectedElementID});
|
||||
if (inspectedElementID !== null) {
|
||||
dispatch({type: 'SELECT_OWNER', payload: inspectedElementID});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
[dispatch, selectedElementID],
|
||||
[dispatch, inspectedElementID],
|
||||
);
|
||||
|
||||
// If we switch the selected element while using the keyboard,
|
||||
@@ -238,8 +238,8 @@ export default function Tree(): React.Node {
|
||||
didSelectNewSearchResult = true;
|
||||
}
|
||||
if (isNavigatingWithKeyboard || didSelectNewSearchResult) {
|
||||
if (selectedElementID !== null) {
|
||||
highlightHostInstance(selectedElementID);
|
||||
if (inspectedElementID !== null) {
|
||||
highlightHostInstance(inspectedElementID);
|
||||
} else {
|
||||
clearHighlightHostInstance();
|
||||
}
|
||||
@@ -250,7 +250,7 @@ export default function Tree(): React.Node {
|
||||
highlightHostInstance,
|
||||
searchIndex,
|
||||
searchResults,
|
||||
selectedElementID,
|
||||
inspectedElementID,
|
||||
]);
|
||||
|
||||
// Highlight last hovered element.
|
||||
|
||||
+145
-189
@@ -29,14 +29,12 @@ import type {ReactContext} from 'shared/ReactTypes';
|
||||
import * as React from 'react';
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useReducer,
|
||||
useRef,
|
||||
startTransition,
|
||||
} from 'react';
|
||||
import {createRegExp} from '../utils';
|
||||
import {StoreContext} from '../context';
|
||||
@@ -48,8 +46,6 @@ export type StateContext = {
|
||||
// Tree
|
||||
numElements: number,
|
||||
ownerSubtreeLeafElementID: number | null,
|
||||
selectedElementID: number | null,
|
||||
selectedElementIndex: number | null,
|
||||
|
||||
// Search
|
||||
searchIndex: number | null,
|
||||
@@ -62,6 +58,7 @@ export type StateContext = {
|
||||
|
||||
// Inspection element panel
|
||||
inspectedElementID: number | null,
|
||||
inspectedElementIndex: number | null,
|
||||
};
|
||||
|
||||
type ACTION_GO_TO_NEXT_SEARCH_RESULT = {
|
||||
@@ -123,9 +120,6 @@ type ACTION_SET_SEARCH_TEXT = {
|
||||
type: 'SET_SEARCH_TEXT',
|
||||
payload: string,
|
||||
};
|
||||
type ACTION_UPDATE_INSPECTED_ELEMENT_ID = {
|
||||
type: 'UPDATE_INSPECTED_ELEMENT_ID',
|
||||
};
|
||||
|
||||
type Action =
|
||||
| ACTION_GO_TO_NEXT_SEARCH_RESULT
|
||||
@@ -145,8 +139,7 @@ type Action =
|
||||
| ACTION_SELECT_PREVIOUS_SIBLING_IN_TREE
|
||||
| ACTION_SELECT_OWNER_LIST_NEXT_ELEMENT_IN_TREE
|
||||
| ACTION_SELECT_OWNER_LIST_PREVIOUS_ELEMENT_IN_TREE
|
||||
| ACTION_SET_SEARCH_TEXT
|
||||
| ACTION_UPDATE_INSPECTED_ELEMENT_ID;
|
||||
| ACTION_SET_SEARCH_TEXT;
|
||||
|
||||
export type DispatcherContext = (action: Action) => void;
|
||||
|
||||
@@ -162,8 +155,6 @@ type State = {
|
||||
// Tree
|
||||
numElements: number,
|
||||
ownerSubtreeLeafElementID: number | null,
|
||||
selectedElementID: number | null,
|
||||
selectedElementIndex: number | null,
|
||||
|
||||
// Search
|
||||
searchIndex: number | null,
|
||||
@@ -176,14 +167,15 @@ type State = {
|
||||
|
||||
// Inspection element panel
|
||||
inspectedElementID: number | null,
|
||||
inspectedElementIndex: number | null,
|
||||
};
|
||||
|
||||
function reduceTreeState(store: Store, state: State, action: Action): State {
|
||||
let {
|
||||
numElements,
|
||||
ownerSubtreeLeafElementID,
|
||||
selectedElementIndex,
|
||||
selectedElementID,
|
||||
inspectedElementID,
|
||||
inspectedElementIndex,
|
||||
} = state;
|
||||
const ownerID = state.ownerID;
|
||||
|
||||
@@ -201,34 +193,33 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
|
||||
// We deduce the parent-child mapping from removedIDs (id -> parentID)
|
||||
// because by now it's too late to read them from the store.
|
||||
while (
|
||||
selectedElementID !== null &&
|
||||
removedIDs.has(selectedElementID)
|
||||
inspectedElementID !== null &&
|
||||
removedIDs.has(inspectedElementID)
|
||||
) {
|
||||
selectedElementID = ((removedIDs.get(
|
||||
selectedElementID,
|
||||
): any): number);
|
||||
// $FlowExpectedError[incompatible-type]
|
||||
inspectedElementID = removedIDs.get(inspectedElementID);
|
||||
}
|
||||
if (selectedElementID === 0) {
|
||||
if (inspectedElementID === 0) {
|
||||
// The whole root was removed.
|
||||
selectedElementIndex = null;
|
||||
inspectedElementIndex = null;
|
||||
}
|
||||
break;
|
||||
case 'SELECT_CHILD_ELEMENT_IN_TREE':
|
||||
ownerSubtreeLeafElementID = null;
|
||||
|
||||
if (selectedElementIndex !== null) {
|
||||
const selectedElement = store.getElementAtIndex(
|
||||
((selectedElementIndex: any): number),
|
||||
if (inspectedElementIndex !== null) {
|
||||
const inspectedElement = store.getElementAtIndex(
|
||||
inspectedElementIndex,
|
||||
);
|
||||
if (
|
||||
selectedElement !== null &&
|
||||
selectedElement.children.length > 0 &&
|
||||
!selectedElement.isCollapsed
|
||||
inspectedElement !== null &&
|
||||
inspectedElement.children.length > 0 &&
|
||||
!inspectedElement.isCollapsed
|
||||
) {
|
||||
const firstChildID = selectedElement.children[0];
|
||||
const firstChildID = inspectedElement.children[0];
|
||||
const firstChildIndex = store.getIndexOfElementID(firstChildID);
|
||||
if (firstChildIndex !== null) {
|
||||
selectedElementIndex = firstChildIndex;
|
||||
inspectedElementIndex = firstChildIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -236,7 +227,8 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
|
||||
case 'SELECT_ELEMENT_AT_INDEX':
|
||||
ownerSubtreeLeafElementID = null;
|
||||
|
||||
selectedElementIndex = (action: ACTION_SELECT_ELEMENT_AT_INDEX).payload;
|
||||
inspectedElementIndex = (action: ACTION_SELECT_ELEMENT_AT_INDEX)
|
||||
.payload;
|
||||
break;
|
||||
case 'SELECT_ELEMENT_BY_ID':
|
||||
ownerSubtreeLeafElementID = null;
|
||||
@@ -245,30 +237,30 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
|
||||
// It might also cause problems if the specified element was inside of a (not yet expanded) subtree.
|
||||
lookupIDForIndex = false;
|
||||
|
||||
selectedElementID = (action: ACTION_SELECT_ELEMENT_BY_ID).payload;
|
||||
selectedElementIndex =
|
||||
selectedElementID === null
|
||||
inspectedElementID = (action: ACTION_SELECT_ELEMENT_BY_ID).payload;
|
||||
inspectedElementIndex =
|
||||
inspectedElementID === null
|
||||
? null
|
||||
: store.getIndexOfElementID(selectedElementID);
|
||||
: store.getIndexOfElementID(inspectedElementID);
|
||||
break;
|
||||
case 'SELECT_NEXT_ELEMENT_IN_TREE':
|
||||
ownerSubtreeLeafElementID = null;
|
||||
|
||||
if (
|
||||
selectedElementIndex === null ||
|
||||
selectedElementIndex + 1 >= numElements
|
||||
inspectedElementIndex === null ||
|
||||
inspectedElementIndex + 1 >= numElements
|
||||
) {
|
||||
selectedElementIndex = 0;
|
||||
inspectedElementIndex = 0;
|
||||
} else {
|
||||
selectedElementIndex++;
|
||||
inspectedElementIndex++;
|
||||
}
|
||||
break;
|
||||
case 'SELECT_NEXT_SIBLING_IN_TREE':
|
||||
ownerSubtreeLeafElementID = null;
|
||||
|
||||
if (selectedElementIndex !== null) {
|
||||
if (inspectedElementIndex !== null) {
|
||||
const selectedElement = store.getElementAtIndex(
|
||||
((selectedElementIndex: any): number),
|
||||
((inspectedElementIndex: any): number),
|
||||
);
|
||||
if (selectedElement !== null && selectedElement.parentID !== 0) {
|
||||
const parent = store.getElementByID(selectedElement.parentID);
|
||||
@@ -279,23 +271,23 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
|
||||
selectedChildIndex < children.length - 1
|
||||
? children[selectedChildIndex + 1]
|
||||
: children[0];
|
||||
selectedElementIndex = store.getIndexOfElementID(nextChildID);
|
||||
inspectedElementIndex = store.getIndexOfElementID(nextChildID);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'SELECT_OWNER_LIST_NEXT_ELEMENT_IN_TREE':
|
||||
if (selectedElementIndex !== null) {
|
||||
if (inspectedElementIndex !== null) {
|
||||
if (
|
||||
ownerSubtreeLeafElementID !== null &&
|
||||
ownerSubtreeLeafElementID !== selectedElementID
|
||||
ownerSubtreeLeafElementID !== inspectedElementID
|
||||
) {
|
||||
const leafElement = store.getElementByID(ownerSubtreeLeafElementID);
|
||||
if (leafElement !== null) {
|
||||
let currentElement: null | Element = leafElement;
|
||||
while (currentElement !== null) {
|
||||
if (currentElement.ownerID === selectedElementID) {
|
||||
selectedElementIndex = store.getIndexOfElementID(
|
||||
if (currentElement.ownerID === inspectedElementID) {
|
||||
inspectedElementIndex = store.getIndexOfElementID(
|
||||
currentElement.id,
|
||||
);
|
||||
break;
|
||||
@@ -308,23 +300,23 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
|
||||
}
|
||||
break;
|
||||
case 'SELECT_OWNER_LIST_PREVIOUS_ELEMENT_IN_TREE':
|
||||
if (selectedElementIndex !== null) {
|
||||
if (inspectedElementIndex !== null) {
|
||||
if (ownerSubtreeLeafElementID === null) {
|
||||
// If this is the first time we're stepping through the owners tree,
|
||||
// pin the current component as the owners list leaf.
|
||||
// This will enable us to step back down to this component.
|
||||
ownerSubtreeLeafElementID = selectedElementID;
|
||||
ownerSubtreeLeafElementID = inspectedElementID;
|
||||
}
|
||||
|
||||
const selectedElement = store.getElementAtIndex(
|
||||
((selectedElementIndex: any): number),
|
||||
((inspectedElementIndex: any): number),
|
||||
);
|
||||
if (selectedElement !== null && selectedElement.ownerID !== 0) {
|
||||
const ownerIndex = store.getIndexOfElementID(
|
||||
selectedElement.ownerID,
|
||||
);
|
||||
if (ownerIndex !== null) {
|
||||
selectedElementIndex = ownerIndex;
|
||||
inspectedElementIndex = ownerIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -332,16 +324,16 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
|
||||
case 'SELECT_PARENT_ELEMENT_IN_TREE':
|
||||
ownerSubtreeLeafElementID = null;
|
||||
|
||||
if (selectedElementIndex !== null) {
|
||||
if (inspectedElementIndex !== null) {
|
||||
const selectedElement = store.getElementAtIndex(
|
||||
((selectedElementIndex: any): number),
|
||||
((inspectedElementIndex: any): number),
|
||||
);
|
||||
if (selectedElement !== null && selectedElement.parentID !== 0) {
|
||||
const parentIndex = store.getIndexOfElementID(
|
||||
selectedElement.parentID,
|
||||
);
|
||||
if (parentIndex !== null) {
|
||||
selectedElementIndex = parentIndex;
|
||||
inspectedElementIndex = parentIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -349,18 +341,18 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
|
||||
case 'SELECT_PREVIOUS_ELEMENT_IN_TREE':
|
||||
ownerSubtreeLeafElementID = null;
|
||||
|
||||
if (selectedElementIndex === null || selectedElementIndex === 0) {
|
||||
selectedElementIndex = numElements - 1;
|
||||
if (inspectedElementIndex === null || inspectedElementIndex === 0) {
|
||||
inspectedElementIndex = numElements - 1;
|
||||
} else {
|
||||
selectedElementIndex--;
|
||||
inspectedElementIndex--;
|
||||
}
|
||||
break;
|
||||
case 'SELECT_PREVIOUS_SIBLING_IN_TREE':
|
||||
ownerSubtreeLeafElementID = null;
|
||||
|
||||
if (selectedElementIndex !== null) {
|
||||
if (inspectedElementIndex !== null) {
|
||||
const selectedElement = store.getElementAtIndex(
|
||||
((selectedElementIndex: any): number),
|
||||
((inspectedElementIndex: any): number),
|
||||
);
|
||||
if (selectedElement !== null && selectedElement.parentID !== 0) {
|
||||
const parent = store.getElementByID(selectedElement.parentID);
|
||||
@@ -371,7 +363,7 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
|
||||
selectedChildIndex > 0
|
||||
? children[selectedChildIndex - 1]
|
||||
: children[children.length - 1];
|
||||
selectedElementIndex = store.getIndexOfElementID(nextChildID);
|
||||
inspectedElementIndex = store.getIndexOfElementID(nextChildID);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -384,7 +376,7 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
|
||||
}
|
||||
|
||||
let flatIndex = 0;
|
||||
if (selectedElementIndex !== null) {
|
||||
if (inspectedElementIndex !== null) {
|
||||
// Resume from the current position in the list.
|
||||
// Otherwise step to the previous item, relative to the current selection.
|
||||
for (
|
||||
@@ -393,7 +385,7 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
|
||||
i--
|
||||
) {
|
||||
const {index} = elementIndicesWithErrorsOrWarnings[i];
|
||||
if (index >= selectedElementIndex) {
|
||||
if (index >= inspectedElementIndex) {
|
||||
flatIndex = i;
|
||||
} else {
|
||||
break;
|
||||
@@ -407,12 +399,12 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
|
||||
elementIndicesWithErrorsOrWarnings[
|
||||
elementIndicesWithErrorsOrWarnings.length - 1
|
||||
];
|
||||
selectedElementID = prevEntry.id;
|
||||
selectedElementIndex = prevEntry.index;
|
||||
inspectedElementID = prevEntry.id;
|
||||
inspectedElementIndex = prevEntry.index;
|
||||
} else {
|
||||
prevEntry = elementIndicesWithErrorsOrWarnings[flatIndex - 1];
|
||||
selectedElementID = prevEntry.id;
|
||||
selectedElementIndex = prevEntry.index;
|
||||
inspectedElementID = prevEntry.id;
|
||||
inspectedElementIndex = prevEntry.index;
|
||||
}
|
||||
|
||||
lookupIDForIndex = false;
|
||||
@@ -426,12 +418,12 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
|
||||
}
|
||||
|
||||
let flatIndex = -1;
|
||||
if (selectedElementIndex !== null) {
|
||||
if (inspectedElementIndex !== null) {
|
||||
// Resume from the current position in the list.
|
||||
// Otherwise step to the next item, relative to the current selection.
|
||||
for (let i = 0; i < elementIndicesWithErrorsOrWarnings.length; i++) {
|
||||
const {index} = elementIndicesWithErrorsOrWarnings[i];
|
||||
if (index <= selectedElementIndex) {
|
||||
if (index <= inspectedElementIndex) {
|
||||
flatIndex = i;
|
||||
} else {
|
||||
break;
|
||||
@@ -442,12 +434,12 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
|
||||
let nextEntry;
|
||||
if (flatIndex >= elementIndicesWithErrorsOrWarnings.length - 1) {
|
||||
nextEntry = elementIndicesWithErrorsOrWarnings[0];
|
||||
selectedElementID = nextEntry.id;
|
||||
selectedElementIndex = nextEntry.index;
|
||||
inspectedElementID = nextEntry.id;
|
||||
inspectedElementIndex = nextEntry.index;
|
||||
} else {
|
||||
nextEntry = elementIndicesWithErrorsOrWarnings[flatIndex + 1];
|
||||
selectedElementID = nextEntry.id;
|
||||
selectedElementIndex = nextEntry.index;
|
||||
inspectedElementID = nextEntry.id;
|
||||
inspectedElementIndex = nextEntry.index;
|
||||
}
|
||||
|
||||
lookupIDForIndex = false;
|
||||
@@ -460,12 +452,15 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
|
||||
}
|
||||
|
||||
// Keep selected item ID and index in sync.
|
||||
if (lookupIDForIndex && selectedElementIndex !== state.selectedElementIndex) {
|
||||
if (selectedElementIndex === null) {
|
||||
selectedElementID = null;
|
||||
if (
|
||||
lookupIDForIndex &&
|
||||
inspectedElementIndex !== state.inspectedElementIndex
|
||||
) {
|
||||
if (inspectedElementIndex === null) {
|
||||
inspectedElementID = null;
|
||||
} else {
|
||||
selectedElementID = store.getElementIDAtIndex(
|
||||
((selectedElementIndex: any): number),
|
||||
inspectedElementID = store.getElementIDAtIndex(
|
||||
((inspectedElementIndex: any): number),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -475,8 +470,8 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
|
||||
|
||||
numElements,
|
||||
ownerSubtreeLeafElementID,
|
||||
selectedElementIndex,
|
||||
selectedElementID,
|
||||
inspectedElementIndex,
|
||||
inspectedElementID,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -485,8 +480,8 @@ function reduceSearchState(store: Store, state: State, action: Action): State {
|
||||
searchIndex,
|
||||
searchResults,
|
||||
searchText,
|
||||
selectedElementID,
|
||||
selectedElementIndex,
|
||||
inspectedElementID,
|
||||
inspectedElementIndex,
|
||||
} = state;
|
||||
const ownerID = state.ownerID;
|
||||
|
||||
@@ -594,11 +589,11 @@ function reduceSearchState(store: Store, state: State, action: Action): State {
|
||||
});
|
||||
if (searchResults.length > 0) {
|
||||
if (prevSearchIndex === null) {
|
||||
if (selectedElementIndex !== null) {
|
||||
if (inspectedElementIndex !== null) {
|
||||
searchIndex = getNearestResultIndex(
|
||||
store,
|
||||
searchResults,
|
||||
selectedElementIndex,
|
||||
inspectedElementIndex,
|
||||
);
|
||||
} else {
|
||||
searchIndex = 0;
|
||||
@@ -619,7 +614,7 @@ function reduceSearchState(store: Store, state: State, action: Action): State {
|
||||
}
|
||||
|
||||
if (searchText !== prevSearchText) {
|
||||
const newSearchIndex = searchResults.indexOf(selectedElementID);
|
||||
const newSearchIndex = searchResults.indexOf(inspectedElementID);
|
||||
if (newSearchIndex === -1) {
|
||||
// Only move the selection if the new query
|
||||
// doesn't match the current selection anymore.
|
||||
@@ -631,17 +626,17 @@ function reduceSearchState(store: Store, state: State, action: Action): State {
|
||||
}
|
||||
}
|
||||
if (didRequestSearch && searchIndex !== null) {
|
||||
selectedElementID = ((searchResults[searchIndex]: any): number);
|
||||
selectedElementIndex = store.getIndexOfElementID(
|
||||
((selectedElementID: any): number),
|
||||
inspectedElementID = ((searchResults[searchIndex]: any): number);
|
||||
inspectedElementIndex = store.getIndexOfElementID(
|
||||
((inspectedElementID: any): number),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
|
||||
selectedElementID,
|
||||
selectedElementIndex,
|
||||
inspectedElementID,
|
||||
inspectedElementIndex,
|
||||
|
||||
searchIndex,
|
||||
searchResults,
|
||||
@@ -652,14 +647,14 @@ function reduceSearchState(store: Store, state: State, action: Action): State {
|
||||
function reduceOwnersState(store: Store, state: State, action: Action): State {
|
||||
let {
|
||||
numElements,
|
||||
selectedElementID,
|
||||
selectedElementIndex,
|
||||
ownerID,
|
||||
ownerFlatTree,
|
||||
inspectedElementID,
|
||||
inspectedElementIndex,
|
||||
} = state;
|
||||
const {searchIndex, searchResults, searchText} = state;
|
||||
|
||||
let prevSelectedElementIndex = selectedElementIndex;
|
||||
let prevInspectedElementIndex = inspectedElementIndex;
|
||||
|
||||
switch (action.type) {
|
||||
case 'HANDLE_STORE_MUTATION':
|
||||
@@ -667,75 +662,76 @@ function reduceOwnersState(store: Store, state: State, action: Action): State {
|
||||
if (!store.containsElement(ownerID)) {
|
||||
ownerID = null;
|
||||
ownerFlatTree = null;
|
||||
selectedElementID = null;
|
||||
prevInspectedElementIndex = null;
|
||||
} else {
|
||||
ownerFlatTree = store.getOwnersListForElement(ownerID);
|
||||
if (selectedElementID !== null) {
|
||||
if (inspectedElementID !== null) {
|
||||
// Mutation might have caused the index of this ID to shift.
|
||||
selectedElementIndex = ownerFlatTree.findIndex(
|
||||
element => element.id === selectedElementID,
|
||||
prevInspectedElementIndex = ownerFlatTree.findIndex(
|
||||
element => element.id === inspectedElementID,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (selectedElementID !== null) {
|
||||
if (inspectedElementID !== null) {
|
||||
// Mutation might have caused the index of this ID to shift.
|
||||
selectedElementIndex = store.getIndexOfElementID(selectedElementID);
|
||||
inspectedElementIndex = store.getIndexOfElementID(inspectedElementID);
|
||||
}
|
||||
}
|
||||
if (selectedElementIndex === -1) {
|
||||
if (inspectedElementIndex === -1) {
|
||||
// If we couldn't find this ID after mutation, unselect it.
|
||||
selectedElementIndex = null;
|
||||
selectedElementID = null;
|
||||
inspectedElementIndex = null;
|
||||
inspectedElementID = null;
|
||||
}
|
||||
break;
|
||||
case 'RESET_OWNER_STACK':
|
||||
ownerID = null;
|
||||
ownerFlatTree = null;
|
||||
selectedElementIndex =
|
||||
selectedElementID !== null
|
||||
? store.getIndexOfElementID(selectedElementID)
|
||||
inspectedElementIndex =
|
||||
inspectedElementID !== null
|
||||
? store.getIndexOfElementID(inspectedElementID)
|
||||
: null;
|
||||
break;
|
||||
case 'SELECT_ELEMENT_AT_INDEX':
|
||||
if (ownerFlatTree !== null) {
|
||||
selectedElementIndex = (action: ACTION_SELECT_ELEMENT_AT_INDEX).payload;
|
||||
inspectedElementIndex = (action: ACTION_SELECT_ELEMENT_AT_INDEX)
|
||||
.payload;
|
||||
}
|
||||
break;
|
||||
case 'SELECT_ELEMENT_BY_ID':
|
||||
if (ownerFlatTree !== null) {
|
||||
const payload = (action: ACTION_SELECT_ELEMENT_BY_ID).payload;
|
||||
if (payload === null) {
|
||||
selectedElementIndex = null;
|
||||
inspectedElementIndex = null;
|
||||
} else {
|
||||
selectedElementIndex = ownerFlatTree.findIndex(
|
||||
inspectedElementIndex = ownerFlatTree.findIndex(
|
||||
element => element.id === payload,
|
||||
);
|
||||
|
||||
// If the selected element is outside of the current owners list,
|
||||
// exit the list and select the element in the main tree.
|
||||
// This supports features like toggling Suspense.
|
||||
if (selectedElementIndex !== null && selectedElementIndex < 0) {
|
||||
if (inspectedElementIndex !== null && inspectedElementIndex < 0) {
|
||||
ownerID = null;
|
||||
ownerFlatTree = null;
|
||||
selectedElementIndex = store.getIndexOfElementID(payload);
|
||||
inspectedElementIndex = store.getIndexOfElementID(payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'SELECT_NEXT_ELEMENT_IN_TREE':
|
||||
if (ownerFlatTree !== null && ownerFlatTree.length > 0) {
|
||||
if (selectedElementIndex === null) {
|
||||
selectedElementIndex = 0;
|
||||
} else if (selectedElementIndex + 1 < ownerFlatTree.length) {
|
||||
selectedElementIndex++;
|
||||
if (inspectedElementIndex === null) {
|
||||
inspectedElementIndex = 0;
|
||||
} else if (inspectedElementIndex + 1 < ownerFlatTree.length) {
|
||||
inspectedElementIndex++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'SELECT_PREVIOUS_ELEMENT_IN_TREE':
|
||||
if (ownerFlatTree !== null && ownerFlatTree.length > 0) {
|
||||
if (selectedElementIndex !== null && selectedElementIndex > 0) {
|
||||
selectedElementIndex--;
|
||||
if (inspectedElementIndex !== null && inspectedElementIndex > 0) {
|
||||
inspectedElementIndex--;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -747,8 +743,8 @@ function reduceOwnersState(store: Store, state: State, action: Action): State {
|
||||
ownerFlatTree = store.getOwnersListForElement(ownerID);
|
||||
|
||||
// Always force reset selection to be the top of the new owner tree.
|
||||
selectedElementIndex = 0;
|
||||
prevSelectedElementIndex = null;
|
||||
inspectedElementIndex = 0;
|
||||
prevInspectedElementIndex = null;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -769,12 +765,12 @@ function reduceOwnersState(store: Store, state: State, action: Action): State {
|
||||
}
|
||||
|
||||
// Keep selected item ID and index in sync.
|
||||
if (selectedElementIndex !== prevSelectedElementIndex) {
|
||||
if (selectedElementIndex === null) {
|
||||
selectedElementID = null;
|
||||
if (inspectedElementIndex !== prevInspectedElementIndex) {
|
||||
if (inspectedElementIndex === null) {
|
||||
inspectedElementID = null;
|
||||
} else {
|
||||
if (ownerFlatTree !== null) {
|
||||
selectedElementID = ownerFlatTree[selectedElementIndex].id;
|
||||
inspectedElementID = ownerFlatTree[inspectedElementIndex].id;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -783,8 +779,6 @@ function reduceOwnersState(store: Store, state: State, action: Action): State {
|
||||
...state,
|
||||
|
||||
numElements,
|
||||
selectedElementID,
|
||||
selectedElementIndex,
|
||||
|
||||
searchIndex,
|
||||
searchResults,
|
||||
@@ -792,49 +786,27 @@ function reduceOwnersState(store: Store, state: State, action: Action): State {
|
||||
|
||||
ownerID,
|
||||
ownerFlatTree,
|
||||
|
||||
inspectedElementID,
|
||||
inspectedElementIndex,
|
||||
};
|
||||
}
|
||||
|
||||
function reduceSuspenseState(
|
||||
store: Store,
|
||||
state: State,
|
||||
action: Action,
|
||||
): State {
|
||||
const {type} = action;
|
||||
switch (type) {
|
||||
case 'UPDATE_INSPECTED_ELEMENT_ID':
|
||||
if (state.inspectedElementID !== state.selectedElementID) {
|
||||
return {
|
||||
...state,
|
||||
inspectedElementID: state.selectedElementID,
|
||||
};
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// React can bailout of no-op updates.
|
||||
return state;
|
||||
}
|
||||
|
||||
type Props = {
|
||||
children: React$Node,
|
||||
|
||||
// Used for automated testing
|
||||
defaultInspectedElementID?: ?number,
|
||||
defaultOwnerID?: ?number,
|
||||
defaultSelectedElementID?: ?number,
|
||||
defaultSelectedElementIndex?: ?number,
|
||||
defaultInspectedElementID?: ?number,
|
||||
defaultInspectedElementIndex?: ?number,
|
||||
};
|
||||
|
||||
// TODO Remove TreeContextController wrapper element once global Context.write API exists.
|
||||
function TreeContextController({
|
||||
children,
|
||||
defaultInspectedElementID,
|
||||
defaultOwnerID,
|
||||
defaultSelectedElementID,
|
||||
defaultSelectedElementIndex,
|
||||
defaultInspectedElementID,
|
||||
defaultInspectedElementIndex,
|
||||
}: Props): React.Node {
|
||||
const store = useContext(StoreContext);
|
||||
|
||||
@@ -865,23 +837,22 @@ function TreeContextController({
|
||||
case 'SELECT_PREVIOUS_ELEMENT_WITH_ERROR_OR_WARNING_IN_TREE':
|
||||
case 'SELECT_PREVIOUS_SIBLING_IN_TREE':
|
||||
case 'SELECT_OWNER':
|
||||
case 'UPDATE_INSPECTED_ELEMENT_ID':
|
||||
case 'SET_SEARCH_TEXT':
|
||||
state = reduceTreeState(store, state, action);
|
||||
state = reduceSearchState(store, state, action);
|
||||
state = reduceOwnersState(store, state, action);
|
||||
state = reduceSuspenseState(store, state, action);
|
||||
|
||||
// TODO(hoxyq): review
|
||||
// If the selected ID is in a collapsed subtree, reset the selected index to null.
|
||||
// We'll know the correct index after the layout effect will toggle the tree,
|
||||
// and the store tree is mutated to account for that.
|
||||
if (
|
||||
state.selectedElementID !== null &&
|
||||
store.isInsideCollapsedSubTree(state.selectedElementID)
|
||||
state.inspectedElementID !== null &&
|
||||
store.isInsideCollapsedSubTree(state.inspectedElementID)
|
||||
) {
|
||||
return {
|
||||
...state,
|
||||
selectedElementIndex: null,
|
||||
inspectedElementIndex: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -897,16 +868,6 @@ function TreeContextController({
|
||||
// Tree
|
||||
numElements: store.numElements,
|
||||
ownerSubtreeLeafElementID: null,
|
||||
selectedElementID:
|
||||
defaultSelectedElementID != null
|
||||
? defaultSelectedElementID
|
||||
: store.lastSelectedHostInstanceElementId,
|
||||
selectedElementIndex:
|
||||
defaultSelectedElementIndex != null
|
||||
? defaultSelectedElementIndex
|
||||
: store.lastSelectedHostInstanceElementId
|
||||
? store.getIndexOfElementID(store.lastSelectedHostInstanceElementId)
|
||||
: null,
|
||||
|
||||
// Search
|
||||
searchIndex: null,
|
||||
@@ -922,42 +883,38 @@ function TreeContextController({
|
||||
defaultInspectedElementID != null
|
||||
? defaultInspectedElementID
|
||||
: store.lastSelectedHostInstanceElementId,
|
||||
inspectedElementIndex:
|
||||
defaultInspectedElementIndex != null
|
||||
? defaultInspectedElementIndex
|
||||
: store.lastSelectedHostInstanceElementId
|
||||
? store.getIndexOfElementID(store.lastSelectedHostInstanceElementId)
|
||||
: null,
|
||||
});
|
||||
|
||||
const dispatchWrapper = useCallback(
|
||||
(action: Action) => {
|
||||
dispatch(action);
|
||||
startTransition(() => {
|
||||
dispatch({type: 'UPDATE_INSPECTED_ELEMENT_ID'});
|
||||
});
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
// Listen for host element selections.
|
||||
useEffect(() => {
|
||||
const handler = (id: Element['id']) =>
|
||||
dispatchWrapper({type: 'SELECT_ELEMENT_BY_ID', payload: id});
|
||||
dispatch({type: 'SELECT_ELEMENT_BY_ID', payload: id});
|
||||
|
||||
store.addListener('hostInstanceSelected', handler);
|
||||
return () => store.removeListener('hostInstanceSelected', handler);
|
||||
}, [store, dispatchWrapper]);
|
||||
}, [store, dispatch]);
|
||||
|
||||
// If a newly-selected search result or inspection selection is inside of a collapsed subtree, auto expand it.
|
||||
// This needs to be a layout effect to avoid temporarily flashing an incorrect selection.
|
||||
const prevSelectedElementID = useRef<number | null>(null);
|
||||
const prevInspectedElementID = useRef<number | null>(null);
|
||||
useLayoutEffect(() => {
|
||||
if (state.selectedElementID !== prevSelectedElementID.current) {
|
||||
prevSelectedElementID.current = state.selectedElementID;
|
||||
if (state.inspectedElementID !== prevInspectedElementID.current) {
|
||||
prevInspectedElementID.current = state.inspectedElementID;
|
||||
|
||||
if (state.selectedElementID !== null) {
|
||||
const element = store.getElementByID(state.selectedElementID);
|
||||
if (state.inspectedElementID !== null) {
|
||||
const element = store.getElementByID(state.inspectedElementID);
|
||||
if (element !== null && element.parentID > 0) {
|
||||
store.toggleIsCollapsed(element.parentID, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [state.selectedElementID, store]);
|
||||
}, [state.inspectedElementID, store]);
|
||||
|
||||
// Mutations to the underlying tree may impact this context (e.g. search results, selection state).
|
||||
useEffect(() => {
|
||||
@@ -965,7 +922,7 @@ function TreeContextController({
|
||||
Array<number>,
|
||||
Map<number, number>,
|
||||
]) => {
|
||||
dispatchWrapper({
|
||||
dispatch({
|
||||
type: 'HANDLE_STORE_MUTATION',
|
||||
payload: [addedElementIDs, removedElementIDs],
|
||||
});
|
||||
@@ -976,20 +933,19 @@ function TreeContextController({
|
||||
// At the moment, we can treat this as a mutation.
|
||||
// We don't know which Elements were newly added/removed, but that should be okay in this case.
|
||||
// It would only impact the search state, which is unlikely to exist yet at this point.
|
||||
dispatchWrapper({
|
||||
dispatch({
|
||||
type: 'HANDLE_STORE_MUTATION',
|
||||
payload: [[], new Map()],
|
||||
});
|
||||
}
|
||||
|
||||
store.addListener('mutated', handleStoreMutated);
|
||||
|
||||
return () => store.removeListener('mutated', handleStoreMutated);
|
||||
}, [dispatchWrapper, initialRevision, store]);
|
||||
}, [dispatch, initialRevision, store]);
|
||||
|
||||
return (
|
||||
<TreeStateContext.Provider value={state}>
|
||||
<TreeDispatcherContext.Provider value={dispatchWrapper}>
|
||||
<TreeDispatcherContext.Provider value={dispatch}>
|
||||
{children}
|
||||
</TreeDispatcherContext.Provider>
|
||||
</TreeStateContext.Provider>
|
||||
@@ -1028,11 +984,11 @@ function recursivelySearchTree(
|
||||
function getNearestResultIndex(
|
||||
store: Store,
|
||||
searchResults: Array<number>,
|
||||
selectedElementIndex: number,
|
||||
inspectedElementIndex: number,
|
||||
): number {
|
||||
const index = searchResults.findIndex(id => {
|
||||
const innerIndex = store.getIndexOfElementID(id);
|
||||
return innerIndex !== null && innerIndex >= selectedElementIndex;
|
||||
return innerIndex !== null && innerIndex >= inspectedElementIndex;
|
||||
});
|
||||
|
||||
return index === -1 ? 0 : index;
|
||||
|
||||
+3
-3
@@ -88,7 +88,7 @@ type Props = {
|
||||
|
||||
function ProfilerContextController({children}: Props): React.Node {
|
||||
const store = useContext(StoreContext);
|
||||
const {selectedElementID} = useContext(TreeStateContext);
|
||||
const {inspectedElementID} = useContext(TreeStateContext);
|
||||
const dispatch = useContext(TreeDispatcherContext);
|
||||
|
||||
const {profilerStore} = store;
|
||||
@@ -176,9 +176,9 @@ function ProfilerContextController({children}: Props): React.Node {
|
||||
|
||||
if (rootID === null || !dataForRoots.has(rootID)) {
|
||||
let selectedElementRootID = null;
|
||||
if (selectedElementID !== null) {
|
||||
if (inspectedElementID !== null) {
|
||||
selectedElementRootID =
|
||||
store.getRootIDForElement(selectedElementID);
|
||||
store.getRootIDForElement(inspectedElementID);
|
||||
}
|
||||
if (
|
||||
selectedElementRootID !== null &&
|
||||
|
||||
Reference in New Issue
Block a user