Skip to content

Commit

Permalink
Show highlight when element is selected in React devtools
Browse files Browse the repository at this point in the history
Summary:
Changelog:
[General][Changed] - Copied and refactored the current devtools highlighting code from Inspector into its own module and add to the top level `AppContainer`. The effect is that the highlight stills shows without Inspector opened.

This diff copies the current devtools highlighting logic from Inspector into a module and add to the top level `AppContainer`. The effect is without Inspector opened, the highlight will still show.

## Context
This is the first diff for "Component Tab Automatically Highlight Elements". The idea is to replicate the behavior on Web to RN.

## Behavior
on Web:
- highlight shows whenever an element in the component list is hovered
- Selecting an element doesn't keeps the highlight showing.

on RN (before this diff):
- when RN inspector opens: selecting an element keeps the highlight showing
- when RN inspector closes: stop showing highlihgintg.

on RN(this diff)
- selecting an element keeps the highlight showing

## TODO
- See if highlighting event can be sent on hover, instead of when an element is selected.

Reviewed By: lunaruan

Differential Revision: D38568135

fbshipit-source-id: d168874677d08a9c5526a7f896943579da804565
  • Loading branch information
tyao1 authored and facebook-github-bot committed Aug 16, 2022
1 parent 6442543 commit a632048
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 45 deletions.
80 changes: 80 additions & 0 deletions Libraries/Inspector/DevtoolsHighlighter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/

import ElementBox from './ElementBox';
import * as React from 'react';
const {useEffect, useState} = React;

const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;

export default function DevtoolsHighlighter(): React.Node {
const [inspected, setInspected] = useState(null);
useEffect(() => {
let devToolsAgent = null;
let hideTimeoutId = null;

function onAgentHideNativeHighlight() {
// we wait to actually hide in order to avoid flicker
clearTimeout(hideTimeoutId);
hideTimeoutId = setTimeout(() => {
setInspected(null);
}, 100);
}

function onAgentShowNativeHighlight(node: any) {
clearTimeout(hideTimeoutId);
// Shape of `node` is different in Fabric.
const component = node.canonical ?? node;

component.measure((x, y, width, height, left, top) => {
setInspected({
frame: {left, top, width, height},
});
});
}

function cleanup() {
const currentAgent = devToolsAgent;
if (currentAgent != null) {
currentAgent.removeListener(
'hideNativeHighlight',
onAgentHideNativeHighlight,
);
currentAgent.removeListener(
'showNativeHighlight',
onAgentShowNativeHighlight,
);
currentAgent.removeListener('shutdown', cleanup);
devToolsAgent = null;
}
}

function _attachToDevtools(agent: Object) {
devToolsAgent = agent;
agent.addListener('hideNativeHighlight', onAgentHideNativeHighlight);
agent.addListener('showNativeHighlight', onAgentShowNativeHighlight);
agent.addListener('shutdown', cleanup);
}

hook.on('react-devtools', _attachToDevtools);
if (hook.reactDevtoolsAgent) {
_attachToDevtools(hook.reactDevtoolsAgent);
}
return () => {
hook.off('react-devtools', _attachToDevtools);
cleanup();
};
}, []);

if (inspected != null) {
return <ElementBox frame={inspected.frame} />;
}
return null;
}
38 changes: 0 additions & 38 deletions Libraries/Inspector/Inspector.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,54 +144,16 @@ class Inspector extends React.Component<
}

_attachToDevtools = (agent: Object) => {
agent.addListener('hideNativeHighlight', this._onAgentHideNativeHighlight);
agent.addListener('showNativeHighlight', this._onAgentShowNativeHighlight);
agent.addListener('shutdown', this._onAgentShutdown);

this.setState({
devtoolsAgent: agent,
});
};

_onAgentHideNativeHighlight = () => {
if (this.state.inspected === null) {
return;
}
// we wait to actually hide in order to avoid flicker
this._hideTimeoutID = setTimeout(() => {
this.setState({
inspected: null,
});
}, 100);
};

_onAgentShowNativeHighlight = (node: any) => {
clearTimeout(this._hideTimeoutID);

// Shape of `node` is different in Fabric.
const component = node.canonical ?? node;

component.measure((x, y, width, height, left, top) => {
this.setState({
hierarchy: [],
inspected: {
frame: {left, top, width, height},
},
});
});
};

_onAgentShutdown = () => {
const agent = this.state.devtoolsAgent;
if (agent != null) {
agent.removeListener(
'hideNativeHighlight',
this._onAgentHideNativeHighlight,
);
agent.removeListener(
'showNativeHighlight',
this._onAgentShowNativeHighlight,
);
agent.removeListener('shutdown', this._onAgentShutdown);

this.setState({devtoolsAgent: null});
Expand Down
20 changes: 13 additions & 7 deletions Libraries/ReactNative/AppContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,19 @@ class AppContainer extends React.Component<Props, State> {

render(): React.Node {
let logBox = null;
let devtoolsHighlighter = null;
if (__DEV__) {
if (
!global.__RCTProfileIsProfiling &&
!this.props.internal_excludeLogBox
) {
const LogBoxNotificationContainer =
require('../LogBox/LogBoxNotificationContainer').default;
logBox = <LogBoxNotificationContainer />;
if (!global.__RCTProfileIsProfiling) {
if (!this.props.internal_excludeLogBox) {
const LogBoxNotificationContainer =
require('../LogBox/LogBoxNotificationContainer').default;
logBox = <LogBoxNotificationContainer />;
}
if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__ != null) {
const DevtoolsHighlighter =
require('../Inspector/DevtoolsHighlighter').default;
devtoolsHighlighter = <DevtoolsHighlighter />;
}
}
}

Expand Down Expand Up @@ -118,6 +123,7 @@ class AppContainer extends React.Component<Props, State> {
<RootTagContext.Provider value={createRootTag(this.props.rootTag)}>
<View style={styles.appContainer} pointerEvents="box-none">
{!this.state.hasError && innerView}
{devtoolsHighlighter}
{this.state.inspector}
{logBox}
</View>
Expand Down

0 comments on commit a632048

Please sign in to comment.