diff --git a/packages/react-native/Libraries/Inspector/ElementProperties.js b/packages/react-native/Libraries/Inspector/ElementProperties.js index b9c7c159744..5756d236dbb 100644 --- a/packages/react-native/Libraries/Inspector/ElementProperties.js +++ b/packages/react-native/Libraries/Inspector/ElementProperties.js @@ -10,7 +10,9 @@ 'use strict'; +import type {InspectorData} from '../Renderer/shims/ReactNativeTypes'; import type {ViewStyleProp} from '../StyleSheet/StyleSheet'; +import type {InspectedElementSource} from './Inspector'; const TouchableHighlight = require('../Components/Touchable/TouchableHighlight'); const TouchableWithoutFeedback = require('../Components/Touchable/TouchableWithoutFeedback'); @@ -25,13 +27,9 @@ const StyleInspector = require('./StyleInspector'); const React = require('react'); type Props = $ReadOnly<{| - hierarchy: Array<{|name: string|}>, + hierarchy: ?InspectorData['hierarchy'], style?: ?ViewStyleProp, - source?: ?{ - fileName?: string, - lineNumber?: number, - ... - }, + source?: ?InspectedElementSource, frame?: ?Object, selection?: ?number, setSelection?: number => mixed, @@ -63,23 +61,29 @@ class ElementProperties extends React.Component { - {mapWithSeparator( - this.props.hierarchy, - (hierarchyItem, i): React.MixedElement => ( - this.props.setSelection(i)}> - {hierarchyItem.name} - - ), - (i): React.MixedElement => ( - - ▸ - - ), - )} + {this.props.hierarchy != null && + mapWithSeparator( + this.props.hierarchy, + (hierarchyItem, i): React.MixedElement => ( + this.props.setSelection(i)}> + + {hierarchyItem.name} + + + ), + (i): React.MixedElement => ( + + ▸ + + ), + )} diff --git a/packages/react-native/Libraries/Inspector/Inspector.js b/packages/react-native/Libraries/Inspector/Inspector.js index d6f3da192cd..f22c5457756 100644 --- a/packages/react-native/Libraries/Inspector/Inspector.js +++ b/packages/react-native/Libraries/Inspector/Inspector.js @@ -10,7 +10,11 @@ 'use strict'; -import type {TouchedViewDataAtPoint} from '../Renderer/shims/ReactNativeTypes'; +import type { + InspectorData, + TouchedViewDataAtPoint, +} from '../Renderer/shims/ReactNativeTypes'; +import type {ViewStyleProp} from '../StyleSheet/StyleSheet'; import type {ReactDevToolsAgent} from '../Types/ReactDevToolsTypes'; import type {HostRef} from './getInspectorDataForViewAtPoint'; @@ -25,71 +29,68 @@ const InspectorOverlay = require('./InspectorOverlay'); const InspectorPanel = require('./InspectorPanel'); const React = require('react'); +const {useState} = React; + +type PanelPosition = 'top' | 'bottom'; +type SelectedTab = + | 'elements-inspector' + | 'network-profiling' + | 'performance-profiling'; + +export type InspectedElementFrame = TouchedViewDataAtPoint['frame']; +export type InspectedElementSource = InspectorData['source']; +export type InspectedElement = $ReadOnly<{ + frame: InspectedElementFrame, + source?: InspectedElementSource, + style?: ViewStyleProp, +}>; +export type ElementsHierarchy = InspectorData['hierarchy']; + type Props = { inspectedView: ?HostRef, onRequestRerenderApp: () => void, reactDevToolsAgent?: ReactDevToolsAgent, }; -class Inspector extends React.Component< - Props, - { - hierarchy: any, - panelPos: string, - inspecting: boolean, - selection: ?number, - perfing: boolean, - inspected: any, - inspectedView: ?HostRef, - networking: boolean, - ... - }, -> { - _setTouchedViewData: ?(TouchedViewDataAtPoint) => void; +function Inspector({ + inspectedView, + onRequestRerenderApp, + reactDevToolsAgent, +}: Props): React.Node { + const [selectedTab, setSelectedTab] = + useState('elements-inspector'); - constructor(props: Props) { - super(props); + const [panelPosition, setPanelPosition] = useState('bottom'); + const [inspectedElement, setInspectedElement] = + useState(null); + const [selectionIndex, setSelectionIndex] = useState(null); + const [elementsHierarchy, setElementsHierarchy] = + useState(null); - this.state = { - hierarchy: null, - panelPos: 'bottom', - inspecting: true, - perfing: false, - inspected: null, - selection: null, - inspectedView: this.props.inspectedView, - networking: false, - }; - } + const setSelection = (i: number) => { + const hierarchyItem = elementsHierarchy?.[i]; + if (hierarchyItem == null) { + return; + } - componentWillUnmount() { - this._setTouchedViewData = null; - } - - UNSAFE_componentWillReceiveProps(newProps: Props) { - this.setState({inspectedView: newProps.inspectedView}); - } - - setSelection(i: number) { - const hierarchyItem = this.state.hierarchy[i]; - // we pass in findNodeHandle as the method is injected + // We pass in findNodeHandle as the method is injected const {measure, props, source} = hierarchyItem.getInspectorData(findNodeHandle); measure((x, y, width, height, left, top) => { - this.setState({ - inspected: { - frame: {left, top, width, height}, - style: props.style, - source, - }, - selection: i, + // $FlowFixMe[incompatible-call] `props` from InspectorData are defined as dictionary, which is incompatible with ViewStyleProp + setInspectedElement({ + frame: {left, top, width, height}, + source, + style: props.style, }); - }); - } - onTouchPoint(locationX: number, locationY: number) { - this._setTouchedViewData = viewData => { + setSelectionIndex(i); + }); + }; + + const onTouchPoint = (locationX: number, locationY: number) => { + const setTouchedViewData = (viewData: TouchedViewDataAtPoint) => { const { hierarchy, props, @@ -104,109 +105,90 @@ class Inspector extends React.Component< // Sync the touched view with React DevTools. // Note: This is Paper only. To support Fabric, // DevTools needs to be updated to not rely on view tags. - const agent = this.props.reactDevToolsAgent; - if (agent) { - agent.selectNode(findNodeHandle(touchedViewTag)); + if (reactDevToolsAgent) { + reactDevToolsAgent.selectNode(findNodeHandle(touchedViewTag)); if (closestInstance != null) { - agent.selectNode(closestInstance); + reactDevToolsAgent.selectNode(closestInstance); } } - this.setState({ - panelPos: - pointerY > Dimensions.get('window').height / 2 ? 'top' : 'bottom', - selection: selectedIndex, - hierarchy, - inspected: { - style: props.style, - frame, - source, - }, + setPanelPosition( + pointerY > Dimensions.get('window').height / 2 ? 'top' : 'bottom', + ); + setSelectionIndex(selectedIndex); + setElementsHierarchy(hierarchy); + // $FlowFixMe[incompatible-call] `props` from InspectorData are defined as dictionary, which is incompatible with ViewStyleProp + setInspectedElement({ + frame, + source, + style: props.style, }); }; + getInspectorDataForViewAtPoint( - this.state.inspectedView, + inspectedView, locationX, locationY, viewData => { - if (this._setTouchedViewData != null) { - this._setTouchedViewData(viewData); - this._setTouchedViewData = null; - } + setTouchedViewData(viewData); return false; }, ); - } + }; - setPerfing(val: boolean) { - this.setState({ - perfing: val, - inspecting: false, - inspected: null, - networking: false, - }); - } + const setInspecting = (enabled: boolean) => { + setSelectedTab(enabled ? 'elements-inspector' : null); + setInspectedElement(null); + }; - setInspecting(val: boolean) { - this.setState({ - inspecting: val, - inspected: null, - }); - } + const setPerfing = (enabled: boolean) => { + setSelectedTab(enabled ? 'performance-profiling' : null); + setInspectedElement(null); + }; - setTouchTargeting(val: boolean) { + const setNetworking = (enabled: boolean) => { + setSelectedTab(enabled ? 'network-profiling' : null); + setInspectedElement(null); + }; + + const setTouchTargeting = (val: boolean) => { PressabilityDebug.setEnabled(val); - this.props.onRequestRerenderApp(); - } + onRequestRerenderApp(); + }; - setNetworking(val: boolean) { - this.setState({ - networking: val, - perfing: false, - inspecting: false, - inspected: null, - }); - } + const panelContainerStyle = + panelPosition === 'bottom' + ? {bottom: 0} + : Platform.select({ios: {top: 0}, default: {top: 0}}); - render(): React.Node { - const panelContainerStyle = - this.state.panelPos === 'bottom' - ? {bottom: 0} - : {top: Platform.OS === 'ios' ? 20 : 0}; - return ( - - {this.state.inspecting && ( - - )} - - - + return ( + + {selectedTab === 'elements-inspector' && ( + + )} + + + - ); - } + + ); } const styles = StyleSheet.create({ diff --git a/packages/react-native/Libraries/Inspector/InspectorOverlay.js b/packages/react-native/Libraries/Inspector/InspectorOverlay.js index c8973baea39..c73a04f23ea 100644 --- a/packages/react-native/Libraries/Inspector/InspectorOverlay.js +++ b/packages/react-native/Libraries/Inspector/InspectorOverlay.js @@ -10,8 +10,8 @@ 'use strict'; -import type {ViewStyleProp} from '../StyleSheet/StyleSheet'; import type {PressEvent} from '../Types/CoreEventTypes'; +import type {InspectedElement} from './Inspector'; const View = require('../Components/View/View'); const StyleSheet = require('../StyleSheet/StyleSheet'); @@ -19,13 +19,8 @@ const Dimensions = require('../Utilities/Dimensions').default; const ElementBox = require('./ElementBox'); const React = require('react'); -type Inspected = $ReadOnly<{| - frame?: Object, - style?: ViewStyleProp, -|}>; - type Props = $ReadOnly<{| - inspected?: Inspected, + inspected?: ?InspectedElement, onTouchPoint: (locationX: number, locationY: number) => void, |}>; diff --git a/packages/react-native/Libraries/Inspector/InspectorPanel.js b/packages/react-native/Libraries/Inspector/InspectorPanel.js index 6f40146db8a..d36c52089b9 100644 --- a/packages/react-native/Libraries/Inspector/InspectorPanel.js +++ b/packages/react-native/Libraries/Inspector/InspectorPanel.js @@ -10,7 +10,7 @@ 'use strict'; -import type {ViewStyleProp} from '../StyleSheet/StyleSheet'; +import type {ElementsHierarchy, InspectedElement} from './Inspector'; import SafeAreaView from '../Components/SafeAreaView/SafeAreaView'; @@ -34,22 +34,10 @@ type Props = $ReadOnly<{| setTouchTargeting: (val: boolean) => void, networking: boolean, setNetworking: (val: boolean) => void, - hierarchy?: ?Array<{|name: string|}>, + hierarchy?: ?ElementsHierarchy, selection?: ?number, setSelection: number => mixed, - inspected?: ?$ReadOnly<{| - style?: ?ViewStyleProp, - frame?: ?$ReadOnly<{| - top?: ?number, - left?: ?number, - width?: ?number, - height: ?number, - |}>, - source?: ?{| - fileName?: string, - lineNumber?: number, - |}, - |}>, + inspected?: ?InspectedElement, |}>; class InspectorPanel extends React.Component { @@ -71,7 +59,6 @@ class InspectorPanel extends React.Component { style={this.props.inspected.style} frame={this.props.inspected.frame} source={this.props.inspected.source} - // $FlowFixMe[incompatible-type] : Hierarchy should be non-nullable hierarchy={this.props.hierarchy} selection={this.props.selection} setSelection={this.props.setSelection} diff --git a/packages/react-native/Libraries/Inspector/ReactDevToolsOverlay.js b/packages/react-native/Libraries/Inspector/ReactDevToolsOverlay.js index 727b9deadb6..04959ca766e 100644 --- a/packages/react-native/Libraries/Inspector/ReactDevToolsOverlay.js +++ b/packages/react-native/Libraries/Inspector/ReactDevToolsOverlay.js @@ -15,6 +15,7 @@ import type { ReactDevToolsAgent, } from '../Types/ReactDevToolsTypes'; import type {HostRef} from './getInspectorDataForViewAtPoint'; +import type {InspectedElement} from './Inspector'; import View from '../Components/View/View'; import ReactNativeFeatureFlags from '../ReactNative/ReactNativeFeatureFlags'; @@ -37,9 +38,7 @@ export default function ReactDevToolsOverlay({ inspectedView, reactDevToolsAgent, }: Props): React.Node { - const [inspected, setInspected] = useState(null); + const [inspected, setInspected] = useState(null); const [isInspecting, setIsInspecting] = useState(false); useEffect(() => {