Fetch owners list from renderer (using suspense)

Owners in the list may have been filtered out of the Store, but in the owners list view- it's important to still show them. The frontend cannot do this on its own, so this list needs to come from the renderer interface.
This commit is contained in:
Brian Vaughn
2019-05-09 11:47:22 -07:00
parent 17516b76ae
commit 564a223368
13 changed files with 404 additions and 182 deletions
+17 -5
View File
@@ -16,6 +16,7 @@ import type {
RendererID,
RendererInterface,
} from './types';
import type { OwnersList } from 'src/devtools/views/Components/types';
import type { Bridge, ComponentFilter } from '../types';
const debug = (methodName, ...args) => {
@@ -29,7 +30,7 @@ const debug = (methodName, ...args) => {
}
};
type InspectSelectParams = {|
type ElementAndRendererID = {|
id: number,
rendererID: number,
|};
@@ -99,6 +100,7 @@ export default class Agent extends EventEmitter {
bridge.addListener('getProfilingStatus', this.getProfilingStatus);
bridge.addListener('getProfilingSummary', this.getProfilingSummary);
bridge.addListener('highlightElementInDOM', this.highlightElementInDOM);
bridge.addListener('getOwnersList', this.getOwnersList);
bridge.addListener('inspectElement', this.inspectElement);
bridge.addListener('logElementToConsole', this.logElementToConsole);
bridge.addListener('overrideContext', this.overrideContext);
@@ -303,7 +305,17 @@ export default class Agent extends EventEmitter {
}
};
inspectElement = ({ id, rendererID }: InspectSelectParams) => {
getOwnersList = ({ id, rendererID }: ElementAndRendererID) => {
const renderer = this._rendererInterfaces[rendererID];
if (renderer == null) {
console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
} else {
const owners = renderer.getOwnersList(id);
this._bridge.send('ownersList', ({ id, owners }: OwnersList));
}
};
inspectElement = ({ id, rendererID }: ElementAndRendererID) => {
const renderer = this._rendererInterfaces[rendererID];
if (renderer == null) {
console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
@@ -312,7 +324,7 @@ export default class Agent extends EventEmitter {
}
};
logElementToConsole = ({ id, rendererID }: InspectSelectParams) => {
logElementToConsole = ({ id, rendererID }: ElementAndRendererID) => {
const renderer = this._rendererInterfaces[rendererID];
if (renderer == null) {
console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
@@ -340,7 +352,7 @@ export default class Agent extends EventEmitter {
this._bridge.send('screenshotCaptured', { commitIndex, dataURL });
};
selectElement = ({ id, rendererID }: InspectSelectParams) => {
selectElement = ({ id, rendererID }: ElementAndRendererID) => {
const renderer = this._rendererInterfaces[rendererID];
if (renderer == null) {
console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
@@ -506,7 +518,7 @@ export default class Agent extends EventEmitter {
}
};
viewElementSource = ({ id, rendererID }: InspectSelectParams) => {
viewElementSource = ({ id, rendererID }: ElementAndRendererID) => {
const renderer = this._rendererInterfaces[rendererID];
if (renderer == null) {
console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
+30 -16
View File
@@ -50,7 +50,10 @@ import type {
ReactRenderer,
RendererInterface,
} from './types';
import type { InspectedElement } from 'src/devtools/views/Components/types';
import type {
InspectedElement,
Owner,
} from 'src/devtools/views/Components/types';
import type { ComponentFilter, ElementType } from 'src/types';
function getInternalReactConstants(version) {
@@ -1685,6 +1688,30 @@ export function attach(
}
}
function getOwnersList(id: number): Array<Owner> | null {
let fiber = findCurrentFiberUsingSlowPathById(id);
if (fiber == null) {
return null;
}
const { _debugOwner } = fiber;
let owners = null;
if (_debugOwner) {
owners = [];
let owner = _debugOwner;
while (owner !== null) {
owners.push({
displayName: getDisplayNameForFiber(owner) || 'Unknown',
id: getFiberID(getPrimaryFiber(owner)),
});
owner = owner._debugOwner || null;
}
}
return owners;
}
function inspectElementRaw(id: number): InspectedElement | null {
let fiber = findCurrentFiberUsingSlowPathById(id);
if (fiber == null) {
@@ -1692,7 +1719,6 @@ export function attach(
}
const {
_debugOwner,
_debugSource,
stateNode,
memoizedProps,
@@ -1764,19 +1790,6 @@ export function attach(
context = { value: context };
}
let owners = null;
if (_debugOwner) {
owners = [];
let owner = _debugOwner;
while (owner !== null) {
owners.push({
displayName: getDisplayNameForFiber(owner) || 'Unknown',
id: getFiberID(getPrimaryFiber(owner)),
});
owner = owner._debugOwner || null;
}
}
const isTimedOutSuspense =
tag === SuspenseComponent && memoizedState !== null;
@@ -1812,7 +1825,7 @@ export function attach(
state: usesHooks ? null : memoizedState,
// List of owners
owners,
owners: getOwnersList(id),
// Location of component in source coude.
source: _debugSource,
@@ -2385,6 +2398,7 @@ export function attach(
getFiberCommits,
getInteractions,
findNativeByFiberID,
getOwnersList,
getPathForElement,
getProfilingDataForDownload,
getProfilingSummary,
+5 -1
View File
@@ -1,7 +1,10 @@
// @flow
import type { ComponentFilter, ElementType } from 'src/types';
import type { InspectedElement } from 'src/devtools/views/Components/types';
import type {
InspectedElement,
Owner,
} from 'src/devtools/views/Components/types';
type BundleType =
| 0 // PROD
@@ -175,6 +178,7 @@ export type RendererInterface = {
) => number | null,
getFiberCommits: (rootID: number, fiberID: number) => FiberCommitsBackend,
getInteractions: (rootID: number) => InteractionsBackend,
getOwnersList: (id: number) => Array<Owner> | null,
getProfilingDataForDownload: (rootID: number) => Object,
getProfilingSummary: (rootID: number) => ProfilingSummaryBackend,
getPathForElement: (id: number) => Array<PathFrame> | null,
+16 -13
View File
@@ -4,6 +4,7 @@ import React, { Suspense } from 'react';
import Tree from './Tree';
import SelectedElement from './SelectedElement';
import { InspectedElementContextController } from './InspectedElementContext';
import { OwnersListContextController } from './OwnersListContext';
import portaledContent from '../portaledContent';
import { ModalDialog } from '../ModalDialog';
@@ -12,19 +13,21 @@ import styles from './Components.css';
function Components(_: {||}) {
// TODO Flex wrappers below should be user resizable.
return (
<div className={styles.Components}>
<div className={styles.TreeWrapper}>
<Tree />
</div>
<div className={styles.SelectedElementWrapper}>
<InspectedElementContextController>
<Suspense fallback={<Loading />}>
<SelectedElement />
</Suspense>
</InspectedElementContextController>
</div>
<ModalDialog />
</div>
<OwnersListContextController>
<InspectedElementContextController>
<div className={styles.Components}>
<div className={styles.TreeWrapper}>
<Tree />
</div>
<div className={styles.SelectedElementWrapper}>
<Suspense fallback={<Loading />}>
<SelectedElement />
</Suspense>
</div>
<ModalDialog />
</div>
</InspectedElementContextController>
</OwnersListContextController>
);
}
+2 -2
View File
@@ -29,7 +29,7 @@ type Props = {
export default function ElementView({ data, index, style }: Props) {
const store = useContext(StoreContext);
const { ownerFlatTree, ownerStack, selectedElementID } = useContext(
const { ownerFlatTree, ownerID, selectedElementID } = useContext(
TreeStateContext
);
const dispatch = useContext(TreeDispatcherContext);
@@ -168,7 +168,7 @@ export default function ElementView({ data, index, style }: Props) {
}}
>
<span className={styles.ScrollAnchor} ref={scrollAnchorStartRef} />
{ownerStack.length === 0 ? (
{ownerID === null ? (
<ExpandCollapseToggle element={element} store={store} />
) : null}
<span className={styles.Component}>
@@ -0,0 +1,109 @@
// @flow
import React, {
createContext,
useCallback,
useContext,
useEffect,
} from 'react';
import { createResource } from '../../cache';
import { BridgeContext, StoreContext } from '../context';
import { TreeStateContext } from './TreeContext';
import type {
Element,
Owner,
OwnersList,
} from 'src/devtools/views/Components/types';
import type { Resource, Thenable } from '../../cache';
type Context = (id: number) => Array<Owner> | null;
const OwnersListContext = createContext<Context>(((null: any): Context));
OwnersListContext.displayName = 'OwnersListContext';
type ResolveFn = (ownersList: Array<Owner> | null) => void;
type InProgressRequest = {|
promise: Thenable<Array<Owner>>,
resolveFn: ResolveFn,
|};
const inProgressRequests: WeakMap<Element, InProgressRequest> = new WeakMap();
const resource: Resource<Element, Element, Array<Owner>> = createResource(
(element: Element) => {
let request = inProgressRequests.get(element);
if (request != null) {
return request.promise;
}
let resolveFn = ((null: any): ResolveFn);
const promise = new Promise(resolve => {
resolveFn = resolve;
});
inProgressRequests.set(element, { promise, resolveFn });
return promise;
},
(element: Element) => element,
{ useWeakMap: true }
);
type Props = {|
children: React$Node,
|};
function OwnersListContextController({ children }: Props) {
const bridge = useContext(BridgeContext);
const store = useContext(StoreContext);
const { ownerID } = useContext(TreeStateContext);
const read = useCallback(
(id: number) => {
const element = store.getElementByID(id);
if (element !== null) {
return resource.read(element);
} else {
return null;
}
},
[store]
);
useEffect(() => {
const onOwnersList = (ownersList: OwnersList) => {
const id = ownersList.id;
const element = store.getElementByID(id);
if (element !== null) {
const request = inProgressRequests.get(element);
if (request != null) {
inProgressRequests.delete(element);
request.resolveFn(ownersList.owners);
}
}
};
bridge.addListener('ownersList', onOwnersList);
return () => bridge.removeListener('ownersList', onOwnersList);
}, [bridge, store]);
// This effect requests an updated owners list any time the selected owner changes
useEffect(() => {
if (ownerID !== null) {
const rendererID = store.getRendererIDForElement(ownerID);
bridge.send('getOwnersList', { id: ownerID, rendererID });
}
return () => {};
}, [bridge, ownerID, store]);
return (
<OwnersListContext.Provider value={read}>
{children}
</OwnersListContext.Provider>
);
}
export { OwnersListContext, OwnersListContextController };
@@ -91,3 +91,8 @@
font-family: var(--font-family-monospace);
font-size: var(--font-size-monospace-normal);
}
.NotInStore,
.NotInStore:hover {
color: var(--color-dimmest);
}
+156 -56
View File
@@ -4,6 +4,7 @@ import React, {
useCallback,
useContext,
useLayoutEffect,
useReducer,
useRef,
useState,
} from 'react';
@@ -12,22 +13,111 @@ import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
import Button from '../Button';
import ButtonIcon from '../ButtonIcon';
import Toggle from '../Toggle';
import { OwnersListContext } from './OwnersListContext';
import { TreeDispatcherContext, TreeStateContext } from './TreeContext';
import { StoreContext } from '../context';
import { useIsOverflowing } from '../hooks';
import { StoreContext } from '../context';
import type { Element } from './types';
import type { Owner } from './types';
import styles from './OwnersStack.css';
type SelectOwner = (owner: Owner | null) => void;
type ACTION_UPDATE_OWNER_ID = {|
type: 'UPDATE_OWNER_ID',
ownerID: number | null,
owners: Array<Owner>,
|};
type ACTION_UPDATE_SELECTED_INDEX = {|
type: 'UPDATE_SELECTED_INDEX',
selectedIndex: number,
|};
type Action = ACTION_UPDATE_OWNER_ID | ACTION_UPDATE_SELECTED_INDEX;
type State = {|
ownerID: number | null,
owners: Array<Owner>,
selectedIndex: number,
|};
function dialogReducer(state, action) {
switch (action.type) {
case 'UPDATE_OWNER_ID':
const selectedIndex = state.owners.findIndex(
owner => owner.id === action.ownerID
);
return {
ownerID: action.ownerID,
owners: action.owners,
selectedIndex,
};
case 'UPDATE_SELECTED_INDEX':
return {
...state,
selectedIndex: action.selectedIndex,
};
default:
throw new Error(`Invalid action "${action.type}"`);
}
}
export default function OwnerStack() {
const { ownerStack, ownerStackIndex } = useContext(TreeStateContext);
const dispatch = useContext(TreeDispatcherContext);
const read = useContext(OwnersListContext);
const { ownerID } = useContext(TreeStateContext);
const treeDispatch = useContext(TreeDispatcherContext);
const [state, dispatch] = useReducer<State, Action>(dialogReducer, {
ownerID: null,
owners: [],
selectedIndex: -1,
});
// TODO (owners) Explain this and use reducer with ownerID too to avoid inf. loop
if (ownerID === null) {
dispatch({
type: 'UPDATE_OWNER_ID',
ownerID: null,
owners: [],
});
} else if (ownerID !== state.ownerID) {
const isInList = state.owners.findIndex(owner => owner.id === ownerID) >= 0;
dispatch({
type: 'UPDATE_OWNER_ID',
ownerID,
owners: isInList ? state.owners : read(ownerID) || [],
});
}
const { owners, selectedIndex } = state;
const selectOwner = useCallback<SelectOwner>(
(owner: Owner | null) => {
if (owner !== null) {
const index = owners.indexOf(owner);
dispatch({
type: 'UPDATE_SELECTED_INDEX',
selectedIndex: index >= 0 ? index : 0,
});
treeDispatch({ type: 'SELECT_OWNER', payload: owner.id });
} else {
dispatch({
type: 'UPDATE_SELECTED_INDEX',
selectedIndex: 0,
});
treeDispatch({ type: 'RESET_OWNER_STACK' });
}
},
[owners, treeDispatch]
);
const [elementsTotalWidth, setElementsTotalWidth] = useState(0);
const elementsBarRef = useRef<HTMLDivElement | null>(null);
const isOverflowing = useIsOverflowing(elementsBarRef, elementsTotalWidth);
const selectedOwner = owners[selectedIndex];
useLayoutEffect(() => {
// If we're already overflowing, then we don't need to re-measure items.
// That's because once the owners stack is open, it can only get larger (by driling in).
@@ -37,7 +127,7 @@ export default function OwnerStack() {
}
let elementsTotalWidth = 0;
for (let i = 0; i < ownerStack.length; i++) {
for (let i = 0; i < owners.length; i++) {
const element = elementsBarRef.current.children[i];
const computedStyle = getComputedStyle(element);
@@ -48,7 +138,7 @@ export default function OwnerStack() {
}
setElementsTotalWidth(elementsTotalWidth);
}, [elementsBarRef, isOverflowing, ownerStack.length]);
}, [elementsBarRef, isOverflowing, owners.length]);
return (
<div className={styles.OwnerStack}>
@@ -56,28 +146,38 @@ export default function OwnerStack() {
{isOverflowing && (
<Fragment>
<ElementsDropdown
ownerStack={ownerStack}
ownerStackIndex={ownerStackIndex}
owners={owners}
selectedIndex={selectedIndex}
selectOwner={selectOwner}
/>
<BackToOwnerButton
ownerStack={ownerStack}
ownerStackIndex={ownerStackIndex}
/>
<ElementView
id={ownerStack[((ownerStackIndex: any): number)]}
index={ownerStackIndex}
owners={owners}
selectedIndex={selectedIndex}
selectOwner={selectOwner}
/>
{selectedOwner != null && (
<ElementView
owner={selectedOwner}
isSelected
selectOwner={selectOwner}
/>
)}
</Fragment>
)}
{!isOverflowing &&
ownerStack.map((id, index) => (
<ElementView key={id} id={id} index={index} />
owners.map((owner, index) => (
<ElementView
key={index}
owner={owner}
isSelected={index === selectedIndex}
selectOwner={selectOwner}
/>
))}
</div>
<div className={styles.VRule} />
<Button
className={styles.IconButton}
onClick={() => dispatch({ type: 'RESET_OWNER_STACK' })}
onClick={() => selectOwner(null)}
title="Back to tree view"
>
<ButtonIcon type="close" />
@@ -87,26 +187,28 @@ export default function OwnerStack() {
}
type ElementsDropdownProps = {
ownerStack: Array<number>,
ownerStackIndex: number | null,
owners: Array<Owner>,
selectedIndex: number,
selectOwner: SelectOwner,
};
function ElementsDropdown({
ownerStack,
ownerStackIndex,
owners,
selectedIndex,
selectOwner,
}: ElementsDropdownProps) {
const store = useContext(StoreContext);
const dispatch = useContext(TreeDispatcherContext);
const menuItems = [];
for (let index = ownerStack.length - 1; index >= 0; index--) {
const id = ownerStack[index];
for (let index = owners.length - 1; index >= 0; index--) {
const owner = owners[index];
const isInStore = store.containsElement(owner.id);
menuItems.push(
<MenuItem
key={id}
className={styles.Component}
onSelect={() => dispatch({ type: 'SELECT_OWNER', payload: id })}
key={owner.id}
className={`${styles.Component} ${isInStore ? '' : styles.NotInStore}`}
onSelect={() => (isInStore ? selectOwner(owner) : null)}
>
{((store.getElementByID(id): any): Element).displayName}
{owner.displayName}
</MenuItem>
);
}
@@ -126,28 +228,26 @@ function ElementsDropdown({
}
type ElementViewProps = {
id: number,
index: number | null,
isSelected: boolean,
owner: Owner,
selectOwner: SelectOwner,
};
function ElementView({ id, index }: ElementViewProps) {
function ElementView({ isSelected, owner, selectOwner }: ElementViewProps) {
const store = useContext(StoreContext);
const { ownerStackIndex } = useContext(TreeStateContext);
const dispatch = useContext(TreeDispatcherContext);
const { displayName } = ((store.getElementByID(id): any): Element);
const isChecked = ownerStackIndex === index;
const { displayName } = owner;
const isInStore = store.containsElement(owner.id);
const handleChange = useCallback(() => {
if (!isChecked) {
dispatch({ type: 'SELECT_OWNER', payload: id });
if (isInStore) {
selectOwner(owner);
}
}, [dispatch, id, isChecked]);
}, [isInStore, selectOwner, owner]);
return (
<Toggle
className={styles.Component}
isChecked={isChecked}
className={`${styles.Component} ${isInStore ? '' : styles.NotInStore}`}
isChecked={isSelected}
onChange={handleChange}
>
{displayName}
@@ -156,32 +256,32 @@ function ElementView({ id, index }: ElementViewProps) {
}
type BackToOwnerButtonProps = {|
ownerStack: Array<number>,
ownerStackIndex: number | null,
owners: Array<Owner>,
selectedIndex: number,
selectOwner: SelectOwner,
|};
function BackToOwnerButton({
ownerStack,
ownerStackIndex,
owners,
selectedIndex,
selectOwner,
}: BackToOwnerButtonProps) {
const store = useContext(StoreContext);
const dispatch = useContext(TreeDispatcherContext);
if (ownerStackIndex === null || ownerStackIndex === 0) {
if (selectedIndex <= 0) {
return null;
}
const ownerID = ownerStack[ownerStackIndex - 1];
const owner = store.getElementByID(ownerID);
const owner = owners[selectedIndex - 1];
if (owner == null) {
debugger;
}
const isInStore = store.containsElement(owner.id);
return (
<Button
onClick={() =>
dispatch({
type: 'SELECT_OWNER',
payload: ownerID,
})
}
title={`Up to ${(owner !== null && owner.displayName) || 'owner'}`}
className={isInStore ? undefined : styles.NotInStore}
onClick={() => (isInStore ? selectOwner(owner) : null)}
title={`Up to ${owner.displayName || 'owner'}`}
>
<ButtonIcon type="previous" />
</Button>
@@ -227,7 +227,7 @@ function InspectedElementView({
state,
} = inspectedElement;
const { ownerStack } = useContext(TreeStateContext);
const { ownerID } = useContext(TreeStateContext);
const bridge = useContext(BridgeContext);
const store = useContext(StoreContext);
@@ -298,7 +298,7 @@ function InspectedElementView({
overrideValueFn={overrideContextFn}
/>
{ownerStack.length === 0 && owners !== null && owners.length > 0 && (
{ownerID === null && owners !== null && owners.length > 0 && (
<div className={styles.Owners}>
<div className={styles.OwnersHeader}>rendered by</div>
{owners.map(owner => (
+11
View File
@@ -37,3 +37,14 @@
margin: 0 0.5rem;
background-color: var(--color-border);
}
.Loading {
height: 100%;
padding-left: 0.5rem;
display: flex;
align-items: center;
flex: 1;
justify-content: flex-start;
font-size: var(--font-size-sans-large);
color: var(--color-dim);
}
+13 -7
View File
@@ -1,6 +1,7 @@
// @flow
import React, {
Suspense,
useState,
useCallback,
useContext,
@@ -38,7 +39,7 @@ export default function Tree(props: Props) {
const dispatch = useContext(TreeDispatcherContext);
const {
numElements,
ownerStack,
ownerID,
searchIndex,
searchResults,
selectedElementID,
@@ -277,7 +278,9 @@ export default function Tree(props: Props) {
<div className={styles.SearchInput}>
<InspectHostNodesToggle />
<div className={styles.VRule} />
{ownerStack.length > 0 ? <OwnersStack /> : <SearchInput />}
<Suspense fallback={<Loading />}>
{ownerID !== null ? <OwnersStack /> : <SearchInput />}
</Suspense>
<div className={styles.VRule} />
<ToggleComponentFiltersModalButton />
</div>
@@ -317,7 +320,7 @@ export default function Tree(props: Props) {
}
function InnerElementType({ style, ...rest }) {
const { ownerStack } = useContext(TreeStateContext);
const { ownerID } = useContext(TreeStateContext);
// The list may need to scroll horizontally due to deeply nested elements.
// We don't know the maximum scroll width up front, because we're windowing.
@@ -346,10 +349,9 @@ function InnerElementType({ style, ...rest }) {
// We shouldn't retain this width across different conceptual trees though,
// so when the user opens the "owners tree" view, we should discard the previous width.
const hasOwnerStack = ownerStack.length > 0;
const [prevHasOwnerStack, setPrevHasOwnerStack] = useState(hasOwnerStack);
if (hasOwnerStack !== prevHasOwnerStack) {
setPrevHasOwnerStack(hasOwnerStack);
const [prevOwnerID, setPrevOwnerID] = useState(ownerID);
if (ownerID !== prevOwnerID) {
setPrevOwnerID(ownerID);
setMinWidth(null);
}
@@ -371,3 +373,7 @@ function InnerElementType({ style, ...rest }) {
/>
);
}
function Loading() {
return <div className={styles.Loading}>Loading...</div>;
}
+33 -80
View File
@@ -50,9 +50,8 @@ type StateContext = {|
searchText: string,
// Owners
ownerID: number | null,
ownerFlatTree: Array<Element> | null,
ownerStack: Array<number>,
ownerStackIndex: number | null,
// Inspection element panel
inspectedElementID: number | null,
@@ -142,8 +141,7 @@ type State = {|
searchText: string,
// Owners
ownerStack: Array<number>,
ownerStackIndex: number | null,
ownerID: number | null,
ownerFlatTree: Array<Element> | null,
// Inspection element panel
@@ -151,17 +149,12 @@ type State = {|
|};
function reduceTreeState(store: Store, state: State, action: Action): State {
let {
numElements,
ownerStack,
selectedElementIndex,
selectedElementID,
} = state;
let { numElements, ownerID, selectedElementIndex, selectedElementID } = state;
let lookupIDForIndex = true;
// Base tree should ignore selected element changes when the owner's tree is active.
if (ownerStack.length === 0) {
if (ownerID === null) {
switch (action.type) {
case 'HANDLE_STORE_MUTATION':
numElements = store.numElements;
@@ -276,7 +269,7 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
function reduceSearchState(store: Store, state: State, action: Action): State {
let {
ownerStack,
ownerID,
searchIndex,
searchResults,
searchText,
@@ -295,7 +288,7 @@ function reduceSearchState(store: Store, state: State, action: Action): State {
let didRequestSearch = false;
// Search isn't supported when the owner's tree is active.
if (ownerStack.length === 0) {
if (ownerID === null) {
switch (action.type) {
case 'GO_TO_NEXT_SEARCH_RESULT':
if (numPrevSearchResults > 0) {
@@ -442,9 +435,8 @@ function reduceOwnersState(store: Store, state: State, action: Action): State {
numElements,
selectedElementID,
selectedElementIndex,
ownerID,
ownerFlatTree,
ownerStack,
ownerStackIndex,
searchIndex,
searchResults,
searchText,
@@ -454,25 +446,9 @@ function reduceOwnersState(store: Store, state: State, action: Action): State {
switch (action.type) {
case 'HANDLE_STORE_MUTATION':
if (ownerStack.length > 0) {
let indexOfRemovedItem = -1;
for (let i = 0; i < ownerStack.length; i++) {
if (store.getElementByID(ownerStack[i]) === null) {
indexOfRemovedItem = i;
break;
}
}
if (indexOfRemovedItem >= 0) {
ownerStack = ownerStack.slice(0, indexOfRemovedItem);
if (ownerStack.length === 0) {
ownerFlatTree = null;
ownerStackIndex = null;
} else {
ownerStackIndex = ownerStack.length - 1;
}
}
if (selectedElementID !== null && ownerFlatTree !== null) {
if (ownerID !== null) {
ownerFlatTree = store.getOwnersListForElement(ownerID);
if (selectedElementID !== null) {
// Mutation might have caused the index of this ID to shift.
selectedElementIndex = ownerFlatTree.findIndex(
element => element.id === selectedElementID
@@ -491,13 +467,12 @@ function reduceOwnersState(store: Store, state: State, action: Action): State {
}
break;
case 'RESET_OWNER_STACK':
ownerStack = [];
ownerStackIndex = null;
ownerID = null;
ownerFlatTree = null;
selectedElementIndex =
selectedElementID !== null
? store.getIndexOfElementID(selectedElementID)
: null;
ownerFlatTree = null;
break;
case 'SELECT_ELEMENT_AT_INDEX':
if (ownerFlatTree !== null) {
@@ -533,33 +508,12 @@ function reduceOwnersState(store: Store, state: State, action: Action): State {
// If the Store doesn't have any owners metadata, don't drill into an empty stack.
// This is a confusing user experience.
if (store.hasOwnerMetadata) {
const id = (action: ACTION_SELECT_OWNER).payload;
ownerStackIndex = ownerStack.indexOf(id);
ownerID = (action: ACTION_SELECT_OWNER).payload;
ownerFlatTree = store.getOwnersListForElement(ownerID);
// Always force reset selection to be the top of the new owner tree.
selectedElementIndex = 0;
prevSelectedElementIndex = null;
// If this owner is already in the current stack, just select it.
// Otherwise, create a new stack.
if (ownerStackIndex < 0) {
// Add this new owner, and fill in the owners above it as well.
ownerStack = [];
let currentOwnerID = id;
while (currentOwnerID !== 0) {
ownerStack.unshift(currentOwnerID);
currentOwnerID = ((store.getElementByID(
currentOwnerID
): any): Element).ownerID;
}
ownerStackIndex = ownerStack.length - 1;
if (searchText !== '') {
searchIndex = null;
searchResults = [];
searchText = '';
}
}
}
break;
default:
@@ -569,17 +523,12 @@ function reduceOwnersState(store: Store, state: State, action: Action): State {
// Changes in the selected owner require re-calculating the owners tree.
if (
ownerStackIndex !== state.ownerStackIndex ||
ownerStack !== state.ownerStack ||
ownerFlatTree !== state.ownerFlatTree ||
action.type === 'HANDLE_STORE_MUTATION'
) {
if (ownerStackIndex === null) {
ownerFlatTree = null;
if (ownerFlatTree === null) {
numElements = store.numElements;
} else {
ownerFlatTree = store.getOwnersListForElement(
ownerStack[ownerStackIndex]
);
numElements = ownerFlatTree.length;
}
}
@@ -588,9 +537,10 @@ function reduceOwnersState(store: Store, state: State, action: Action): State {
if (selectedElementIndex !== prevSelectedElementIndex) {
if (selectedElementIndex === null) {
selectedElementID = null;
} else if (ownerFlatTree !== null) {
selectedElementID =
ownerFlatTree[((selectedElementIndex: any): number)].id;
} else {
if (ownerFlatTree !== null) {
selectedElementID = ownerFlatTree[selectedElementIndex].id;
}
}
}
@@ -605,8 +555,7 @@ function reduceOwnersState(store: Store, state: State, action: Action): State {
searchResults,
searchText,
ownerStack,
ownerStackIndex,
ownerID,
ownerFlatTree,
};
}
@@ -619,14 +568,19 @@ function reduceSuspenseState(
const { type } = action;
switch (type) {
case 'UPDATE_INSPECTED_ELEMENT_ID':
return {
...state,
inspectedElementID: state.selectedElementID,
};
if (state.inspectedElementID !== state.selectedElementID) {
return {
...state,
inspectedElementID: state.selectedElementID,
};
}
break;
default:
// React can bailout of no-op updates.
return state;
break;
}
// React can bailout of no-op updates.
return state;
}
type Props = {| children: React$Node |};
@@ -696,8 +650,7 @@ function TreeContextController({ children }: Props) {
searchText: '',
// Owners
ownerStack: [],
ownerStackIndex: null,
ownerID: null,
ownerFlatTree: null,
// Inspection element panel
+5
View File
@@ -35,6 +35,11 @@ export type Owner = {|
id: number,
|};
export type OwnersList = {|
id: number,
owners: Array<Owner> | null,
|};
export type InspectedElement = {|
id: number,