You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ga...@apache.org on 2016/11/02 15:01:04 UTC

fauxton commit: updated refs/heads/master to 13d1807

Repository: couchdb-fauxton
Updated Branches:
  refs/heads/master 5194d64f1 -> 13d18075c


Create React Layout

This creates a <Onepane/> layout that can be used instead of the
one_pane.html.


Project: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/commit/13d18075
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/13d18075
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/13d18075

Branch: refs/heads/master
Commit: 13d18075c3175ef81573916536065da7b2f3498f
Parents: 5194d64
Author: Garren Smith <ga...@gmail.com>
Authored: Tue Nov 1 14:36:57 2016 +0200
Committer: Garren Smith <ga...@gmail.com>
Committed: Wed Nov 2 17:00:46 2016 +0200

----------------------------------------------------------------------
 app/addons/activetasks/components.react.jsx     | 111 +++++++++----------
 app/addons/activetasks/layout.js                |  39 +++++++
 app/addons/activetasks/routes.js                |  22 ++--
 .../components/assets/less/components.less      |   1 +
 app/addons/components/assets/less/layouts.less  |  14 +++
 app/addons/components/layouts.js                |  85 ++++++++++++++
 .../components/react-components.react.jsx       |  14 ++-
 app/addons/fauxton/base.js                      |  36 +++---
 .../notifications/notifications.react.jsx       |   2 +-
 app/templates/layouts/empty.html                |  15 +++
 10 files changed, 246 insertions(+), 93 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/13d18075/app/addons/activetasks/components.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/activetasks/components.react.jsx b/app/addons/activetasks/components.react.jsx
index 4ce3a26..7ad0f4c 100644
--- a/app/addons/activetasks/components.react.jsx
+++ b/app/addons/activetasks/components.react.jsx
@@ -24,11 +24,11 @@ import ReactCSSTransitionGroup from "react-addons-css-transition-group";
 const TabElementWrapper = Components.TabElementWrapper;
 const TabElement = Components.TabElement;
 
-var activeTasksStore = Stores.activeTasksStore;
+const activeTasksStore = Stores.activeTasksStore;
 
