nifty-wallet/ui/app/app.js

652 lines
18 KiB
JavaScript
Raw Normal View History

const inherits = require('util').inherits
const Component = require('react').Component
const connect = require('react-redux').connect
const h = require('react-hyperscript')
const actions = require('./actions')
// init
const InitializeMenuScreen = require('./first-time/init-menu')
2016-10-20 11:33:18 -07:00
const NewKeyChainScreen = require('./new-keychain')
// unlock
const UnlockScreen = require('./unlock')
// accounts
2017-07-30 20:42:12 -07:00
const MainContainer = require('./main-container')
const SendTransactionScreen = require('./send')
const ConfirmTxScreen = require('./conf-tx')
// notice
2016-12-20 15:54:59 -08:00
const NoticeScreen = require('./components/notice')
2016-12-22 14:09:10 -08:00
const generateLostAccountsNotice = require('../lib/lost-accounts-notice')
// slideout menu
const WalletView = require('./components/wallet-view')
const SlideoutMenu = require('react-burger-menu').slide
// other views
const ConfigScreen = require('./config')
const AddTokenScreen = require('./add-token')
const Import = require('./accounts/import')
const InfoScreen = require('./info')
const Loading = require('./components/loading')
2016-05-18 12:30:03 -07:00
const SandwichExpando = require('sandwich-expando')
2017-07-24 17:04:13 -07:00
const Dropdown = require('./components/dropdown').Dropdown
const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem
const NetworkIndicator = require('./components/network')
const BuyView = require('./components/buy-button-subview')
2016-08-18 10:40:35 -07:00
const QrView = require('./components/qr-code')
const HDCreateVaultComplete = require('./keychains/hd/create-vault-complete')
const HDRestoreVaultScreen = require('./keychains/hd/restore-vault')
2016-11-01 22:19:04 -07:00
const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation')
module.exports = connect(mapStateToProps)(App)
inherits(App, Component)
2016-06-21 13:18:32 -07:00
function App () { Component.call(this) }
2016-06-21 13:18:32 -07:00
function mapStateToProps (state) {
return {
// state from plugin
2017-08-02 13:32:02 -07:00
sidebarOpen: state.appState.sidebarOpen,
isLoading: state.appState.isLoading,
loadingMessage: state.appState.loadingMessage,
noActiveNotices: state.metamask.noActiveNotices,
isInitialized: state.metamask.isInitialized,
isUnlocked: state.metamask.isUnlocked,
currentView: state.appState.currentView,
activeAddress: state.appState.activeAddress,
transForward: state.appState.transForward,
seedWords: state.metamask.seedWords,
unapprovedTxs: state.metamask.unapprovedTxs,
unapprovedMsgs: state.metamask.unapprovedMsgs,
2016-05-18 12:30:03 -07:00
menuOpen: state.appState.menuOpen,
network: state.metamask.network,
2016-06-28 18:06:10 -07:00
provider: state.metamask.provider,
forgottenPassword: state.appState.forgottenPassword,
lastUnreadNotice: state.metamask.lastUnreadNotice,
lostAccounts: state.metamask.lostAccounts,
frequentRpcList: state.metamask.frequentRpcList || [],
}
}
2016-06-21 13:18:32 -07:00
App.prototype.render = function () {
var props = this.props
const { isLoading, loadingMessage, transForward, network, sidebarOpen } = props
const isLoadingNetwork = network === 'loading' && props.currentView.name !== 'config'
2017-06-14 19:36:37 -07:00
const loadMessage = loadingMessage || isLoadingNetwork ?
`Connecting to ${this.getNetworkName()}` : null
log.debug('Main ui render function')
return (
h('.flex-column.full-height', {
style: {
// Windows was showing a vertical scroll bar:
overflowX: 'hidden',
// TODO: check with dev who committed L75, see if this still happens, and whether auto is enough
// overflowY: 'auto',
2016-06-30 23:50:20 -07:00
position: 'relative',
2017-07-24 17:04:13 -07:00
alignItems: 'center',
2016-06-21 13:18:32 -07:00
},
2016-05-13 18:07:47 -07:00
}, [
2016-05-13 14:56:29 -07:00
// app bar
2016-05-13 18:07:47 -07:00
this.renderAppBar(),
// slideout - move to separate render func
this.renderSidebar(),
// h('div.phone-visible', {} ,[
// h(SlideoutMenu, {
// isOpen: false,
// }, [
// h(WalletView, {
// responsiveDisplayClassname: '.phone-visible',
// }),
// ]),
// ])
// network dropdown
this.renderNetworkDropdown(),
// this.renderDropdown(),
h(Loading, {
isLoading: isLoading || isLoadingNetwork,
2017-06-14 19:36:37 -07:00
loadingMessage: loadMessage,
}),
// panel content
2017-07-30 20:42:12 -07:00
this.renderPrimary(),
])
)
}
App.prototype.renderSidebar = function() {
if (!this.props.sidebarOpen) {
return null;
}
return h('div.phone-visible', {}, [
// content
h(WalletView, {
responsiveDisplayClassname: '.phone-visible',
style: {
zIndex: 26,
position: 'fixed',
top: '6%',
left: '0px',
right: '0px',
bottom: '0px',
opacity: '1',
visibility: 'visible',
transition: 'transform 0.3s ease-out',
willChange: 'transform',
transform: 'translateX(0%)',
overflowY: 'auto',
boxShadow: 'rgba(0, 0, 0, 0.15) 2px 2px 4px',
width: '85%',
height: '100%',
},
}),
// overlay
h('div', {
style: {
zIndex: 25,
position: 'fixed',
top: '6%',
left: '0px',
right: '0px',
bottom: '0px',
opacity: '1',
visibility: 'visible',
transition: 'opacity 0.3s ease-out, visibility 0.3s ease-out',
backgroundColor: 'rgba(0, 0, 0, 0.3)',
}
}, [])
])
}
2016-06-21 13:18:32 -07:00
App.prototype.renderAppBar = function () {
if (window.METAMASK_UI_TYPE === 'notification') {
return null
}
const props = this.props
const state = this.state || {}
const isNetworkMenuOpen = state.isNetworkMenuOpen || false
2016-05-13 18:07:47 -07:00
return (
h('.full-width', {
height: '38px',
2017-07-24 17:04:13 -07:00
}, [
2016-05-13 18:07:47 -07:00
2016-05-18 14:36:35 -07:00
h('.app-header.flex-row.flex-space-between', {
style: {
alignItems: 'center',
visibility: props.isUnlocked ? 'visible' : 'none',
background: '#EFEFEF', // $gallery
2017-07-30 20:42:12 -07:00
height: '11vh',
position: 'relative',
zIndex: 12,
2016-05-18 14:36:35 -07:00
},
2017-02-15 15:35:56 -08:00
}, [
2016-05-18 14:36:35 -07:00
h('div.left-menu-section', {
style: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
2017-07-30 20:42:12 -07:00
marginBottom: '1.8em',
2016-06-21 13:18:32 -07:00
},
}, [
2016-12-16 10:04:57 -08:00
// mini logo
h('img', {
height: 24,
width: 24,
src: '/images/icon-128.png',
}),
// metamask name
h('h1', {
style: {
position: 'relative',
left: '9px',
2017-01-04 15:04:51 -08:00
},
}, 'MetaMask'),
2016-05-18 14:36:35 -07:00
]),
h('div', {
style: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
2017-07-30 20:42:12 -07:00
marginBottom: '1.8em',
2016-05-18 14:36:35 -07:00
},
}, [
// Network Indicator
h(NetworkIndicator, {
network: this.props.network,
provider: this.props.provider,
onClick: (event) => {
event.preventDefault()
event.stopPropagation()
this.setState({ isNetworkMenuOpen: !isNetworkMenuOpen })
},
}),
]),
2016-05-18 14:36:35 -07:00
]),
2017-07-29 13:31:53 -07:00
2016-05-18 14:36:35 -07:00
])
2016-05-13 18:07:47 -07:00
)
}
2016-06-21 13:18:32 -07:00
App.prototype.renderNetworkDropdown = function () {
const props = this.props
2017-07-24 17:04:13 -07:00
const { provider: { type: providerType, rpcTarget: activeNetwork } } = props
const rpcList = props.frequentRpcList
const state = this.state || {}
const isOpen = state.isNetworkMenuOpen
2017-07-24 17:04:13 -07:00
return h(Dropdown, {
isOpen,
2016-06-21 13:18:32 -07:00
onClickOutside: (event) => {
2017-07-28 15:55:55 -07:00
const { classList } = event.target
const isNotToggleElement = [
classList.contains('menu-icon'),
classList.contains('network-name'),
classList.contains('network-indicator'),
].filter(bool => bool).length === 0;
// classes from three constituent nodes of the toggle element
if (isNotToggleElement) {
this.setState({ isNetworkMenuOpen: false })
}
},
zIndex: 11,
style: {
2016-06-30 23:50:20 -07:00
position: 'absolute',
right: '2px',
top: '38px',
},
2017-07-26 13:02:08 -07:00
innerStyle: {
padding: '2px 16px 2px 0px',
},
2017-07-24 17:04:13 -07:00
}, [
h(
DropdownMenuItem,
{
2017-07-27 18:43:09 -07:00
key: 'main',
2017-07-24 17:04:13 -07:00
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
onClick: () => props.dispatch(actions.setProviderType('mainnet')),
},
[
h('.menu-icon.diamond'),
'Main Ethereum Network',
providerType === 'mainnet' ? h('.check', '✓') : null,
]
),
h(
DropdownMenuItem,
{
2017-07-27 18:43:09 -07:00
key: 'ropsten',
2017-07-24 17:04:13 -07:00
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
onClick: () => props.dispatch(actions.setProviderType('ropsten')),
},
[
h('.menu-icon.red-dot'),
'Ropsten Test Network',
providerType === 'ropsten' ? h('.check', '✓') : null,
]
),
h(
DropdownMenuItem,
{
2017-07-27 18:43:09 -07:00
key: 'kovan',
2017-07-24 17:04:13 -07:00
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
onClick: () => props.dispatch(actions.setProviderType('kovan')),
},
[
h('.menu-icon.hollow-diamond'),
'Kovan Test Network',
providerType === 'kovan' ? h('.check', '✓') : null,
]
),
h(
DropdownMenuItem,
{
2017-07-27 18:43:09 -07:00
key: 'rinkeby',
2017-07-24 17:04:13 -07:00
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
onClick: () => props.dispatch(actions.setProviderType('rinkeby')),
},
[
h('.menu-icon.golden-square'),
'Rinkeby Test Network',
providerType === 'rinkeby' ? h('.check', '✓') : null,
]
),
h(
DropdownMenuItem,
{
2017-07-27 18:43:09 -07:00
key: 'default',
2017-07-24 17:04:13 -07:00
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
2017-07-27 14:07:36 -07:00
onClick: () => props.dispatch(actions.setDefaultRpcTarget()),
2017-07-24 17:04:13 -07:00
},
[
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
'Localhost 8545',
activeNetwork === 'http://localhost:8545' ? h('.check', '✓') : null,
]
),
2016-07-28 10:53:51 -07:00
2016-11-22 16:34:02 -08:00
this.renderCustomOption(props.provider),
this.renderCommonRpc(rpcList, props.provider),
2017-07-24 17:04:13 -07:00
h(
DropdownMenuItem,
{
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
onClick: () => this.props.dispatch(actions.showConfigPage()),
},
[
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
'Custom RPC',
activeNetwork === 'custom' ? h('.check', '✓') : null,
]
),
])
}
2016-06-21 13:18:32 -07:00
App.prototype.renderDropdown = function () {
const state = this.state || {}
const isOpen = state.isMainMenuOpen
2017-07-24 17:04:13 -07:00
return h(Dropdown, {
isOpen: isOpen,
zIndex: 11,
onClickOutside: (event) => {
const { classList } = event.target
const isNotToggleElement = !classList.contains('sandwich-expando')
if (isNotToggleElement) {
this.setState({ isMainMenuOpen: false })
}
},
style: {
2016-06-30 23:50:20 -07:00
position: 'absolute',
2017-07-24 17:04:13 -07:00
right: '2px',
top: '38px',
},
2017-07-24 17:04:13 -07:00
innerStyle: {},
}, [
h(DropdownMenuItem, {
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
2017-07-24 17:04:13 -07:00
onClick: () => { this.props.dispatch(actions.showConfigPage()) },
}, 'Settings'),
2017-07-24 17:04:13 -07:00
h(DropdownMenuItem, {
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
2017-07-24 17:04:13 -07:00
onClick: () => { this.props.dispatch(actions.lockMetamask()) },
}, 'Lock'),
2017-07-24 17:04:13 -07:00
h(DropdownMenuItem, {
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
2017-07-24 17:04:13 -07:00
onClick: () => { this.props.dispatch(actions.showInfoPage()) },
}, 'Info/Help'),
])
}
2016-11-01 15:00:28 -07:00
2016-08-26 11:30:07 -07:00
App.prototype.renderBackButton = function (style, justArrow = false) {
var props = this.props
return (
h('.flex-row', {
key: 'leftArrow',
style: style,
onClick: () => props.dispatch(actions.goBackToInitView()),
}, [
h('i.fa.fa-arrow-left.cursor-pointer'),
justArrow ? null : h('div.cursor-pointer', {
style: {
marginLeft: '3px',
},
onClick: () => props.dispatch(actions.goBackToInitView()),
}, 'BACK'),
])
)
}
2016-11-01 15:00:28 -07:00
2016-06-21 13:18:32 -07:00
App.prototype.renderPrimary = function () {
log.debug('rendering primary')
var props = this.props
// notices
2017-02-20 12:59:12 -08:00
if (!props.noActiveNotices) {
2017-02-20 12:59:44 -08:00
log.debug('rendering notice screen for unread notices.')
return h(NoticeScreen, {
notice: props.lastUnreadNotice,
key: 'NoticeScreen',
onConfirm: () => props.dispatch(actions.markNoticeRead(props.lastUnreadNotice)),
})
} else if (props.lostAccounts && props.lostAccounts.length > 0) {
2017-02-20 12:59:44 -08:00
log.debug('rendering notice screen for lost accounts view.')
return h(NoticeScreen, {
notice: generateLostAccountsNotice(props.lostAccounts),
key: 'LostAccountsNotice',
onConfirm: () => props.dispatch(actions.markAccountsFound()),
})
2016-06-16 16:55:32 -07:00
}
if (props.seedWords) {
2017-02-20 12:59:44 -08:00
log.debug('rendering seed words')
return h(HDCreateVaultComplete, {key: 'HDCreateVaultComplete'})
}
// show initialize screen
if (!props.isInitialized || props.forgottenPassword) {
// show current view
2017-02-20 12:59:44 -08:00
log.debug('rendering an initialize screen')
switch (props.currentView.name) {
case 'restoreVault':
2017-02-20 12:59:44 -08:00
log.debug('rendering restore vault screen')
return h(HDRestoreVaultScreen, {key: 'HDRestoreVaultScreen'})
2016-05-11 22:21:10 -07:00
default:
2017-02-20 12:59:44 -08:00
log.debug('rendering menu screen')
return h(InitializeMenuScreen, {key: 'menuScreenInit'})
}
}
// show unlock screen
if (!props.isUnlocked) {
2016-11-01 15:00:28 -07:00
switch (props.currentView.name) {
case 'restoreVault':
2017-02-20 12:59:44 -08:00
log.debug('rendering restore vault screen')
2016-11-01 15:00:28 -07:00
return h(HDRestoreVaultScreen, {key: 'HDRestoreVaultScreen'})
2017-03-13 16:43:34 -07:00
case 'config':
log.debug('rendering config screen from unlock screen.')
return h(ConfigScreen, {key: 'config'})
2016-11-01 15:00:28 -07:00
default:
2017-02-20 12:59:44 -08:00
log.debug('rendering locked screen')
2016-11-01 15:00:28 -07:00
return h(UnlockScreen, {key: 'locked'})
}
}
// show current view
switch (props.currentView.name) {
case 'accountDetail':
2017-07-30 20:42:12 -07:00
log.debug('rendering main container')
return h(MainContainer, {key: 'account-detail'})
case 'sendTransaction':
2017-02-20 12:59:44 -08:00
log.debug('rendering send tx screen')
return h(SendTransactionScreen, {key: 'send-transaction'})
2016-10-15 10:48:12 -07:00
case 'newKeychain':
2017-02-20 12:59:44 -08:00
log.debug('rendering new keychain screen')
2016-10-15 10:48:12 -07:00
return h(NewKeyChainScreen, {key: 'new-keychain'})
case 'confTx':
2017-02-20 12:59:44 -08:00
log.debug('rendering confirm tx screen')
return h(ConfirmTxScreen, {key: 'confirm-tx'})
case 'add-token':
log.debug('rendering add-token screen from unlock screen.')
return h(AddTokenScreen, {key: 'add-token'})
case 'config':
2017-02-20 12:59:44 -08:00
log.debug('rendering config screen')
return h(ConfigScreen, {key: 'config'})
case 'import-menu':
2017-02-20 12:59:44 -08:00
log.debug('rendering import screen')
return h(Import, {key: 'import-menu'})
case 'reveal-seed-conf':
2017-02-20 12:59:44 -08:00
log.debug('rendering reveal seed confirmation screen')
return h(RevealSeedConfirmation, {key: 'reveal-seed-conf'})
case 'info':
2017-02-20 12:59:44 -08:00
log.debug('rendering info screen')
return h(InfoScreen, {key: 'info'})
case 'buyEth':
2017-02-20 12:59:44 -08:00
log.debug('rendering buy ether screen')
return h(BuyView, {key: 'buyEthView'})
2016-08-18 10:40:35 -07:00
case 'qr':
2017-02-20 12:59:44 -08:00
log.debug('rendering show qr screen')
2016-08-18 10:40:35 -07:00
return h('div', {
style: {
position: 'absolute',
height: '100%',
top: '0px',
left: '0px',
},
}, [
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', {
onClick: () => props.dispatch(actions.backToAccountDetail(props.activeAddress)),
style: {
marginLeft: '10px',
marginTop: '50px',
},
}),
h('div', {
style: {
position: 'absolute',
left: '44px',
width: '285px',
},
}, [
h(QrView, {key: 'qr'}),
]),
])
default:
2017-02-20 12:59:44 -08:00
log.debug('rendering default, account detail screen')
2017-07-30 20:42:12 -07:00
return h(MainContainer, {key: 'account-detail'})
2016-06-21 13:18:32 -07:00
}
}
2016-06-21 13:18:32 -07:00
App.prototype.toggleMetamaskActive = function () {
2016-05-13 18:07:47 -07:00
if (!this.props.isUnlocked) {
// currently inactive: redirect to password box
var passwordBox = document.querySelector('input[type=password]')
if (!passwordBox) return
passwordBox.focus()
} else {
// currently active: deactivate
this.props.dispatch(actions.lockMetamask(false))
}
}
2016-06-28 17:57:53 -07:00
2016-11-22 16:34:02 -08:00
App.prototype.renderCustomOption = function (provider) {
const { rpcTarget, type } = provider
const props = this.props
2016-11-22 16:34:02 -08:00
if (type !== 'rpc') return null
// Concatenate long URLs
let label = rpcTarget
if (rpcTarget.length > 31) {
label = label.substr(0, 34) + '...'
}
2016-06-28 18:06:10 -07:00
switch (rpcTarget) {
2016-08-29 12:36:47 -07:00
case 'http://localhost:8545':
2016-10-21 15:19:16 -07:00
return null
2016-06-28 17:57:53 -07:00
default:
2017-07-24 17:04:13 -07:00
return h(
DropdownMenuItem,
{
key: rpcTarget,
2017-07-26 15:30:41 -07:00
onClick: () => props.dispatch(actions.setRpcTarget(rpcTarget)),
2017-07-24 17:04:13 -07:00
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
},
[
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
label,
h('.check', '✓'),
]
)
2016-06-28 17:57:53 -07:00
}
}
App.prototype.getNetworkName = function () {
const { provider } = this.props
const providerName = provider.type
let name
if (providerName === 'mainnet') {
name = 'Main Ethereum Network'
} else if (providerName === 'ropsten') {
name = 'Ropsten Test Network'
} else if (providerName === 'kovan') {
name = 'Kovan Test Network'
} else if (providerName === 'rinkeby') {
name = 'Rinkeby Test Network'
} else {
name = 'Unknown Private Network'
}
return name
}
App.prototype.renderCommonRpc = function (rpcList, provider) {
const props = this.props
2017-07-26 15:30:41 -07:00
const rpcTarget = provider.rpcTarget
return rpcList.map((rpc) => {
if ((rpc === 'http://localhost:8545') || (rpc === rpcTarget)) {
return null
} else {
2017-07-26 15:30:41 -07:00
2017-07-24 17:04:13 -07:00
return h(
DropdownMenuItem,
{
2017-07-27 18:43:09 -07:00
key: `common${rpc}`,
2017-07-24 17:04:13 -07:00
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
2017-07-26 15:30:41 -07:00
onClick: () => props.dispatch(actions.setRpcTarget(rpc)),
2017-07-24 17:04:13 -07:00
},
[
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
rpc,
2017-07-26 15:30:41 -07:00
rpcTarget === rpc ? h('.check', '✓') : null,
2017-07-24 17:04:13 -07:00
]
)
}
})
}