Skip to content
This repository was archived by the owner on Jun 26, 2020. It is now read-only.

[WIP] Support hiding components in devtools #1187

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions backend/attachRendererFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ function attachRendererFiber(hook: Hook, rid: string, renderer: ReactRenderer):
var nodeType = null;
var name = null;
var text = null;
var needHideBySymbol = null;
var needHideByParensInName = null;

// Profiler data
var actualDuration = null;
Expand All @@ -225,6 +227,8 @@ function attachRendererFiber(hook: Hook, rid: string, renderer: ReactRenderer):
publicInstance = fiber.stateNode;
props = fiber.memoizedProps;
state = fiber.memoizedState;
needHideBySymbol = typeof Symbol === 'function' && fiber.type[Symbol.for('react.devtools.hide')];
needHideByParensInName = /\(.*\)/.test(name);
if (publicInstance != null) {
context = publicInstance.context;
if (context && Object.keys(context).length === 0) {
Expand Down Expand Up @@ -414,6 +418,8 @@ function attachRendererFiber(hook: Hook, rid: string, renderer: ReactRenderer):
updater,
publicInstance,
memoizedInteractions,
needHideBySymbol,
needHideByParensInName,

// Profiler data
actualDuration,
Expand Down
10 changes: 10 additions & 0 deletions backend/getData.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,14 @@ function getData(internalInstance: Object): DataType {
};
}

const needHideBySymbol = nodeType === 'Composite' &&
typeof Symbol === 'function' &&
typeof type === 'function' &&
type[Symbol.for('react.devtools.hide')];
const needHideByParensInName = nodeType === 'Composite' &&
typeof type === 'function' &&
name && /\(.*\)/.test(name);

// $FlowFixMe
return {
nodeType,
Expand All @@ -180,6 +188,8 @@ function getData(internalInstance: Object): DataType {
text,
updater,
publicInstance,
needHideBySymbol,
needHideByParensInName,
};
}

Expand Down
2 changes: 2 additions & 0 deletions backend/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ export type DataType = {
text: ?string,
updater: ?(CompositeUpdater | NativeUpdater),
publicInstance: ?Object,
needHideBySymbol: boolean,
needHideByParensInName: boolean
};

// This type is entirely opaque to the backend.
Expand Down
23 changes: 13 additions & 10 deletions frontend/Breadcrumb.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const PropTypes = require('prop-types');
const React = require('react');
const decorate = require('./decorate');

type BreadcrumbPath = Array<{id: ElementID, node: Object}>;
type BreadcrumbPath = Array<{id: ElementID, node: Object, isDimmedNode: boolean}>;

type Props = {
hover: (string, boolean) => void;
Expand Down Expand Up @@ -70,13 +70,9 @@ class Breadcrumb extends React.Component<Props, State> {

return (
<ul style={containerStyle(theme)}>
{path.map(({ id, node }) => {
{path.map(({ id, node, isDimmedNode }) => {
const isSelected = id === selected;
const style = itemStyle(
isSelected,
node.get('nodeType'),
theme,
);
const style = itemStyle({isSelected, nodeType: node.get('nodeType'), isDimmedNode}, theme);

return (
<li
Expand Down Expand Up @@ -127,7 +123,10 @@ const containerStyle = (theme: Theme) => ({
overflow: 'auto',
});

const itemStyle = (isSelected: boolean, nodeType: string, theme: Theme) => {
const itemStyle = (
{isSelected, nodeType, isDimmedNode}: {isSelected: boolean, nodeType: string, isDimmedNode: boolean},
theme: Theme
) => {
let color;
if (isSelected) {
color = theme.state02;
Expand All @@ -146,24 +145,28 @@ const itemStyle = (isSelected: boolean, nodeType: string, theme: Theme) => {
MozUserSelect: 'none',
userSelect: 'none',
display: 'inline-block',
opacity: isDimmedNode ? 0.8 : 1,
fontStyle: isDimmedNode ? 'italic' : undefined,
};
};

function getBreadcrumbPath(store: Store): BreadcrumbPath {
var path = [];
var current = store.breadcrumbHead;
while (current) {
const node = store.get(current);
path.unshift({
id: current,
node: store.get(current),
node: node,
isDimmedNode: store.isDimmedNode(node),
});
current = store.skipWrapper(store.getParent(current), true);
}
return path;
}

module.exports = decorate({
listeners: () => ['breadcrumbHead', 'selected'],
listeners: () => ['breadcrumbHead', 'selected', 'hidingStyleChange', 'hideSymbolChange', 'hideByParensInNameChange'],
props(store, props) {
return {
select: id => store.selectBreadcrumb(id),
Expand Down
22 changes: 16 additions & 6 deletions frontend/Node.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ type PropsType = {
isBottomTagSelected: boolean,
searchRegExp: ?RegExp,
wrappedChildren: ?Array<any>,
isHiddenNode: boolean,
isDimmedNode: boolean,
onHover: (isHovered: boolean) => void,
onHoverBottom: (isHovered: boolean) => void,
onContextMenu: () => void,
Expand Down Expand Up @@ -195,14 +197,15 @@ class Node extends React.Component<PropsType, StateType> {
wrappedChildren,
} = this.props;
const {isWindowFocused} = this.state;
const isDimmed = this.props.isDimmedNode;

if (!node) {
return 'Node was deleted';
}

let children = node.get('children');

if (node.get('nodeType') === 'Wrapper') {
if (node.get('nodeType') === 'Wrapper' || this.props.isHiddenNode) {
return children.map(child =>
<WrappedNode key={child} id={child} depth={depth}/>
);
Expand Down Expand Up @@ -297,7 +300,7 @@ class Node extends React.Component<PropsType, StateType> {

// Single-line tag (collapsed / simple content / no content)
if (!children || typeof children === 'string' || !children.length) {
const jsxSingleLineTagStyle = jsxTagStyle(inverted, nodeType, theme);
const jsxSingleLineTagStyle = jsxTagStyle({inverted, nodeType, isDimmed}, theme);
const content = children;
const isCollapsed = content === null || content === undefined;
return (
Expand Down Expand Up @@ -328,7 +331,7 @@ class Node extends React.Component<PropsType, StateType> {
);
}

const jsxCloseTagStyle = jsxTagStyle(inverted && (isBottomTagSelected || collapsed), nodeType, theme);
const jsxCloseTagStyle = jsxTagStyle({inverted: inverted && (isBottomTagSelected || collapsed), nodeType, isDimmed}, theme);
const closeTag = (
<Fragment>
&lt;/
Expand All @@ -342,7 +345,7 @@ class Node extends React.Component<PropsType, StateType> {

const headInverted = inverted && (!isBottomTagSelected || collapsed);

const jsxOpenTagStyle = jsxTagStyle(inverted && (!isBottomTagSelected || collapsed), nodeType, theme);
const jsxOpenTagStyle = jsxTagStyle({inverted: inverted && (!isBottomTagSelected || collapsed), nodeType, isDimmed}, theme);
const head = (
<div style={sharedHeadStyle} {...headEvents}>
<span
Expand Down Expand Up @@ -418,7 +421,7 @@ Node.contextTypes = {

var WrappedNode = decorate({
listeners(props) {
return [props.id];
return [props.id, 'hidingStyleChange', 'hideSymbolChange', 'hideByParensInNameChange'];
},
props(store, props) {
var node = store.get(props.id);
Expand All @@ -435,6 +438,8 @@ var WrappedNode = decorate({
isBottomTagHovered: store.isBottomTagHovered,
hovered: store.hovered === props.id,
searchRegExp: props.searchRegExp,
isHiddenNode: store.isHiddenNode(node),
isDimmedNode: store.isDimmedNode(node),
onToggleCollapse: e => {
e.preventDefault();
store.toggleCollapse(props.id);
Expand Down Expand Up @@ -510,7 +515,10 @@ const headStyle = ({
};
};

const jsxTagStyle = (inverted: boolean, nodeType: string, theme: Theme) => {
const jsxTagStyle = (
{inverted, nodeType, isDimmed}: {inverted: boolean, nodeType: string, isDimmed: boolean},
theme: Theme
) => {
let color;
if (inverted) {
color = theme.state02;
Expand All @@ -524,6 +532,8 @@ const jsxTagStyle = (inverted: boolean, nodeType: string, theme: Theme) => {

return {
color,
opacity: isDimmed ? 0.8 : 1,
fontStyle: isDimmed ? 'italic' : undefined,
};
};

Expand Down
15 changes: 15 additions & 0 deletions frontend/PreferencesPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ const ThemeEditor = require('./Themes/Editor/Editor');
const Hoverable = require('./Hoverable');
const TraceUpdatesFrontendControl = require('../plugins/TraceUpdates/TraceUpdatesFrontendControl');
const ColorizerFrontendControl = require('../plugins/Colorizer/ColorizerFrontendControl');
const HidingEffectFrontendControl = require('../plugins/HidingComponents/EffectFrontendControl');
const HidingSymbolFrontendControl = require('../plugins/HidingComponents/SymbolFrontendControl');
const HidingDisplayNamedFrontendControl = require('../plugins/HidingComponents/DisplayNamedFrontendControl');

import type {Theme} from './types';

Expand Down Expand Up @@ -118,6 +121,18 @@ class PreferencesPanel extends React.Component<Props, State> {
<EditIcon />
</EditButton>
</div>
<h4 style={styles.header}>Hiding components</h4>
<div style={styles.preference}>
<HidingEffectFrontendControl value="none" text="No hiding" />
<HidingEffectFrontendControl value="hide" text="Hiding" />
<HidingEffectFrontendControl value="dim" text="Dimming"/>
</div>
<div style={styles.preference}>
<HidingSymbolFrontendControl />
</div>
<div style={styles.preference}>
<HidingDisplayNamedFrontendControl />
</div>
<div style={styles.buttonBar}>
<button
onClick={hide}
Expand Down
4 changes: 2 additions & 2 deletions frontend/SettingsCheckbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ class SettingsCheckbox extends React.Component<Props, State> {
}

componentDidMount(): void {
if (!this.props.state !== this._defaultState) {
this.props.onChange(this._defaultState);
if (!this._defaultState.equals(this.props.state)) {
this.props.onChange(this._defaultState.merge(this.props.state));
}
}

Expand Down
73 changes: 73 additions & 0 deletions frontend/SettingsRadio.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/

'use strict';

const React = require('react');

const {sansSerif} = require('./Themes/Fonts');


type StateValue = any;

type Props = {
state: StateValue,
value: StateValue,
text: string,
onChange: (v: StateValue) => void,
};

class SettingsRadio extends React.Component<Props> {
_toggle: (b: boolean) => void;

constructor(props: Props) {
super(props);
this._toggle = this._toggle.bind(this);
}

render() {
var {state, value} = this.props;
return (
<label style={styles.container} onClick={this._toggle} tabIndex={0}>
<input
style={styles.radio}
type="radio"
checked={state === value}
readOnly={true}
/>
<span>{this.props.text}</span>
</label>
);
}

_toggle() {
var {onChange, value} = this.props;
onChange(value);
}
}

var styles = {
radio: {
pointerEvents: 'none',
marginRight: '0.5rem',
},
container: {
WebkitUserSelect: 'none',
cursor: 'default',
display: 'inline-block',
outline: 'none',
fontFamily: sansSerif.family,
userSelect: 'none',
marginRight: '0.5rem',
},
};

module.exports = SettingsRadio;
Loading