dome: front-end: add boards app (originally the WelcomePage)

The purpose of WelcomePage was letting the user to select board. Once
selected, the user wasn't able to go back to the WelcomePage to select
other boards previously. This is a problem since the user will need to
change board some times.

Making this page a new app solves the problem, also unified the
behavior of all apps (WelcomePage was a special "board selection app"
before).

This patch:
- Renames WelcomePage to BoardsApp
- Merges AppPage into DomeApp

BUG=b:30999924
TEST=Manually tested

Change-Id: Icbbe3b9f4a4cd5cced2d38246bbcfdf5e8d8bae1
Reviewed-on: https://chromium-review.googlesource.com/386544
Commit-Ready: Mao Huang <littlecvr@chromium.org>
Tested-by: Mao Huang <littlecvr@chromium.org>
Reviewed-by: Wei-Han Chen <stimim@chromium.org>
diff --git a/py/dome/frontend/components/AppPage.js b/py/dome/frontend/components/AppPage.js
deleted file mode 100644
index 3ce28c0..0000000
--- a/py/dome/frontend/components/AppPage.js
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright 2016 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {connect} from 'react-redux';
-import Drawer from 'material-ui/Drawer';
-import Immutable from 'immutable';
-import MenuItem from 'material-ui/MenuItem';
-import React from 'react';
-
-import AppNames from '../constants/AppNames';
-import BundlesApp from './BundlesApp';
-import DomeActions from '../actions/domeactions';
-import FixedAppBar from './FixedAppBar';
-import SettingsApp from './SettingsApp';
-
-const AppPage = React.createClass({
-  propTypes: {
-    app: React.PropTypes.string.isRequired,
-    switchApp: React.PropTypes.func.isRequired,
-    tasks: React.PropTypes.instanceOf(Immutable.Map).isRequired
-  },
-
-  toggleAppMenu() {
-    this.setState({appMenuOpened: !this.state.appMenuOpened});
-  },
-
-  handleClick(nextApp) {
-    // close the drawer
-    this.setState({appMenuOpened: false});
-    this.props.switchApp(nextApp);
-  },
-
-  getInitialState() {
-    return {
-      appMenuOpened: false,
-    };
-  },
-
-  render() {
-    // must not let the task list cover the main content
-    // 82 = 24 + 58
-    //   24: space above the task list
-    //   58: height of the title bar of the task list
-    // 48: height of each task item
-    // 24: space below task list
-    // TODO(littlecvr): find a better way to get the dimension of TaskList
-    //                  instead of calculating our own
-    var paddingBottom = (this.props.tasks.size == 0 ? 0 : 82) +
-        48 * (this.state.taskListCollapsed ? 0 : this.props.tasks.size) + 24;
-
-    var app = null;
-    if (this.props.app == AppNames.BUNDLES_APP) {
-      // TODO(littlecvr): standardize the floating button API so we don't need
-      //                  to pass offset like this
-      app = <BundlesApp offset={paddingBottom} />;
-    } else if (this.props.app == AppNames.SETTINGS_APP) {
-      app = <SettingsApp />;
-    } else {
-      console.error(`Unknown app ${this.props.app}`);
-    }
-
-    return (
-      <div style={{paddingBottom}}>
-        <FixedAppBar
-          title="Dome"
-          onLeftIconButtonTouchTap={this.toggleAppMenu}
-        />
-        <Drawer
-          docked={false}
-          open={this.state.appMenuOpened}
-          onRequestChange={open => this.setState({appMenuOpened: open})}
-        >
-          <MenuItem onTouchTap={() => this.handleClick(AppNames.BUNDLES_APP)}>
-            Bundles
-          </MenuItem>
-          <MenuItem onTouchTap={() => this.handleClick(AppNames.SETTINGS_APP)}>
-            Settings
-          </MenuItem>
-        </Drawer>
-        {app}
-      </div>
-    );
-  }
-});
-
-function mapStateToProps(state) {
-  return {
-    app: state.getIn(['dome', 'currentApp']),
-    tasks: state.getIn(['dome', 'tasks'])
-  };
-}
-
-function mapDispatchToProps(dispatch) {
-  return {
-    switchApp: nextApp => dispatch(DomeActions.switchApp(nextApp))
-  };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(AppPage);
diff --git a/py/dome/frontend/components/WelcomePage.js b/py/dome/frontend/components/BoardsApp.js
similarity index 97%
rename from py/dome/frontend/components/WelcomePage.js
rename to py/dome/frontend/components/BoardsApp.js
index ac6a2b2..67d407a 100644
--- a/py/dome/frontend/components/WelcomePage.js
+++ b/py/dome/frontend/components/BoardsApp.js
@@ -16,7 +16,7 @@
 
 import DomeActions from '../actions/domeactions';
 
-var WelcomePage = React.createClass({
+var BoardsApp = React.createClass({
   propTypes: {
     boards: React.PropTypes.instanceOf(Immutable.List).isRequired,
 
@@ -182,4 +182,4 @@
   };
 }
 
-export default connect(mapStateToProps, mapDispatchToProps)(WelcomePage);
+export default connect(mapStateToProps, mapDispatchToProps)(BoardsApp);
diff --git a/py/dome/frontend/components/DomeApp.js b/py/dome/frontend/components/DomeApp.js
index 9e2528e..45ca966 100644
--- a/py/dome/frontend/components/DomeApp.js
+++ b/py/dome/frontend/components/DomeApp.js
@@ -3,33 +3,104 @@
 // found in the LICENSE file.
 
 import {connect} from 'react-redux';
+import Drawer from 'material-ui/Drawer';
+import Immutable from 'immutable';
+import MenuItem from 'material-ui/MenuItem';
 import React from 'react';
 
-import WelcomePage from './WelcomePage';
-import AppPage from './AppPage';
+import AppNames from '../constants/AppNames';
+import BoardsApp from './BoardsApp';
+import BundlesApp from './BundlesApp';
+import DomeActions from '../actions/domeactions';
+import FixedAppBar from './FixedAppBar';
+import SettingsApp from './SettingsApp';
 import TaskList from './TaskList';
 
 var DomeApp = React.createClass({
   propTypes: {
-    board: React.PropTypes.string.isRequired
+    app: React.PropTypes.string.isRequired,
+    board: React.PropTypes.string.isRequired,
+    switchApp: React.PropTypes.func.isRequired,
+    tasks: React.PropTypes.instanceOf(Immutable.Map).isRequired
+  },
+
+  handleClick(nextApp) {
+    // close the drawer
+    this.setState({appMenuOpened: false});
+    this.props.switchApp(nextApp);
   },
 
   setTaskListCollapsed(collapsed) {
     this.setState({taskListCollapsed: collapsed});
   },
 
+  toggleAppMenu() {
+    this.setState({appMenuOpened: !this.state.appMenuOpened});
+  },
+
   getInitialState() {
     return {
+      appMenuOpened: false,
       taskListCollapsed: false
     };
   },
 
   render() {
-    return (
-      <div>
-        {this.props.board === '' && <WelcomePage />}
-        {this.props.board !== '' && <AppPage />}
+    // must not let the task list cover the main content
+    // 82 = 24 + 58
+    //   24: space above the task list
+    //   58: height of the title bar of the task list
+    // 48: height of each task item
+    // 24: space below task list
+    // TODO(littlecvr): find a better way to get the dimension of TaskList
+    //                  instead of calculating our own
+    var paddingBottom = (this.props.tasks.size == 0 ? 0 : 82) +
+        48 * (this.state.taskListCollapsed ? 0 : this.props.tasks.size) + 24;
 
+    // TODO(b/31579770): should define a "app" system (like a dynamic module
+    //                   system), which automatically import and display
+    //                   corresponding app intead of writing a long if-elif-else
+    //                   statement.
+    var app = null;
+    if (this.props.app == AppNames.BOARDS_APP) {
+      app = <BoardsApp />;
+    } else if (this.props.app == AppNames.BUNDLES_APP) {
+      // TODO(littlecvr): standardize the floating button API so we don't need
+      //                  to pass offset like this
+      app = <BundlesApp offset={paddingBottom} />;
+    } else if (this.props.app == AppNames.SETTINGS_APP) {
+      app = <SettingsApp />;
+    } else {
+      console.error(`Unknown app ${this.props.app}`);
+    }
+
+    return (
+      <div style={{paddingBottom}}>
+        <FixedAppBar
+          title="Dome"
+          onLeftIconButtonTouchTap={this.toggleAppMenu}
+        />
+        <Drawer
+          docked={false}
+          open={this.state.appMenuOpened}
+          onRequestChange={open => this.setState({appMenuOpened: open})}
+        >
+          <MenuItem onTouchTap={() => this.handleClick(AppNames.BOARDS_APP)}>
+            Boards
+          </MenuItem>
+          {/* TODO(b/31579770): leave this to the app itself to determine where
+                                and when to show
+          */}
+          {this.props.board != '' &&
+            <MenuItem onTouchTap={() => this.handleClick(AppNames.BUNDLES_APP)}>
+              Bundles
+            </MenuItem>
+          }
+          <MenuItem onTouchTap={() => this.handleClick(AppNames.SETTINGS_APP)}>
+            Settings
+          </MenuItem>
+        </Drawer>
+        {app}
         <TaskList
           collapsed={this.state.taskListCollapsed}
           setCollapsed={this.setTaskListCollapsed}
@@ -41,8 +112,16 @@
 
 function mapStateToProps(state) {
   return {
-    board: state.getIn(['dome', 'currentBoard'])
+    app: state.getIn(['dome', 'currentApp']),
+    board: state.getIn(['dome', 'currentBoard']),
+    tasks: state.getIn(['dome', 'tasks'])
   };
 }
 
-export default connect(mapStateToProps, null)(DomeApp);
+function mapDispatchToProps(dispatch) {
+  return {
+    switchApp: nextApp => dispatch(DomeActions.switchApp(nextApp))
+  };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(DomeApp);
diff --git a/py/dome/frontend/constants/AppNames.js b/py/dome/frontend/constants/AppNames.js
index 4a109a9..dc01fa9 100644
--- a/py/dome/frontend/constants/AppNames.js
+++ b/py/dome/frontend/constants/AppNames.js
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 export default {
+  BOARDS_APP: 'BOARDS_APP',
   BUNDLES_APP: 'BUNDLES_APP',
   SETTINGS_APP: 'SETTINGS_APP'
 };
diff --git a/py/dome/frontend/reducers/domereducer.js b/py/dome/frontend/reducers/domereducer.js
index 687e1c9..4fecc63 100644
--- a/py/dome/frontend/reducers/domereducer.js
+++ b/py/dome/frontend/reducers/domereducer.js
@@ -11,7 +11,7 @@
 const INITIAL_STATE = Immutable.fromJS({
   boards: [],
   currentBoard: '',
-  currentApp: AppNames.BUNDLES_APP,  // default app is bundle manager
+  currentApp: AppNames.BOARDS_APP,  // default app is the board selection page
   formVisibility: {
   },
   formPayload: {
@@ -25,7 +25,11 @@
       return state.set('boards', Immutable.fromJS(action.boards));
 
     case ActionTypes.SWITCH_BOARD:
-      return state.set('currentBoard', action.nextBoard);
+      return state.withMutations(s => {
+        s.set('currentBoard', action.nextBoard);
+        // switch to bundle manager after switching board by default
+        s.set('currentApp', AppNames.BUNDLES_APP);
+      });
 
     case ActionTypes.SWITCH_APP:
       return state.set('currentApp', action.nextApp);