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..f80a6dabca 100644
--- a/frontend/Store.js
+++ b/frontend/Store.js
@@ -90,6 +90,7 @@ class Store extends EventEmitter {
bananaslugState: ?ControlState;
colorizerState: ?ControlState;
regexState: ?ControlState;
+ hideWrappersState: ?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..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) {
@@ -64,12 +65,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 +92,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;