-var ActiveTasksController = React.createClass({
+export const ActiveTasksController = React.createClass({
 
-  getStoreState: function () {
+  getStoreState () {
     return {
       collection: activeTasksStore.getCollection(),
       searchTerm: activeTasksStore.getSearchTerm(),
@@ -42,45 +42,42 @@ var ActiveTasksController = React.createClass({
     };
   },
 
-  getInitialState: function () {
+  getInitialState () {
     return this.getStoreState();
   },
 
-  componentDidMount: function () {
+  componentDidMount () {
+    Actions.init(new Resources.AllTasks());
     this.state.setPolling();
     activeTasksStore.on('change', this.onChange, this);
   },
 
-  componentWillUnmount: function () {
+  componentWillUnmount () {
     this.state.clearPolling();
     activeTasksStore.off('change', this.onChange, this);
   },
 
-  onChange: function () {
+  onChange () {
     this.setState(this.getStoreState());
   },
 
-  setNewSearchTerm: function (searchTerm) {
+  setNewSearchTerm (searchTerm) {
     Actions.setSearchTerm(searchTerm);
   },
 
-  switchTab: function (newRadioButton) {  //tabs buttons
+  switchTab (newRadioButton) {  //tabs buttons
     Actions.switchTab(newRadioButton);
   },
 
-  tableHeaderOnClick: function (headerClicked) {
+  tableHeaderOnClick (headerClicked) {
     Actions.sortByColumnHeader(headerClicked);
   },
 
-  render: function () {
-    var collection = this.state.collection;
-    var searchTerm = this.state.searchTerm;
-    var selectedRadio = this.state.selectedRadio;
-    var sortByHeader = this.state.sortByHeader;
-    var headerIsAscending = this.state.headerIsAscending;
+  render () {
+    const {collection, searchTerm, selectedRadio, sortByHeader, headerIsAscending} = this.state;
 
-    var setSearchTerm = this.setNewSearchTerm;
-    var onTableHeaderClick = this.tableHeaderOnClick;
+    const setSearchTerm = this.setNewSearchTerm;
+    const onTableHeaderClick = this.tableHeaderOnClick;
 
     return (
       <div id="active-tasks-page" className="scrollable">
@@ -104,7 +101,7 @@ var ActiveTasksController = React.createClass({
 });
 
 var ActiveTasksFilterTabs = React.createClass({
-  getDefaultProps: function () {
+  getDefaultProps () {
     return {
       radioNames : [
         'All Tasks',
@@ -116,16 +113,16 @@ var ActiveTasksFilterTabs = React.createClass({
     };
   },
 
-  checked: function (radioName) {
+  checked (radioName) {
     return this.props.selectedRadio === radioName;
   },
 
-  onRadioClick: function (e) {
+  onRadioClick (e) {
     var radioName = e.target.value;
     this.props.onRadioClick(radioName);
   },
 
-  createFilterTabs: function () {
+  createFilterTabs () {
     return (
       this.props.radioNames.map((radioName, i) => {
         const checked = this.checked(radioName);
@@ -141,12 +138,12 @@ var ActiveTasksFilterTabs = React.createClass({
     );
   },
 
-  searchTermChange: function (e) {
+  searchTermChange (e) {
     var searchTerm = e.target.value;
     this.props.onSearch(searchTerm);
   },
 
-  render: function () {
+  render () {
     const filterTabs = this.createFilterTabs();
     return (
       <TabElementWrapper>
@@ -167,7 +164,7 @@ var ActiveTasksFilterTabs = React.createClass({
 });
 
 var ActiveTaskTable = React.createClass({
-  render: function () {
+  render () {
     var collection = this.props.collection;
     var selectedRadio = this.props.selectedRadio;
     var searchTerm = this.props.searchTerm;
@@ -193,7 +190,7 @@ var ActiveTaskTable = React.createClass({
 });
 
 var ActiveTasksTableHeader = React.createClass({
-  getDefaultProps: function () {
+  getDefaultProps () {
     return {
       headerNames : [
         ['type', 'Type'],
@@ -206,7 +203,7 @@ var ActiveTasksTableHeader = React.createClass({
     };
   },
 
-  createTableHeadingFields: function () {
+  createTableHeadingFields () {
     var onTableHeaderClick = this.props.onTableHeaderClick;
     var sortByHeader = this.props.sortByHeader;
     var headerIsAscending = this.props.headerIsAscending;
@@ -221,7 +218,7 @@ var ActiveTasksTableHeader = React.createClass({
     });
   },
 
-  render: function () {
+  render () {
     return (
       <thead>
         <tr>{this.createTableHeadingFields()}</tr>
@@ -231,7 +228,7 @@ var ActiveTasksTableHeader = React.createClass({
 });
 
 var TableHeader = React.createClass({
-  arrow: function () {
+  arrow () {
     var sortBy = this.props.sortByHeader;
     var currentName = this.props.headerName;
     var headerIsAscending = this.props.headerIsAscending;
@@ -242,12 +239,12 @@ var TableHeader = React.createClass({
     }
   },
 
-  onTableHeaderClick: function (e) {
+  onTableHeaderClick (e) {
     var headerSelected = e.target.value;
     this.props.onTableHeaderClick(headerSelected);
   },
 
-  render: function () {
+  render () {
     var arrow = this.arrow();
     var th_class = 'header-field ' + this.props.headerName;
 
@@ -272,23 +269,23 @@ var TableHeader = React.createClass({
 
 var ActiveTasksTableBody = React.createClass({
 
-  getStoreState: function () {
+  getStoreState () {
     return {
       filteredTable: activeTasksStore.getFilteredTable(this.props.collection)
     };
   },
 
-  getInitialState: function () {
+  getInitialState () {
     return this.getStoreState();
   },
 
-  componentWillReceiveProps: function (nextProps) {
+  componentWillReceiveProps (nextProps) {
     this.setState({
       filteredTable: activeTasksStore.getFilteredTable(this.props.collection)
     });
   },
 
-  createRows: function () {
+  createRows () {
     var isThereASearchTerm = this.props.searchTerm.trim() === "";
 
     if (this.state.filteredTable.length === 0) {
@@ -300,7 +297,7 @@ var ActiveTasksTableBody = React.createClass({
     });
   },
 
-  noActiveTasks: function () {
+  noActiveTasks () {
     var type = this.props.selectedRadio;
     if (type === "All Tasks") {
       type = "";
@@ -313,7 +310,7 @@ var ActiveTasksTableBody = React.createClass({
     );
   },
 
-  noActiveTasksMatchFilter: function () {
+  noActiveTasksMatchFilter () {
     var type = this.props.selectedRadio;
     if (type === "All Tasks") {
       type = "";
@@ -326,7 +323,7 @@ var ActiveTasksTableBody = React.createClass({
     );
   },
 
-  render: function () {
+  render () {
     return (
       <tbody className="js-tasks-go-here">
       {this.createRows()}
@@ -336,7 +333,7 @@ var ActiveTasksTableBody = React.createClass({
 });
 
 var ActiveTaskTableBodyContents = React.createClass({
-  getInfo: function (item) {
+  getInfo (item) {
     return {
       type : item.type,
       objectField: activeTasksHelpers.getDatabaseFieldMessage(item),
@@ -347,7 +344,7 @@ var ActiveTaskTableBodyContents = React.createClass({
     };
   },
 
-  multilineMessage: function (messageArray, optionalClassName) {
+  multilineMessage (messageArray, optionalClassName) {
 
     if (!optionalClassName) {
       optionalClassName = '';
@@ -359,7 +356,7 @@ var ActiveTaskTableBodyContents = React.createClass({
     });
   },
 
-  render: function () {
+  render () {
     var rowData =  this.getInfo(this.props.item);
     var objectFieldMsg = this.multilineMessage(rowData.objectField, 'to-from-database');
     var startedOnMsg = this.multilineMessage(rowData.started_on, 'time');
@@ -380,7 +377,7 @@ var ActiveTaskTableBodyContents = React.createClass({
 });
 
 var ActiveTasksViewSourceSequence = React.createClass({
-  onTrayToggle: function (e) {
+  onTrayToggle (e) {
     e.preventDefault();
     this.refs.view_source_sequence_btn.toggle(function (shown) {
       if (shown) {
@@ -389,7 +386,7 @@ var ActiveTasksViewSourceSequence = React.createClass({
     }.bind(this));
   },
 
-  sequences: function (item) {
+  sequences (item) {
     if (_.isNumber(item) || _.isString(item)) {
       return <ComponentsReact.ClipboardWithTextField textToCopy={item} uniqueKey={item}/>;
     }
@@ -403,7 +400,7 @@ var ActiveTasksViewSourceSequence = React.createClass({
     return  <ComponentsReact.ClipboardWithTextField textToCopy="???" uniqueKey='unknownRevision'/>;
   },
 
-  render: function () {
+  render () {
 
     if (_.has(this.props.item, 'source_seq')) {
       var sequences = this.sequences(this.props.item.source_seq);
@@ -427,38 +424,38 @@ var ActiveTasksViewSourceSequence = React.createClass({
   }
 });
 
-var ActiveTasksPollingWidgetController = React.createClass({
+export const ActiveTasksPollingWidgetController = React.createClass({
 
-  getStoreState: function () {
+  getStoreState () {
     return {
       pollingInterval:  activeTasksStore.getPollingInterval(),
       isLoading: activeTasksStore.isLoading()
     };
   },
 
-  getInitialState: function () {
+  getInitialState () {
     return this.getStoreState();
   },
 
-  componentDidMount: function () {
+  componentDidMount () {
     activeTasksStore.on('change', this.onChange, this);
   },
 
-  onChange: function () {
+  onChange () {
     if (this.isMounted()) {
       this.setState(this.getStoreState());
     }
   },
 
-  pollingIntervalChange: function (event) {
+  pollingIntervalChange (event) {
     Actions.changePollingInterval(event.target.value);
   },
 
-  getPluralForLabel: function () {
+  getPluralForLabel () {
     return this.state.pollingInterval === "1" ? '' : 's';
   },
 
-  createPollingWidget: function () {
+  createPollingWidget () {
     const {pollingInterval} = this.state;
     const s = this.getPluralForLabel();
 
@@ -483,7 +480,7 @@ var ActiveTasksPollingWidgetController = React.createClass({
     );
   },
 
-  render: function () {
+  render () {
     var pollingWidget = this.createPollingWidget();
     var loadLines = null;
 
@@ -505,7 +502,7 @@ var ActiveTasksPollingWidgetController = React.createClass({
 });
 
 var activeTasksHelpers = {
-  getTimeInfo: function (timeStamp) {
+  getTimeInfo (timeStamp) {
     var timeMessage = [
         app.helpers.formatDate(timeStamp),
         app.helpers.getDateFromNow(timeStamp)
@@ -513,7 +510,7 @@ var activeTasksHelpers = {
     return timeMessage;
   },
 
-  getDatabaseFieldMessage: function (item) {
+  getDatabaseFieldMessage (item) {
     var type = item.type;
     var databaseFieldMessage = [];
 
@@ -530,7 +527,7 @@ var activeTasksHelpers = {
     return databaseFieldMessage;
   },
 
-  getProgressMessage: function (item) {
+  getProgressMessage (item) {
     var progressMessage = [];
     var type = item.type;
 
@@ -557,7 +554,7 @@ var activeTasksHelpers = {
     return progressMessage;
   },
 
-  getSourceSequence: function (item) {
+  getSourceSequence (item) {
     return item.source_seq;
   }
 

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/13d18075/app/addons/activetasks/layout.js
----------------------------------------------------------------------
diff --git a/app/addons/activetasks/layout.js b/app/addons/activetasks/layout.js
new file mode 100644
index 0000000..f893286
--- /dev/null
+++ b/app/addons/activetasks/layout.js
@@ -0,0 +1,39 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import React from 'react';
+import FauxtonAPI from "../../core/api";
+import {OnePane, OnePaneHeader, OnePaneContent} from '../components/layouts';
+import {ActiveTasksController, ActiveTasksPollingWidgetController} from "./components.react";
+
+const crumbs = [
+    {'name': 'Active Tasks'}
+  ];
+
+export const ActiveTasksLayout = () => {
+  return (
+    <OnePane>
+      <OnePaneHeader
+        crumbs={crumbs}
+        docURL={FauxtonAPI.constants.DOC_URLS.ACTIVE_TASKS}
+        endpoint={`${window.location.origin}/_active_tasks`}
+      >
+        <ActiveTasksPollingWidgetController />
+      </OnePaneHeader>
+      <OnePaneContent>
+        <ActiveTasksController />
+      </OnePaneContent>
+    </OnePane>
+  );
+};
+
+export default ActiveTasksLayout;

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/13d18075/app/addons/activetasks/routes.js
----------------------------------------------------------------------
diff --git a/app/addons/activetasks/routes.js b/app/addons/activetasks/routes.js
index e81dab2..efe3783 100644
--- a/app/addons/activetasks/routes.js
+++ b/app/addons/activetasks/routes.js
@@ -15,29 +15,23 @@ import FauxtonAPI from "../../core/api";
 import ActiveTasksResources from "./resources";
 import ActiveTasksComponents from "./components.react";
 import Actions from "./actions";
+import Layout from './layout';
 
 var ActiveTasksRouteObject = FauxtonAPI.RouteObject.extend({
   selectedHeader: 'Active Tasks',
-  layout: 'one_pane',
+  layout: 'empty',
+  hideNotificationCenter: true,
+  hideApiBar: true,
+
   routes: {
     'activetasks/:id': 'showActiveTasks',
     'activetasks': 'showActiveTasks'
   },
-  crumbs: [
-    {'name': 'Active Tasks'}
-  ],
-  apiUrl: function () {
-    var apiurl = window.location.origin + '/_active_tasks';
-    return [apiurl, FauxtonAPI.constants.DOC_URLS.ACTIVE_TASKS];
-  },
+
   roles: ['_admin'],
-  initialize: function () {
-    this.allTasks = new ActiveTasksResources.AllTasks();
-  },
+
   showActiveTasks: function () {
-    Actions.init(this.allTasks);
-    this.setComponent('#dashboard-content', ActiveTasksComponents.ActiveTasksController);
-    this.setComponent('#right-header', ActiveTasksComponents.ActiveTasksPollingWidgetController);
+    this.setComponent(".template", Layout);
   }
 });
 

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/13d18075/app/addons/components/assets/less/components.less
----------------------------------------------------------------------
diff --git a/app/addons/components/assets/less/components.less b/app/addons/components/assets/less/components.less
index 6df33c8..d9ef5c4 100644
--- a/app/addons/components/assets/less/components.less
+++ b/app/addons/components/assets/less/components.less
@@ -22,3 +22,4 @@
 @import "modals.less";
 @import "tab-element.less";
 @import "header-breadcrumbs.less";
+@import "layouts.less";

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/13d18075/app/addons/components/assets/less/layouts.less
----------------------------------------------------------------------
diff --git a/app/addons/components/assets/less/layouts.less b/app/addons/components/assets/less/layouts.less
new file mode 100644
index 0000000..86cc23c
--- /dev/null
+++ b/app/addons/components/assets/less/layouts.less
@@ -0,0 +1,14 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+.template {
+  height: 100%;
+}

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/13d18075/app/addons/components/layouts.js
----------------------------------------------------------------------
diff --git a/app/addons/components/layouts.js b/app/addons/components/layouts.js
new file mode 100644
index 0000000..462fb21
--- /dev/null
+++ b/app/addons/components/layouts.js
@@ -0,0 +1,85 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import {NotificationCenterButton} from "../fauxton/notifications/notifications.react";
+import {ApiBarController} from './react-components.react';
+import {Breadcrumbs} from './header-breadcrumbs';
+import ComponentActions from "./actions";
+
+const ApiBarWrapper = ({docURL, endpoint}) => {
+  //TODO once all modules are using this remove actions and make them props
+  ComponentActions.updateAPIBar({
+    buttonVisible: true,
+    contentVisible: false,
+    endpoint,
+    docURL
+  });
+  return (
+    <div id="api-navbar">
+      <ApiBarController
+        buttonVisible={true}
+        contentVisible={false}
+      />
+  </div>
+  );
+};
+
+export const OnePane = ({children}) => {
+  return (
+    <div id="dashboard" className="one-pane ">
+      {children}
+    </div>
+  );
+};
+
+export const OnePaneHeader = ({showApiUrl, docURL, endpoint, crumbs, children}) => {
+  return (
+    <header>
+      <div className="flex-layout flex-row">
+        <div id="breadcrumbs" className="flex-body">
+          <Breadcrumbs crumbs={crumbs}/>
+        </div>
+        <div id="right-header">
+          {children}
+        </div>
+        {showApiUrl ? <ApiBarWrapper docURL={docURL} endpoint={endpoint} /> : null}
+        <div id="notification-center-btn">
+          <NotificationCenterButton />
+        </div>
+      </div>
+    </header>
+  );
+};
+
+OnePaneHeader.defaultProps = {
+  showApiUrl: true,
+  crumbs: []
+};
+
+OnePaneHeader.propTypes = {
+  docURL: React.PropTypes.string.isRequired,
+  endpoint: React.PropTypes.string.isRequired,
+};
+
+export const OnePaneContent = ({children}) => {
+  return (
+    <div className="content-area container-fluid">
+      <div id="tabs"></div>
+      <div id="dashboard-content" className="scrollable">
+        {children}
+      </div>
+      <div id="footer"></div>
+    </div>
+  );
+};

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/13d18075/app/addons/components/react-components.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/components/react-components.react.jsx b/app/addons/components/react-components.react.jsx
index c45d19d..5b4a396 100644
--- a/app/addons/components/react-components.react.jsx
+++ b/app/addons/components/react-components.react.jsx
@@ -1322,7 +1322,7 @@ var TrayWrapper = React.createClass({
   }
 });
 
-var APIBar = React.createClass({
+const APIBar = React.createClass({
   propTypes: {
     buttonVisible: React.PropTypes.bool.isRequired,
     contentVisible: React.PropTypes.bool.isRequired,
@@ -1430,7 +1430,15 @@ var APIBar = React.createClass({
   }
 });
 
-var ApiBarController = React.createClass({
+export const ApiBarNoStore = ({docUrl, endpoint}) => {
+  return (
+    <TrayWrapper docUrl={docUrl} endpoint={endpoint}>
+      <APIBar buttonVisible={true} contentVisible={false} />
+    </TrayWrapper>
+  );
+};
+
+export const ApiBarController = React.createClass({
 
   getWrap: function () {
     return connectToStores(TrayWrapper, [componentStore], function () {
@@ -1444,7 +1452,7 @@ var ApiBarController = React.createClass({
   },
 
   render: function () {
-    var TrayWrapper = this.getWrap();
+    const TrayWrapper = this.getWrap();
     return (
       <TrayWrapper>
         <APIBar buttonVisible={true} contentVisible={false} />

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/13d18075/app/addons/fauxton/base.js
----------------------------------------------------------------------
diff --git a/app/addons/fauxton/base.js b/app/addons/fauxton/base.js
index d6376bc..e02c970 100644
--- a/app/addons/fauxton/base.js
+++ b/app/addons/fauxton/base.js
@@ -33,30 +33,30 @@ Fauxton.initialize = function () {
   FauxtonAPI.RouteObject.on('beforeEstablish', function (routeObject) {
     NavigationActions.setNavbarActiveLink(_.result(routeObject, 'selectedHeader'));
 
+    if (!routeObject.hideApiBar) {
     // always attempt to render the API Bar. Even if it's hidden on initial load, it may be enabled later
-    routeObject.setComponent('#api-navbar', ReactComponents.ApiBarController, {
-      buttonVisible: true,
-      contentVisible: false
-    });
-
-    const apiAndDocs = routeObject.get('apiUrl');
-    if (apiAndDocs) {
-      ComponentActions.updateAPIBar({
+      routeObject.setComponent('#api-navbar', ReactComponents.ApiBarController, {
         buttonVisible: true,
-        contentVisible: false,
-        endpoint: apiAndDocs[0],
-        docURL: apiAndDocs[1]
+        contentVisible: false
       });
-    } else {
-      ComponentActions.hideAPIBarButton();
+
+      const apiAndDocs = routeObject.get('apiUrl');
+      if (apiAndDocs) {
+        ComponentActions.updateAPIBar({
+          buttonVisible: true,
+          contentVisible: false,
+          endpoint: apiAndDocs[0],
+          docURL: apiAndDocs[1]
+        });
+      } else {
+        ComponentActions.hideAPIBarButton();
+      }
     }
 
     if (!routeObject.get('hideNotificationCenter')) {
       routeObject.setComponent('#notification-center-btn', NotificationComponents.NotificationCenterButton);
     }
 
-    FauxtonAPI.masterLayout.removeView('#breadcrumbs');
-
     const crumbs = routeObject.get('crumbs');
 
     if (!crumbs.length) {
@@ -66,17 +66,17 @@ Fauxton.initialize = function () {
     routeObject.setComponent('#breadcrumbs', Breadcrumbs, {crumbs: crumbs});
   });
 
-  var primaryNavBarEl = $('#primary-navbar')[0];
+  const primaryNavBarEl = $('#primary-navbar')[0];
   if (primaryNavBarEl) {
     NavbarReactComponents.renderNavBar(primaryNavBarEl);
   }
 
-  var notificationEl = $('#notifications')[0];
+  const notificationEl = $('#notifications')[0];
   if (notificationEl) {
     NotificationComponents.renderNotificationController(notificationEl);
   }
-  var versionInfo = new Fauxton.VersionInfo();
 
+  const versionInfo = new Fauxton.VersionInfo();
   versionInfo.fetch().then(function () {
     NavigationActions.setNavbarVersionInfo(versionInfo.get("version"));
   });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/13d18075/app/addons/fauxton/notifications/notifications.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/fauxton/notifications/notifications.react.jsx b/app/addons/fauxton/notifications/notifications.react.jsx
index a8f7814..6ea3a00 100644
--- a/app/addons/fauxton/notifications/notifications.react.jsx
+++ b/app/addons/fauxton/notifications/notifications.react.jsx
@@ -232,7 +232,7 @@ var Notification = React.createClass({
 });
 
 
-var NotificationCenterButton = React.createClass({
+export const NotificationCenterButton = React.createClass({
   getInitialState: function () {
     return {
       visible: true

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/13d18075/app/templates/layouts/empty.html
----------------------------------------------------------------------
diff --git a/app/templates/layouts/empty.html b/app/templates/layouts/empty.html
new file mode 100644
index 0000000..4c23640
--- /dev/null
+++ b/app/templates/layouts/empty.html
@@ -0,0 +1,15 @@
+<%/*
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+*/%>
+<div class="template">
+</div>