diff --git a/package.json b/package.json index d7f41097f..99569e5db 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "identicon.js": "^1.2.1", "inject-css": "^0.1.1", "jazzicon": "^1.1.3", + "menu-droppo": "^1.1.0", "metamask-logo": "^1.1.5", "multiplex": "^6.7.0", "once": "^1.3.3", @@ -52,6 +53,7 @@ "redux": "^3.0.5", "redux-logger": "^2.3.1", "redux-thunk": "^1.0.2", + "sandwich-expando": "^1.0.5", "textarea-caret": "^3.0.1", "three.js": "^0.73.2", "through2": "^2.0.1", diff --git a/ui/app/actions.js b/ui/app/actions.js index f489eede7..ee5e417d4 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -1,6 +1,11 @@ var actions = { GO_HOME: 'GO_HOME', goHome: goHome, + // menu state + TOGGLE_MENU: 'TOGGLE_MENU', + toggleMenu: toggleMenu, + SET_MENU_STATE: 'SET_MENU_STATE', + closeMenu: closeMenu, // remote state UPDATE_METAMASK_STATE: 'UPDATE_METAMASK_STATE', updateMetamaskState: updateMetamaskState, @@ -105,6 +110,21 @@ function goHome() { } } +// menu state + +function toggleMenu() { + return { + type: this.TOGGLE_MENU, + } +} + +function closeMenu() { + return { + type: this.SET_MENU_STATE, + value: false, + } +} + // async actions function tryUnlockMetamask(password) { diff --git a/ui/app/app.js b/ui/app/app.js index 68d34e52f..511012fab 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -24,6 +24,9 @@ const ConfigScreen = require('./config') const InfoScreen = require('./info') const LoadingIndicator = require('./loading') const txHelper = require('../lib/tx-helper') +const SandwichExpando = require('sandwich-expando') +const MenuDroppo = require('menu-droppo') +const DropMenuItem = require('./components/drop-menu-item') module.exports = connect(mapStateToProps)(App) @@ -42,6 +45,7 @@ function mapStateToProps(state) { seedWords: state.metamask.seedWords, unconfTxs: state.metamask.unconfTxs, unconfMsgs: state.metamask.unconfMsgs, + menuOpen: state.appState.menuOpen, } } @@ -64,6 +68,7 @@ App.prototype.render = function() { // app bar this.renderAppBar(), + this.renderDropdown(), // panel content h('.app-primary.flex-grow' + (transForward ? '.from-right' : '.from-left'), { @@ -127,47 +132,104 @@ App.prototype.renderAppBar = function(){ return ( - h('.app-header.flex-row.flex-space-between', { - style: { - alignItems: 'center', - visibility: state.isUnlocked ? 'visibile' : 'none', - background: state.isUnlocked ? 'white' : 'none', - height: '36px', - }, - }, state.isUnlocked && [ + h('div', [ - // mini logo - h('img', { - height: 24, - width: 24, - src: '/images/icon-128.png', - }), + h('.app-header.flex-row.flex-space-between', { + style: { + alignItems: 'center', + visibility: state.isUnlocked ? 'visible' : 'none', + background: state.isUnlocked ? 'white' : 'none', + height: '36px', + position: 'relative', + zIndex: 1, + }, + }, state.isUnlocked && [ - // metamask name - h('h1', 'MetaMask'), + // mini logo + h('img', { + height: 24, + width: 24, + src: '/images/icon-128.png', + }), - // hamburger - h('i.fa.fa-bars.cursor-pointer.color-orange', { - onClick: (event) => state.dispatch(actions.showConfigPage()), - }), + // metamask name + h('h1', 'MetaMask'), + // hamburger + h(SandwichExpando, { + width: 16, + barHeight: 2, + padding: 0, + isOpen: state.menuOpen, + color: 'rgb(247,146,30)', + onClick: (event) => { + event.preventDefault() + event.stopPropagation() + this.props.dispatch(actions.toggleMenu()) + }, + }), + ]), ]) - ) } -App.prototype.renderPrimary = function(state){ - var state = this.props +App.prototype.renderDropdown = function() { + const props = this.props + return h(MenuDroppo, { + isOpen: props.menuOpen, + onClickOutside: (event) => { + this.props.dispatch(actions.closeMenu()) + }, + style: { + position: 'fixed', + right: 0, + zIndex: 0, + }, + innerStyle: { + background: 'white', + boxShadow: '1px 1px 2px rgba(0,0,0,0.1)', + }, + }, [ // DROP MENU ITEMS + h('style', ` + .drop-menu-item:hover { background:rgb(235, 235, 235); } + .drop-menu-item i { margin: 11px; } + `), - if (state.seedWords) { + h(DropMenuItem, { + label: 'Settings', + closeMenu:() => this.props.dispatch(actions.closeMenu()), + action:() => this.props.dispatch(actions.showConfigPage()), + icon: h('i.fa.fa-gear.fa-lg', { ariaHidden: true }), + }), + + h(DropMenuItem, { + label: 'Lock Account', + closeMenu:() => this.props.dispatch(actions.closeMenu()), + action:() => this.props.dispatch(actions.lockMetamask()), + icon: h('i.fa.fa-lock.fa-lg', { ariaHidden: true }), + }), + + h(DropMenuItem, { + label: 'Help', + closeMenu:() => this.props.dispatch(actions.closeMenu()), + action:() => this.props.dispatch(actions.showInfoPage()), + icon: h('i.fa.fa-question.fa-lg', { ariaHidden: true }), + }), + ]) +} + +App.prototype.renderPrimary = function(){ + var props = this.props + + if (props.seedWords) { return h(CreateVaultCompleteScreen, {key: 'createVaultComplete'}) } // show initialize screen - if (!state.isInitialized) { + if (!props.isInitialized) { // show current view - switch (state.currentView.name) { + switch (props.currentView.name) { case 'createVault': return h(CreateVaultScreen, {key: 'createVault'}) @@ -185,12 +247,12 @@ App.prototype.renderPrimary = function(state){ } // show unlock screen - if (!state.isUnlocked) { + if (!props.isUnlocked) { return h(UnlockScreen, {key: 'locked'}) } // show current view - switch (state.currentView.name) { + switch (props.currentView.name) { case 'accounts': return h(AccountsScreen, {key: 'accounts'}) diff --git a/ui/app/components/drop-menu-item.js b/ui/app/components/drop-menu-item.js new file mode 100644 index 000000000..c8e61278c --- /dev/null +++ b/ui/app/components/drop-menu-item.js @@ -0,0 +1,31 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits + +module.exports = DropMenuItem + + +inherits(DropMenuItem, Component) +function DropMenuItem() { + Component.call(this) +} + +DropMenuItem.prototype.render = function() { + + return h('li.drop-menu-item', { + onClick:() => { + this.props.closeMenu() + this.props.action() + }, + style: { + listStyle: 'none', + padding: '6px 16px 6px 5px', + fontFamily: 'Transat Medium', + color: 'rgb(125, 128, 130)', + cursor: 'pointer', + }, + }, [ + this.props.icon, + this.props.label, + ]) +} diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 0e0740c9d..2cdbaf6f3 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -22,6 +22,7 @@ function reduceApp(state, action) { var seedWords = state.metamask.seedWords var appState = extend({ + menuOpen: false, currentView: seedWords ? seedConfView : defaultView, accountDetail: { subview: 'transactions', @@ -34,6 +35,16 @@ function reduceApp(state, action) { switch (action.type) { + case actions.TOGGLE_MENU: + return extend(appState, { + menuOpen: !appState.menuOpen, + }) + + case actions.SET_MENU_STATE: + return extend(appState, { + menuOpen: action.value, + }) + // intialize case actions.SHOW_CREATE_VAULT: