From 4fe3f563c2831c2a199b119ff1c1351ce09a475d Mon Sep 17 00:00:00 2001 From: Zack Argyle Date: Thu, 9 Feb 2017 16:44:31 -0800 Subject: [PATCH 1/2] Add a checkbox to hide HOC wrappers in tree-view --- frontend/Node.js | 9 +++++- frontend/SettingsPane.js | 2 ++ frontend/Store.js | 7 +++++ frontend/decorate.js | 7 ++++- plugins/Wrappers/WrappersFrontendControl.js | 30 +++++++++++++++++++ .../Wrappers/shouldSkipToChildRendering.js | 27 +++++++++++++++++ 6 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 plugins/Wrappers/WrappersFrontendControl.js create mode 100644 plugins/Wrappers/shouldSkipToChildRendering.js diff --git a/frontend/Node.js b/frontend/Node.js index 4984a306ec..a7c5ff345e 100644 --- a/frontend/Node.js +++ b/frontend/Node.js @@ -15,6 +15,7 @@ var React = require('react'); var assign = require('object-assign'); var decorate = require('./decorate'); var Props = require('./Props'); +var shouldSkipToChildRendering = require('../plugins/Wrappers/shouldSkipToChildRendering'); import type {Map} from 'immutable'; @@ -71,6 +72,10 @@ class Node extends React.Component { } var children = node.get('children'); + if (this.props.skipToChildRendering) { + return ; + } + if (node.get('nodeType') === 'Wrapper') { return ( @@ -257,7 +262,7 @@ Node.contextTypes = { var WrappedNode = decorate({ listeners(props) { - return [props.id]; + return [props.id, 'hidewrapperschange']; }, props(store, props) { var node = store.get(props.id); @@ -266,9 +271,11 @@ var WrappedNode = decorate({ var child = store.get(node.get('children')[0]); wrappedChildren = child && child.get('children'); } + return { node, wrappedChildren, + skipToChildRendering: shouldSkipToChildRendering(node, store), selected: store.selected === props.id, isBottomTagSelected: store.isBottomTagSelected, hovered: store.hovered === props.id, diff --git a/frontend/SettingsPane.js b/frontend/SettingsPane.js index c4518b1377..b2d34bb8fa 100644 --- a/frontend/SettingsPane.js +++ b/frontend/SettingsPane.js @@ -12,6 +12,7 @@ var BananaSlugFrontendControl = require('../plugins/BananaSlug/BananaSlugFrontendControl'); var ColorizerFrontendControl = require('../plugins/Colorizer/ColorizerFrontendControl'); var RegexFrontendControl = require('../plugins/Regex/RegexFrontendControl'); +var WrappersFrontendControl = require('../plugins/Wrappers/WrappersFrontendControl'); var React = require('react'); class SettingsPane extends React.Component { @@ -21,6 +22,7 @@ class SettingsPane extends React.Component { + ); } diff --git a/frontend/Store.js b/frontend/Store.js index 6b66b9fb8f..2a2150d33b 100644 --- a/frontend/Store.js +++ b/frontend/Store.js @@ -90,6 +90,7 @@ class Store extends EventEmitter { bananaslugState: ?ControlState; colorizerState: ?ControlState; regexState: ?ControlState; + showWrappersState: ?ControlState; contextMenu: ?ContextMenu; hovered: ?ElementID; isBottomTagSelected: boolean; @@ -127,6 +128,7 @@ class Store extends EventEmitter { this.bananaslugState = null; this.colorizerState = null; this.regexState = null; + this.hideWrappersState = null; this.placeholderText = DEFAULT_PLACEHOLDER; this.refreshSearch = false; @@ -496,6 +498,11 @@ class Store extends EventEmitter { this.changeSearch(this.searchText); } + toggleHideWrappers(state: ControlState) { + this.hideWrappersState = state; + this.emit('hidewrapperschange'); + } + // Private stuff _establishConnection() { var tries = 0; diff --git a/frontend/decorate.js b/frontend/decorate.js index c19491ac27..75aecaf101 100644 --- a/frontend/decorate.js +++ b/frontend/decorate.js @@ -64,12 +64,16 @@ module.exports = function(options: Options, Component: any): any { this.state = {}; } + componentDidMount() { + this._mounted = true; + } + componentWillMount() { if (!this.context[storeKey]) { console.warn('no store on context...'); return; } - this._update = () => this.forceUpdate(); + this._update = () => this._mounted && this.forceUpdate(); if (!options.listeners) { return; } @@ -87,6 +91,7 @@ module.exports = function(options: Options, Component: any): any { this._listeners.forEach(evt => { this.context[storeKey].off(evt, this._update); }); + this._mounted = false; } shouldComponentUpdate(nextProps, nextState) { diff --git a/plugins/Wrappers/WrappersFrontendControl.js b/plugins/Wrappers/WrappersFrontendControl.js new file mode 100644 index 0000000000..c034383b1f --- /dev/null +++ b/plugins/Wrappers/WrappersFrontendControl.js @@ -0,0 +1,30 @@ +/** + * 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'; + +var decorate = require('../../frontend/decorate'); +var SettingsCheckbox = require('../../frontend/SettingsCheckbox'); + +var Wrapped = decorate({ + listeners() { + return ['hidewrapperschange']; + }, + props(store) { + return { + state: store.hideWrappersState, + text: 'Hide Wrappers', + onChange: state => store.toggleHideWrappers(state), + }; + }, +}, SettingsCheckbox); + +module.exports = Wrapped; diff --git a/plugins/Wrappers/shouldSkipToChildRendering.js b/plugins/Wrappers/shouldSkipToChildRendering.js new file mode 100644 index 0000000000..b3e9b09efe --- /dev/null +++ b/plugins/Wrappers/shouldSkipToChildRendering.js @@ -0,0 +1,27 @@ +/** + * 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. + * + */ +'use strict'; + +/* + * Should hide higher-order wrapper components. An HOC is defined + * as a component that returns a single, non-DOM child. + */ +function shouldSkipToChildRendering(node, store) { + if (store.hideWrappersState && store.hideWrappersState.enabled) { + const children = node.get('children'); + if (children && children.length === 1) { + const childName = store.get(children[0]).get('name'); + return childName && !childName.match(/^[a-z]+$/); + } + } + return false; +} + +module.exports = shouldSkipToChildRendering; From c63a9d4ce9fd743050a9535d3796314ae0424f5c Mon Sep 17 00:00:00 2001 From: Zack Argyle Date: Fri, 10 Feb 2017 09:43:58 -0800 Subject: [PATCH 2/2] Fix flow --- frontend/Store.js | 2 +- frontend/decorate.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/Store.js b/frontend/Store.js index 2a2150d33b..f80a6dabca 100644 --- a/frontend/Store.js +++ b/frontend/Store.js @@ -90,7 +90,7 @@ class Store extends EventEmitter { bananaslugState: ?ControlState; colorizerState: ?ControlState; regexState: ?ControlState; - showWrappersState: ?ControlState; + hideWrappersState: ?ControlState; contextMenu: ?ContextMenu; hovered: ?ElementID; isBottomTagSelected: boolean; diff --git a/frontend/decorate.js b/frontend/decorate.js index 75aecaf101..95429d824e 100644 --- a/frontend/decorate.js +++ b/frontend/decorate.js @@ -56,7 +56,8 @@ module.exports = function(options: Options, Component: any): any { var storeKey = options.store || 'store'; class Wrapper extends React.Component { _listeners: Array; - _update: () => void; + _update: () => boolean|void; + _mounted: boolean; state: State; constructor(props) {