diff --git a/frontend/Container.js b/frontend/Container.js index 8a503cb8c3..a1be42ba00 100644 --- a/frontend/Container.js +++ b/frontend/Container.js @@ -15,6 +15,7 @@ var PropState = require('./PropState'); var React = require('react'); var SearchPane = require('./SearchPane'); var SplitPane = require('./SplitPane'); +var TabbedPane = require('./TabbedPane'); import type MenuItem from './ContextMenu'; @@ -22,6 +23,7 @@ class Container extends React.Component { props: { reload: () => void, extraPanes: Array<(node: Object) => ReactElement>, + extraTabs: ?{[key: string]: () => ReactElement}, menuItems: { tree?: (id: string, node: Object, store: Object) => ?Array, attr?: ( @@ -36,45 +38,53 @@ class Container extends React.Component { }; render(): ReactElement { - var defaultItems = { - tree: (id, node, store) => { - var items = []; - if (node.get('name')) { - items.push({ - title: 'Show all ' + node.get('name'), - action: () => store.changeSearch(node.get('name')), - }); - } - if (store.capabilities.scroll) { - items.push({ - title: 'Scroll to node', - action: () => store.scrollToNode(id), - }); - } - return items; - }, - attr: (id, node, val, path, name, store) => { - var items = [{ - title: 'Store as global variable', - action: () => store.makeGlobal(id, path), - }]; - return items; - }, - }; - - return ( -
+ var tabs = { + Elements: () => ( } right={() => } /> - + ), + ...this.props.extraTabs, + }; + return ( +
+ +
); } } +var DEFAULT_MENU_ITEMS = { + tree: (id, node, store) => { + var items = []; + if (node.get('name')) { + items.push({ + title: 'Show all ' + node.get('name'), + action: () => store.changeSearch(node.get('name')), + }); + } + if (store.capabilities.scroll) { + items.push({ + title: 'Scroll to Node', + action: () => store.scrollToNode(id), + }); + } + return items; + }, + attr: (id, node, val, path, name, store) => { + var items = [{ + title: 'Store as global variable', + action: () => store.makeGlobal(id, path), + }]; + return items; + }, +}; + var styles = { container: { flex: 1, diff --git a/frontend/Store.js b/frontend/Store.js index b6b517c429..d166fc5a7b 100644 --- a/frontend/Store.js +++ b/frontend/Store.js @@ -91,6 +91,7 @@ class Store extends EventEmitter { roots: List; searchRoots: ?List; searchText: string; + selectedTab: string; selected: ?ElementID; breadcrumbHead: ?ElementID; // an object describing the capabilities of the inspected runtime. @@ -111,6 +112,7 @@ class Store extends EventEmitter { this.searchRoots = null; this.hovered = null; this.selected = null; + this.selectedTab = 'Elements'; this.breadcrumbHead = null; this.isBottomTagSelected = false; this.searchText = ''; @@ -140,6 +142,7 @@ class Store extends EventEmitter { this._bridge.on('select', ({id, quiet}) => { this._revealDeep(id); this.selectTop(this.skipWrapper(id), quiet); + this.setSelectedTab('Elements'); }); this._establishConnection(); @@ -170,6 +173,14 @@ class Store extends EventEmitter { this._bridge.send('scrollToNode', id); } + setSelectedTab(name: string): void { + if (this.selectedTab === name) { + return; + } + this.selectedTab = name; + this.emit('selectedTab'); + } + // TODO(jared): get this working for react native changeTextContent(id: ElementID, text: string): void { this._bridge.send('changeTextContent', {id, text}); diff --git a/frontend/TabbedPane.js b/frontend/TabbedPane.js new file mode 100644 index 0000000000..86c71f9faa --- /dev/null +++ b/frontend/TabbedPane.js @@ -0,0 +1,99 @@ +/** + * 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 React = require('react'); +var assign = require('object-assign'); +var decorate = require('./decorate'); + +class TabbedPane { + props: { + tabs: {[key: string]: () => ReactElement}, + selected: string, + setSelectedTab: (name: string) => void, + }; + + render() { + var tabs = Object.keys(this.props.tabs); + if (tabs.length === 1) { + return this.props.tabs[tabs[0]](); + } + return ( +
+
    + {tabs.map((name, i) => { + var style = styles.tab; + if (name === this.props.selected) { + style = assign({}, style, styles.selectedTab); + } + if (i === tabs.length - 1) { + style = assign({}, style, styles.lastTab); + } + return ( +
  • this.props.setSelectedTab(name)}> + {name} +
  • + ); + })} +
+
+ {this.props.tabs[this.props.selected]()} +
+
+ ); + } +} + +var styles = { + container:{ + flex: 1, + display: 'flex', + flexDirection: 'column', + }, + tabs: { + display: 'flex', + flexShrink: 0, + listStyle: 'none', + backgroundColor: '#eee', + borderBottom: '1px solid rgb(163, 163, 163)', + margin: 0, + padding: '0 2px', + }, + tab: { + padding: '2px 4px', + lineHeight: '15px', + fontSize: 12, + fontFamily: "'Lucida Grande', sans-serif", + cursor: 'pointer', + borderLeft: '1px solid rgb(163, 163, 163)', + }, + lastTab: { + borderRight: '1px solid rgb(163, 163, 163)', + }, + selectedTab: { + backgroundColor: 'white', + }, + body: { + flex: 1, + display: 'flex', + minHeight: 0, + }, +}; + +module.exports = decorate({ + listeners: () => ['selectedTab'], + props(store) { + return { + selected: store.selectedTab, + setSelectedTab: name => store.setSelectedTab(name), + }; + }, +}, TabbedPane); diff --git a/shells/plain/index.html b/shells/plain/index.html index fc18b4522f..8866b77a83 100644 --- a/shells/plain/index.html +++ b/shells/plain/index.html @@ -11,7 +11,7 @@ } #container { display: flex; - height: 500px; + height: 400px; } body { margin: 0; @@ -22,6 +22,8 @@ left: 0; right: 0; bottom: 0; + margin: 0; + padding: 0; }