diff options
95 files changed, 2738 insertions, 1466 deletions
diff --git a/.gitignore b/.gitignore index 1806b1932..08a544449 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,9 @@ test/background.js test/bundle.js test/test-bundle.js +#ignore css output and sourcemaps +ui/app/css/output/ + notes.txt .coveralls.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index b1921c17a..71c4f46f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Replace account scren with an account drop-down menu. +- Replace confusing buttons with an new account-specific drop-down menu. ## 3.9.5 2017-8-04 - Improved phishing detection configuration update rate diff --git a/app/fonts/DIN Next/DIN Next W01 Bold.otf b/app/fonts/DIN Next/DIN Next W01 Bold.otf Binary files differnew file mode 100644 index 000000000..2b78d1ff4 --- /dev/null +++ b/app/fonts/DIN Next/DIN Next W01 Bold.otf diff --git a/app/fonts/DIN Next/DIN Next W01 Regular.otf b/app/fonts/DIN Next/DIN Next W01 Regular.otf Binary files differnew file mode 100644 index 000000000..09f6ee297 --- /dev/null +++ b/app/fonts/DIN Next/DIN Next W01 Regular.otf diff --git a/app/fonts/DIN Next/DIN Next W10 Black.otf b/app/fonts/DIN Next/DIN Next W10 Black.otf Binary files differnew file mode 100644 index 000000000..08eb73373 --- /dev/null +++ b/app/fonts/DIN Next/DIN Next W10 Black.otf diff --git a/app/fonts/DIN Next/DIN Next W10 Italic.otf b/app/fonts/DIN Next/DIN Next W10 Italic.otf Binary files differnew file mode 100644 index 000000000..73f2b9e8c --- /dev/null +++ b/app/fonts/DIN Next/DIN Next W10 Italic.otf diff --git a/app/fonts/DIN Next/DIN Next W10 Light.otf b/app/fonts/DIN Next/DIN Next W10 Light.otf Binary files differnew file mode 100644 index 000000000..700450e49 --- /dev/null +++ b/app/fonts/DIN Next/DIN Next W10 Light.otf diff --git a/app/fonts/DIN Next/DIN Next W10 Medium.otf b/app/fonts/DIN Next/DIN Next W10 Medium.otf Binary files differnew file mode 100644 index 000000000..b73f2e43f --- /dev/null +++ b/app/fonts/DIN Next/DIN Next W10 Medium.otf diff --git a/app/fonts/DIN_OT/DINOT-2.otf b/app/fonts/DIN_OT/DINOT-2.otf Binary files differnew file mode 100644 index 000000000..4a5e13127 --- /dev/null +++ b/app/fonts/DIN_OT/DINOT-2.otf diff --git a/app/fonts/DIN_OT/DINOT-Bold 2.otf b/app/fonts/DIN_OT/DINOT-Bold 2.otf Binary files differnew file mode 100644 index 000000000..6ed5b6c3d --- /dev/null +++ b/app/fonts/DIN_OT/DINOT-Bold 2.otf diff --git a/app/fonts/DIN_OT/DINOT-BoldItalic.otf b/app/fonts/DIN_OT/DINOT-BoldItalic.otf Binary files differnew file mode 100644 index 000000000..148c90588 --- /dev/null +++ b/app/fonts/DIN_OT/DINOT-BoldItalic.otf diff --git a/app/fonts/DIN_OT/DINOT-Italic 2.otf b/app/fonts/DIN_OT/DINOT-Italic 2.otf Binary files differnew file mode 100644 index 000000000..e365e77ab --- /dev/null +++ b/app/fonts/DIN_OT/DINOT-Italic 2.otf diff --git a/app/fonts/DIN_OT/DINOT-Medium 2.otf b/app/fonts/DIN_OT/DINOT-Medium 2.otf Binary files differnew file mode 100644 index 000000000..a87a2df37 --- /dev/null +++ b/app/fonts/DIN_OT/DINOT-Medium 2.otf diff --git a/app/fonts/DIN_OT/DINOT-MediumItalic 2.otf b/app/fonts/DIN_OT/DINOT-MediumItalic 2.otf Binary files differnew file mode 100644 index 000000000..14eddfc76 --- /dev/null +++ b/app/fonts/DIN_OT/DINOT-MediumItalic 2.otf diff --git a/app/fonts/Roboto/Roboto-Black.ttf b/app/fonts/Roboto/Roboto-Black.ttf Binary files differnew file mode 100644 index 000000000..71f01ac2b --- /dev/null +++ b/app/fonts/Roboto/Roboto-Black.ttf diff --git a/app/fonts/Roboto/Roboto-BlackItalic.ttf b/app/fonts/Roboto/Roboto-BlackItalic.ttf Binary files differnew file mode 100644 index 000000000..ec309c785 --- /dev/null +++ b/app/fonts/Roboto/Roboto-BlackItalic.ttf diff --git a/app/fonts/Roboto/Roboto-Bold.ttf b/app/fonts/Roboto/Roboto-Bold.ttf Binary files differnew file mode 100644 index 000000000..aaf374d2c --- /dev/null +++ b/app/fonts/Roboto/Roboto-Bold.ttf diff --git a/app/fonts/Roboto/Roboto-BoldItalic.ttf b/app/fonts/Roboto/Roboto-BoldItalic.ttf Binary files differnew file mode 100644 index 000000000..dcd0f8007 --- /dev/null +++ b/app/fonts/Roboto/Roboto-BoldItalic.ttf diff --git a/app/fonts/Roboto/Roboto-Italic.ttf b/app/fonts/Roboto/Roboto-Italic.ttf Binary files differnew file mode 100644 index 000000000..f382c6874 --- /dev/null +++ b/app/fonts/Roboto/Roboto-Italic.ttf diff --git a/app/fonts/Roboto/Roboto-Light.ttf b/app/fonts/Roboto/Roboto-Light.ttf Binary files differnew file mode 100644 index 000000000..664e1b2f9 --- /dev/null +++ b/app/fonts/Roboto/Roboto-Light.ttf diff --git a/app/fonts/Roboto/Roboto-LightItalic.ttf b/app/fonts/Roboto/Roboto-LightItalic.ttf Binary files differnew file mode 100644 index 000000000..b8f529637 --- /dev/null +++ b/app/fonts/Roboto/Roboto-LightItalic.ttf diff --git a/app/fonts/Roboto/Roboto-Medium.ttf b/app/fonts/Roboto/Roboto-Medium.ttf Binary files differnew file mode 100644 index 000000000..aa00de0ef --- /dev/null +++ b/app/fonts/Roboto/Roboto-Medium.ttf diff --git a/app/fonts/Roboto/Roboto-MediumItalic.ttf b/app/fonts/Roboto/Roboto-MediumItalic.ttf Binary files differnew file mode 100644 index 000000000..67e25f019 --- /dev/null +++ b/app/fonts/Roboto/Roboto-MediumItalic.ttf diff --git a/app/fonts/Roboto/Roboto-Regular.ttf b/app/fonts/Roboto/Roboto-Regular.ttf Binary files differnew file mode 100644 index 000000000..3e6e2e761 --- /dev/null +++ b/app/fonts/Roboto/Roboto-Regular.ttf diff --git a/app/fonts/Roboto/Roboto-Thin.ttf b/app/fonts/Roboto/Roboto-Thin.ttf Binary files differnew file mode 100644 index 000000000..d262d1446 --- /dev/null +++ b/app/fonts/Roboto/Roboto-Thin.ttf diff --git a/app/fonts/Roboto/Roboto-ThinItalic.ttf b/app/fonts/Roboto/Roboto-ThinItalic.ttf Binary files differnew file mode 100644 index 000000000..63e9f9718 --- /dev/null +++ b/app/fonts/Roboto/Roboto-ThinItalic.ttf diff --git a/app/fonts/Roboto/RobotoCondensed-Bold.ttf b/app/fonts/Roboto/RobotoCondensed-Bold.ttf Binary files differnew file mode 100644 index 000000000..48dd63534 --- /dev/null +++ b/app/fonts/Roboto/RobotoCondensed-Bold.ttf diff --git a/app/fonts/Roboto/RobotoCondensed-BoldItalic.ttf b/app/fonts/Roboto/RobotoCondensed-BoldItalic.ttf Binary files differnew file mode 100644 index 000000000..ad728646a --- /dev/null +++ b/app/fonts/Roboto/RobotoCondensed-BoldItalic.ttf diff --git a/app/fonts/Roboto/RobotoCondensed-Italic.ttf b/app/fonts/Roboto/RobotoCondensed-Italic.ttf Binary files differnew file mode 100644 index 000000000..a232513d5 --- /dev/null +++ b/app/fonts/Roboto/RobotoCondensed-Italic.ttf diff --git a/app/fonts/Roboto/RobotoCondensed-Light.ttf b/app/fonts/Roboto/RobotoCondensed-Light.ttf Binary files differnew file mode 100644 index 000000000..a6e368d40 --- /dev/null +++ b/app/fonts/Roboto/RobotoCondensed-Light.ttf diff --git a/app/fonts/Roboto/RobotoCondensed-LightItalic.ttf b/app/fonts/Roboto/RobotoCondensed-LightItalic.ttf Binary files differnew file mode 100644 index 000000000..5b2b6ae08 --- /dev/null +++ b/app/fonts/Roboto/RobotoCondensed-LightItalic.ttf diff --git a/app/fonts/Roboto/RobotoCondensed-Regular.ttf b/app/fonts/Roboto/RobotoCondensed-Regular.ttf Binary files differnew file mode 100644 index 000000000..65bf32a19 --- /dev/null +++ b/app/fonts/Roboto/RobotoCondensed-Regular.ttf diff --git a/app/home.html b/app/home.html new file mode 100644 index 000000000..cfb4b00a0 --- /dev/null +++ b/app/home.html @@ -0,0 +1,12 @@ +<!doctype html> +<html> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1 user-scalable=no"> + <title>MetaMask Plugin</title> + </head> + <body> + <div id="app-content"></div> + <script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script> + </body> +</html> diff --git a/app/images/eth_logo.svg b/app/images/eth_logo.svg new file mode 100644 index 000000000..894bd70dd --- /dev/null +++ b/app/images/eth_logo.svg @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg width="256px" height="417px" viewBox="0 0 256 417" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid"> + <g> + <polygon fill="#343434" points="127.9611 0 125.1661 9.5 125.1661 285.168 127.9611 287.958 255.9231 212.32"/> + <polygon fill="#8C8C8C" points="127.962 0 0 212.32 127.962 287.959 127.962 154.158"/> + <polygon fill="#3C3C3B" points="127.9611 312.1866 126.3861 314.1066 126.3861 412.3056 127.9611 416.9066 255.9991 236.5866"/> + <polygon fill="#8C8C8C" points="127.962 416.9052 127.962 312.1852 0 236.5852"/> + <polygon fill="#141414" points="127.9611 287.9577 255.9211 212.3207 127.9611 154.1587"/> + <polygon fill="#393939" points="0.0009 212.3208 127.9609 287.9578 127.9609 154.1588"/> + </g> +</svg>
\ No newline at end of file diff --git a/app/notification.html b/app/notification.html index cc485da7f..be38f4aa3 100644 --- a/app/notification.html +++ b/app/notification.html @@ -9,7 +9,7 @@ } </style> </head> - <body> + <body style="width:350px; height:500px;"> <div id="app-content"></div> <script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script> </body> diff --git a/app/popup.html b/app/popup.html index 6d85a9811..fddf01841 100644 --- a/app/popup.html +++ b/app/popup.html @@ -2,10 +2,11 @@ <html> <head> <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1 user-scalable=no"> <title>MetaMask Plugin</title> </head> - <body> + <body style="width:350px; height:500px;"> <div id="app-content"></div> <script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script> </body> -</html>
\ No newline at end of file +</html> diff --git a/app/scripts/lib/environment-type.js b/app/scripts/lib/environment-type.js new file mode 100644 index 000000000..7966926eb --- /dev/null +++ b/app/scripts/lib/environment-type.js @@ -0,0 +1,10 @@ +module.exports = function environmentType () { + const url = window.location.href + if (url.match(/popup.html$/)) { + return 'popup' + } else if (url.match(/home.html$/)) { + return 'responsive' + } else { + return 'notification' + } +} diff --git a/app/scripts/lib/is-popup-or-notification.js b/app/scripts/lib/is-popup-or-notification.js index 693fa8751..73a812d5f 100644 --- a/app/scripts/lib/is-popup-or-notification.js +++ b/app/scripts/lib/is-popup-or-notification.js @@ -1,6 +1,6 @@ module.exports = function isPopupOrNotification () { const url = window.location.href - if (url.match(/popup.html$/)) { + if (url.match(/popup.html$/) || url.match(/home.html$/)) { return 'popup' } else { return 'notification' diff --git a/gulpfile.js b/gulpfile.js index cc723704a..f351c2e2f 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -19,6 +19,8 @@ var manifest = require('./app/manifest.json') var gulpif = require('gulp-if') var replace = require('gulp-replace') var mkdirp = require('mkdirp') +var sass = require('gulp-sass') +var autoprefixer = require('gulp-autoprefixer') var disableDebugTools = gutil.env.disableDebugTools var debug = gutil.env.debug @@ -159,13 +161,7 @@ gulp.task('lint', function () { // To have the process exit with an error code (1) on // lint error, return the stream and pipe to failAfterError last. .pipe(eslint.failAfterError()) -}); - -/* -gulp.task('default', ['lint'], function () { - // This will only run if the lint task is successful... -}); -*/ +}) // build js @@ -176,6 +172,20 @@ const jsFiles = [ 'popup', ] +// scss compilation and autoprefixing tasks + +gulp.task('build:scss', function () { + return gulp.src('ui/app/css/index.scss') + .pipe(sourcemaps.init()) + .pipe(sass().on('error', sass.logError)) + .pipe(sourcemaps.write()) + .pipe(autoprefixer()) + .pipe(gulp.dest('ui/app/css/output')) +}) +gulp.task('watch:scss', function(){ + gulp.watch(['ui/app/css/**/*.scss'], gulp.series(['build:scss'])) +}) + // bundle tasks var jsDevStrings = jsFiles.map(jsFile => `dev:js:${jsFile}`) @@ -214,9 +224,9 @@ gulp.task('zip', gulp.parallel('zip:chrome', 'zip:firefox', 'zip:edge', 'zip:ope // high level tasks -gulp.task('dev', gulp.series('dev:js', 'copy', gulp.parallel('copy:watch', 'dev:reload'))) +gulp.task('dev', gulp.series('build:scss', 'dev:js', 'copy', gulp.parallel('watch:scss', 'copy:watch', 'dev:reload'))) -gulp.task('build', gulp.series('clean', gulp.parallel('build:js', 'copy'))) +gulp.task('build', gulp.series('clean', 'build:scss', gulp.parallel('build:js', 'copy'))) gulp.task('dist', gulp.series('build', 'zip')) // task generators @@ -244,7 +254,7 @@ function zipTask(target) { return () => { return gulp.src(`dist/${target}/**`) .pipe(zip(`metamask-${target}-${manifest.version}.zip`)) - .pipe(gulp.dest('builds')); + .pipe(gulp.dest('builds')) } } diff --git a/package.json b/package.json index 367754a00..bf3f316dd 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,9 @@ "extension-link-enabler": "^1.0.0", "extensionizer": "^1.0.0", "fast-levenshtein": "^2.0.6", + "gulp-autoprefixer": "^4.0.0", "gulp-eslint": "^2.0.0", + "gulp-sass": "^3.1.0", "hat": "0.0.3", "idb-global": "^1.0.0", "identicon.js": "^1.2.1", @@ -91,7 +93,7 @@ "inject-css": "^0.1.1", "jazzicon": "^1.2.0", "loglevel": "^1.4.1", - "menu-droppo": "^1.1.0", + "menu-droppo": "2.0.1", "metamask-logo": "^2.1.2", "mississippi": "^1.2.0", "mkdirp": "^0.5.1", @@ -109,7 +111,8 @@ "pumpify": "^1.3.4", "qrcode-npm": "0.0.3", "react": "^15.0.2", - "react-addons-css-transition-group": "^15.0.2", + "react-addons-css-transition-group": "^15.6.0", + "react-burger-menu": "^2.1.4", "react-dom": "^15.5.4", "react-hyperscript": "^2.2.2", "react-markdown": "^2.3.0", @@ -117,6 +120,7 @@ "react-select": "^1.0.0-rc.2", "react-simple-file-input": "^1.0.0", "react-tooltip-component": "^0.3.0", + "react-transition-group": "^2.2.0", "readable-stream": "^2.1.2", "redux": "^3.0.5", "redux-logger": "^2.10.2", diff --git a/test/unit/responsive/components/dropdown-test.js b/test/unit/responsive/components/dropdown-test.js new file mode 100644 index 000000000..3ad2c390e --- /dev/null +++ b/test/unit/responsive/components/dropdown-test.js @@ -0,0 +1,115 @@ +var assert = require('assert'); + +const additions = require('react-testutils-additions'); +const h = require('react-hyperscript'); +const ReactTestUtils = require('react-addons-test-utils'); +const sinon = require('sinon'); +const path = require('path'); +const Dropdown = require(path.join(__dirname, '..', '..', '..', '..', 'ui', 'app', 'components', 'dropdown.js')).Dropdown; +const DropdownMenuItem = require(path.join(__dirname, '..', '..', '..', '..', 'ui', 'app', 'components', 'dropdown.js')).DropdownMenuItem; + +describe('Dropdown components', function () { + let onClickOutside; + let closeMenu; + let onClick; + + let dropdownComponentProps; + const renderer = ReactTestUtils.createRenderer() + beforeEach(function () { + onClickOutside = sinon.spy(); + closeMenu = sinon.spy(); + onClick = sinon.spy(); + + dropdownComponentProps = { + isOpen: true, + zIndex: 11, + onClickOutside, + style: { + position: 'absolute', + right: 0, + top: '36px', + }, + innerStyle: {}, + } + }); + + it('can render two items', function () { + const dropdownComponent = h( + Dropdown, + dropdownComponentProps, + [ + h('style', ` + .drop-menu-item:hover { background:rgb(235, 235, 235); } + .drop-menu-item i { margin: 11px; } + `), + h(DropdownMenuItem, { + closeMenu, + onClick, + }, 'Item 1'), + h(DropdownMenuItem, { + closeMenu, + onClick, + }, 'Item 2'), + ] + ) + + const component = additions.renderIntoDocument(dropdownComponent); + renderer.render(dropdownComponent); + const items = additions.find(component, 'li'); + assert.equal(items.length, 2); + }); + + it('closes when item clicked', function() { + const dropdownComponent = h( + Dropdown, + dropdownComponentProps, + [ + h('style', ` + .drop-menu-item:hover { background:rgb(235, 235, 235); } + .drop-menu-item i { margin: 11px; } + `), + h(DropdownMenuItem, { + closeMenu, + onClick, + }, 'Item 1'), + h(DropdownMenuItem, { + closeMenu, + onClick, + }, 'Item 2'), + ] + ) + const component = additions.renderIntoDocument(dropdownComponent); + renderer.render(dropdownComponent); + const items = additions.find(component, 'li'); + const node = items[0]; + ReactTestUtils.Simulate.click(node); + assert.equal(closeMenu.calledOnce, true); + }); + + it('invokes click handler when item clicked', function() { + const dropdownComponent = h( + Dropdown, + dropdownComponentProps, + [ + h('style', ` + .drop-menu-item:hover { background:rgb(235, 235, 235); } + .drop-menu-item i { margin: 11px; } + `), + h(DropdownMenuItem, { + closeMenu, + onClick, + }, 'Item 1'), + h(DropdownMenuItem, { + closeMenu, + onClick, + }, 'Item 2'), + ] + ) + const component = additions.renderIntoDocument(dropdownComponent); + renderer.render(dropdownComponent); + const items = additions.find(component, 'li'); + const node = items[0]; + ReactTestUtils.Simulate.click(node); + assert.equal(onClick.calledOnce, true); + }); +}); diff --git a/ui/app/account-and-transaction-details.js b/ui/app/account-and-transaction-details.js new file mode 100644 index 000000000..03f2d9db5 --- /dev/null +++ b/ui/app/account-and-transaction-details.js @@ -0,0 +1,38 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits +// Main Views +const TxView = require('./components/tx-view') +const WalletView = require('./components/wallet-view') + +module.exports = AccountAndTransactionDetails + +inherits(AccountAndTransactionDetails, Component) +function AccountAndTransactionDetails () { + Component.call(this) +} + +AccountAndTransactionDetails.prototype.render = function () { + return h('div', { + style: { + display: 'flex', + flex: '1 0 auto', + }, + }, [ + // wallet + h(WalletView, { + style: { + }, + responsiveDisplayClassname: '.lap-visible', + }, [ + ]), + + // transaction + h(TxView, { + style: { + } + }, [ + ]), + ]) +} + diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index bed05a7fb..24561c32e 100644 --- a/ui/app/account-detail.js +++ b/ui/app/account-detail.js @@ -3,21 +3,17 @@ const extend = require('xtend') const Component = require('react').Component const h = require('react-hyperscript') const connect = require('react-redux').connect -const CopyButton = require('./components/copyButton') -const AccountInfoLink = require('./components/account-info-link') const actions = require('./actions') -const ReactCSSTransitionGroup = require('react-addons-css-transition-group') const valuesFor = require('./util').valuesFor - const Identicon = require('./components/identicon') const EthBalance = require('./components/eth-balance') const TransactionList = require('./components/transaction-list') const ExportAccountView = require('./components/account-export') const ethUtil = require('ethereumjs-util') const EditableLabel = require('./components/editable-label') -const Tooltip = require('./components/tooltip') const TabBar = require('./components/tab-bar') const TokenList = require('./components/token-list') +const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns module.exports = connect(mapStateToProps)(AccountDetailScreen) @@ -54,12 +50,13 @@ AccountDetailScreen.prototype.render = function () { return ( - h('.account-detail-section', [ + h('.account-detail-section.full-flex-height', [ - // identicon, label, balance, etc + // identicon, label, balance, etc h('.account-data-subsection', { style: { margin: '0 20px', + flex: '1 0 auto', }, }, [ @@ -84,6 +81,7 @@ AccountDetailScreen.prototype.render = function () { style: { lineHeight: '10px', marginLeft: '15px', + width: '100%', }, }, [ h(EditableLabel, { @@ -98,7 +96,42 @@ AccountDetailScreen.prototype.render = function () { // What is shown when not editing + edit text: h('label.editing-label', [h('.edit-text', 'edit')]), - h('h2.font-medium.color-forest', {name: 'edit'}, identity && identity.name), + h( + 'div', + { + style: { + display: 'flex', + justifyContent: 'flex-start', + alignItems: 'center', + }, + }, + [ + h( + 'h2.font-medium.color-forest', + { + name: 'edit', + style: { + }, + }, + [ + identity && identity.name, + ] + ), + h( + AccountDropdowns, + { + style: { + marginRight: '8px', + marginLeft: 'auto', + cursor: 'pointer', + }, + selected, + network, + identities: props.identities, + }, + ), + ] + ), ]), h('.flex-row', { style: { @@ -124,56 +157,6 @@ AccountDetailScreen.prototype.render = function () { color: '#AEAEAE', }, }, checksumAddress), - - // copy and export - - h('.flex-row', { - style: { - justifyContent: 'flex-end', - }, - }, [ - - h(AccountInfoLink, { selected, network }), - - h(CopyButton, { - value: checksumAddress, - }), - - h(Tooltip, { - title: 'QR Code', - }, [ - h('i.fa.fa-qrcode.pointer.pop-hover', { - onClick: () => props.dispatch(actions.showQrView(selected, identity ? identity.name : '')), - style: { - fontSize: '18px', - position: 'relative', - color: 'rgb(247, 134, 28)', - top: '5px', - marginLeft: '3px', - marginRight: '3px', - }, - }), - ]), - - h(Tooltip, { - title: 'Export Private Key', - }, [ - h('div', { - style: { - display: 'flex', - alignItems: 'center', - }, - }, [ - h('img.cursor-pointer.color-orange', { - src: 'images/key-32.png', - onClick: () => this.requestAccountExport(selected), - style: { - height: '19px', - }, - }), - ]), - ]), - ]), ]), // account ballence @@ -197,14 +180,11 @@ AccountDetailScreen.prototype.render = function () { }, }), + h('.flex-grow'), + h('button', { onClick: () => props.dispatch(actions.buyEthView(selected)), - style: { - marginBottom: '20px', - marginRight: '8px', - position: 'absolute', - left: '219px', - }, + style: { marginRight: '10px' }, }, 'BUY'), h('button', { @@ -219,14 +199,7 @@ AccountDetailScreen.prototype.render = function () { ]), // subview (tx history, pk export confirm, buy eth warning) - h(ReactCSSTransitionGroup, { - className: 'css-transition-group', - transitionName: 'main', - transitionEnterTimeout: 300, - transitionLeaveTimeout: 300, - }, [ - this.subview(), - ]), + this.subview(), ]) ) @@ -254,7 +227,7 @@ AccountDetailScreen.prototype.subview = function () { AccountDetailScreen.prototype.tabSections = function () { const { currentAccountTab } = this.props - return h('section.tabSection', [ + return h('section.tabSection.full-flex-height.grow-tenx', [ h(TabBar, { tabs: [ @@ -305,7 +278,3 @@ AccountDetailScreen.prototype.transactionList = function () { }, }) } - -AccountDetailScreen.prototype.requestAccountExport = function () { - this.props.dispatch(actions.requestExportAccount()) -} diff --git a/ui/app/accounts/account-list-item.js b/ui/app/accounts/account-list-item.js deleted file mode 100644 index 10a0b6cc7..000000000 --- a/ui/app/accounts/account-list-item.js +++ /dev/null @@ -1,91 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits -const ethUtil = require('ethereumjs-util') - -const EthBalance = require('../components/eth-balance') -const CopyButton = require('../components/copyButton') -const Identicon = require('../components/identicon') - -module.exports = AccountListItem - -inherits(AccountListItem, Component) -function AccountListItem () { - Component.call(this) -} - -AccountListItem.prototype.render = function () { - const { identity, selectedAddress, accounts, onShowDetail, - conversionRate, currentCurrency } = this.props - - const checksumAddress = identity && identity.address && ethUtil.toChecksumAddress(identity.address) - const isSelected = selectedAddress === identity.address - const account = accounts[identity.address] - const selectedClass = isSelected ? '.selected' : '' - - return ( - h(`.accounts-list-option.flex-row.flex-space-between.pointer.hover-white${selectedClass}`, { - key: `account-panel-${identity.address}`, - onClick: (event) => onShowDetail(identity.address, event), - }, [ - - h('.identicon-wrapper.flex-column.flex-center.select-none', [ - this.pendingOrNot(), - this.indicateIfLoose(), - h(Identicon, { - address: identity.address, - imageify: true, - }), - ]), - - // account address, balance - h('.identity-data.flex-column.flex-justify-center.flex-grow.select-none', { - style: { - width: '200px', - }, - }, [ - h('span', identity.name), - h('span.font-small', { - style: { - overflow: 'hidden', - textOverflow: 'ellipsis', - }, - }, checksumAddress), - h(EthBalance, { - value: account && account.balance, - currentCurrency, - conversionRate, - style: { - lineHeight: '7px', - marginTop: '10px', - }, - }), - ]), - - // copy button - h('.identity-copy.flex-column', { - style: { - margin: '0 20px', - }, - }, [ - h(CopyButton, { - value: checksumAddress, - }), - ]), - ]) - ) -} - -AccountListItem.prototype.indicateIfLoose = function () { - try { // Sometimes keyrings aren't loaded yet: - const type = this.props.keyring.type - const isLoose = type !== 'HD Key Tree' - return isLoose ? h('.keyring-label', 'LOOSE') : null - } catch (e) { return } -} - -AccountListItem.prototype.pendingOrNot = function () { - const pending = this.props.pending - if (pending.length === 0) return null - return h('.pending-dot', pending.length) -} diff --git a/ui/app/accounts/index.js b/ui/app/accounts/index.js deleted file mode 100644 index ac2615cd7..000000000 --- a/ui/app/accounts/index.js +++ /dev/null @@ -1,164 +0,0 @@ -const inherits = require('util').inherits -const Component = require('react').Component -const h = require('react-hyperscript') -const connect = require('react-redux').connect -const actions = require('../actions') -const valuesFor = require('../util').valuesFor -const findDOMNode = require('react-dom').findDOMNode -const AccountListItem = require('./account-list-item') - -module.exports = connect(mapStateToProps)(AccountsScreen) - -function mapStateToProps (state) { - const pendingTxs = valuesFor(state.metamask.unapprovedTxs) - .filter(txMeta => txMeta.metamaskNetworkId === state.metamask.network) - const pendingMsgs = valuesFor(state.metamask.unapprovedMsgs) - const pending = pendingTxs.concat(pendingMsgs) - - return { - accounts: state.metamask.accounts, - identities: state.metamask.identities, - unapprovedTxs: state.metamask.unapprovedTxs, - selectedAddress: state.metamask.selectedAddress, - scrollToBottom: state.appState.scrollToBottom, - pending, - keyrings: state.metamask.keyrings, - conversionRate: state.metamask.conversionRate, - currentCurrency: state.metamask.currentCurrency, - } -} - -inherits(AccountsScreen, Component) -function AccountsScreen () { - Component.call(this) -} - -AccountsScreen.prototype.render = function () { - const props = this.props - const { keyrings, conversionRate, currentCurrency } = props - const identityList = valuesFor(props.identities) - const unapprovedTxList = valuesFor(props.unapprovedTxs) - - return ( - - h('.accounts-section.flex-grow', [ - - // subtitle and nav - h('.section-title.flex-center', [ - h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', { - onClick: this.goHome.bind(this), - }), - h('h2.page-subtitle', 'Select Account'), - ]), - - h('hr.horizontal-line'), - - // identity selection - h('section.identity-section', { - style: { - height: '418px', - overflowY: 'auto', - overflowX: 'hidden', - }, - }, - [ - identityList.map((identity) => { - const pending = this.props.pending.filter((txOrMsg) => { - if ('txParams' in txOrMsg) { - return txOrMsg.txParams.from === identity.address - } else if ('msgParams' in txOrMsg) { - return txOrMsg.msgParams.from === identity.address - } else { - return false - } - }) - - const simpleAddress = identity.address.substring(2).toLowerCase() - const keyring = keyrings.find((kr) => { - return kr.accounts.includes(simpleAddress) || - kr.accounts.includes(identity.address) - }) - - return h(AccountListItem, { - key: `acct-panel-${identity.address}`, - identity, - selectedAddress: this.props.selectedAddress, - conversionRate, - currentCurrency, - accounts: this.props.accounts, - onShowDetail: this.onShowDetail.bind(this), - pending, - keyring, - }) - }), - - h('hr.horizontal-line'), - h('div.footer.hover-white.pointer', { - key: 'reveal-account-bar', - onClick: () => { - this.addNewAccount() - }, - style: { - display: 'flex', - height: '40px', - padding: '10px', - justifyContent: 'center', - alignItems: 'center', - }, - }, [ - h('i.fa.fa-plus.fa-lg', {key: ''}), - ]), - h('hr.horizontal-line'), - ]), - - unapprovedTxList.length ? ( - - h('.unconftx-link.flex-row.flex-center', { - onClick: this.navigateToConfTx.bind(this), - }, [ - h('span', 'Unconfirmed Txs'), - h('i.fa.fa-arrow-right.fa-lg'), - ]) - - ) : ( - null - ), - ]) - ) -} - -// If a new account was revealed, scroll to the bottom -AccountsScreen.prototype.componentDidUpdate = function () { - const scrollToBottom = this.props.scrollToBottom - - if (scrollToBottom) { - var container = findDOMNode(this) - var scrollable = container.querySelector('.identity-section') - scrollable.scrollTop = scrollable.scrollHeight - } -} - -AccountsScreen.prototype.navigateToConfTx = function () { - event.stopPropagation() - this.props.dispatch(actions.showConfTxPage()) -} - -AccountsScreen.prototype.onShowDetail = function (address, event) { - event.stopPropagation() - this.props.dispatch(actions.showAccountDetail(address)) -} - -AccountsScreen.prototype.addNewAccount = function () { - this.props.dispatch(actions.addNewAccount(0)) -} - -/* An optional view proposed in this design: - * https://consensys.quip.com/zZVrAysM5znY -AccountsScreen.prototype.addNewAccount = function () { - this.props.dispatch(actions.navigateToNewAccountScreen()) -} -*/ - -AccountsScreen.prototype.goHome = function () { - this.props.dispatch(actions.goHome()) -} diff --git a/ui/app/actions.js b/ui/app/actions.js index 8ff8bbbdd..13a767343 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -5,6 +5,11 @@ var actions = { GO_HOME: 'GO_HOME', goHome: goHome, + // sidebar state + SIDEBAR_OPEN: 'UI_SIDEBAR_OPEN', + SIDEBAR_CLOSE: 'UI_SIDEBAR_CLOSE', + showSidebar: showSidebar, + hideSidebar: hideSidebar, // menu state getNetworkStatus: 'getNetworkStatus', // transition state @@ -709,7 +714,7 @@ function markAccountsFound () { // // default rpc target refers to localhost:8545 in this instance. -function setDefaultRpcTarget (rpcList) { +function setDefaultRpcTarget () { log.debug(`background.setDefaultRpcTarget`) return (dispatch) => { background.setDefaultRpc((err, result) => { @@ -722,7 +727,7 @@ function setDefaultRpcTarget (rpcList) { } function setRpcTarget (newRpc) { - log.debug(`background.setRpcTarget`) + log.debug(`background.setRpcTarget: ${newRpc}`) return (dispatch) => { background.setCustomRpc(newRpc, (err, result) => { if (err) { @@ -763,6 +768,19 @@ function useEtherscanProvider () { } } +function showSidebar () { + return { + type: actions.SIDEBAR_OPEN, + } +} + +function hideSidebar () { + return { + type: actions.SIDEBAR_CLOSE, + } +} + + function showLoadingIndication (message) { return { type: actions.SHOW_LOADING, diff --git a/ui/app/add-token.js b/ui/app/add-token.js index 15ef7a852..5c6dea4a0 100644 --- a/ui/app/add-token.js +++ b/ui/app/add-token.js @@ -212,7 +212,6 @@ AddTokenScreen.prototype.attemptToAutoFillTokenParams = async function (address) const [ symbol, decimals ] = results if (symbol && decimals) { - console.log('SETTING SYMBOL AND DECIMALS', { symbol, decimals }) this.setState({ symbol: symbol[0], decimals: decimals[0].toString() }) } } diff --git a/ui/app/app.js b/ui/app/app.js index 1a63002e1..339e13fd7 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -3,20 +3,23 @@ const Component = require('react').Component const connect = require('react-redux').connect const h = require('react-hyperscript') const actions = require('./actions') -const ReactCSSTransitionGroup = require('react-addons-css-transition-group') // init const InitializeMenuScreen = require('./first-time/init-menu') const NewKeyChainScreen = require('./new-keychain') // unlock const UnlockScreen = require('./unlock') // accounts -const AccountsScreen = require('./accounts') -const AccountDetailScreen = require('./account-detail') +const MainContainer = require('./main-container') const SendTransactionScreen = require('./send') const ConfirmTxScreen = require('./conf-tx') // notice const NoticeScreen = require('./components/notice') 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') @@ -24,17 +27,17 @@ const Import = require('./accounts/import') const InfoScreen = require('./info') const Loading = require('./components/loading') const SandwichExpando = require('sandwich-expando') -const MenuDroppo = require('menu-droppo') -const DropMenuItem = require('./components/drop-menu-item') +const Dropdown = require('./components/dropdown').Dropdown +const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem const NetworkIndicator = require('./components/network') -const Tooltip = require('./components/tooltip') const BuyView = require('./components/buy-button-subview') const QrView = require('./components/qr-code') const HDCreateVaultComplete = require('./keychains/hd/create-vault-complete') const HDRestoreVaultScreen = require('./keychains/hd/restore-vault') const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation') +const ReactCSSTransitionGroup = require('react-addons-css-transition-group') -module.exports = connect(mapStateToProps)(App) +module.exports = connect(mapStateToProps, mapDispatchToProps)(App) inherits(App, Component) function App () { Component.call(this) } @@ -42,6 +45,7 @@ function App () { Component.call(this) } function mapStateToProps (state) { return { // state from plugin + sidebarOpen: state.appState.sidebarOpen, isLoading: state.appState.isLoading, loadingMessage: state.appState.loadingMessage, noActiveNotices: state.metamask.noActiveNotices, @@ -63,55 +67,104 @@ function mapStateToProps (state) { } } +function mapDispatchToProps (dispatch) { + return { + hideSidebar: () => {dispatch(actions.hideSidebar())}, + } +} + App.prototype.render = function () { var props = this.props - const { isLoading, loadingMessage, transForward, network } = props + const { isLoading, loadingMessage, transForward, network, sidebarOpen } = props const isLoadingNetwork = network === 'loading' && props.currentView.name !== 'config' const loadMessage = loadingMessage || isLoadingNetwork ? `Connecting to ${this.getNetworkName()}` : null - log.debug('Main ui render function') return ( - h('.flex-column.flex-grow.full-height', { + h('.flex-column.full-height', { style: { // Windows was showing a vertical scroll bar: - overflow: 'hidden', + overflowX: 'hidden', + // TODO: check with dev who committed L75, see if this still happens, and whether auto is enough + // overflowY: 'auto', position: 'relative', + alignItems: 'center', }, }, [ // app bar this.renderAppBar(), + + // sidebar + this.renderSidebar(), + + // network dropdown this.renderNetworkDropdown(), - this.renderDropdown(), + // this.renderDropdown(), h(Loading, { isLoading: isLoading || isLoadingNetwork, loadingMessage: loadMessage, }), - // panel content - h('.app-primary.flex-grow' + (transForward ? '.from-right' : '.from-left'), { - style: { - height: '380px', - width: '360px', - }, - }, [ - h(ReactCSSTransitionGroup, { - className: 'css-transition-group', - transitionName: 'main', - transitionEnterTimeout: 300, - transitionLeaveTimeout: 300, - }, [ - this.renderPrimary(), - ]), - ]), + // content + this.renderPrimary(), ]) ) } +App.prototype.renderSidebar = function() { + // if (!this.props.sidebarOpen) { + // return null; + // } + + return h('div', { + }, [ + h('style', ` + .sidebar-enter { + transition: transform 300ms ease-in-out; + transform: translateX(-100%); + } + .sidebar-enter.sidebar-enter-active { + transition: transform 300ms ease-in-out; + transform: translateX(0%); + } + .sidebar-leave { + transition: transform 200ms ease-out; + transform: translateX(0%); + } + .sidebar-leave.sidebar-leave-active { + transition: transform 200ms ease-out; + transform: translateX(-100%); + } + `), + + h(ReactCSSTransitionGroup, { + transitionName: 'sidebar', + transitionEnterTimeout: 300, + transitionLeaveTimeout: 200, + }, [ + // content + this.props.sidebarOpen ? h(WalletView, { + responsiveDisplayClassname: '.sidebar', + style: {}, + }) : undefined, + + ]), + + // overlay + // TODO: add onClick for overlay to close sidebar + this.props.sidebarOpen ? h('div.sidebar-overlay', { + style: {}, + onClick: () => { + this.props.hideSidebar() + }, + }, []) : undefined, + ]) +} + App.prototype.renderAppBar = function () { if (window.METAMASK_UI_TYPE === 'notification') { return null @@ -123,27 +176,17 @@ App.prototype.renderAppBar = function () { return ( - h('div', [ + h('.full-width', { + style: {} + }, [ h('.app-header.flex-row.flex-space-between', { - style: { - alignItems: 'center', - visibility: props.isUnlocked ? 'visible' : 'none', - background: props.isUnlocked ? 'white' : 'none', - height: '36px', - position: 'relative', - zIndex: 12, - }, + style: {}, }, [ - h('div.left-menu-section', { - style: { - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - }, + h('div.left-menu-wrapper', { + style: {}, }, [ - // mini logo h('img', { height: 24, @@ -151,6 +194,20 @@ App.prototype.renderAppBar = function () { src: '/images/icon-128.png', }), + // metamask name + h('h1', { + style: { + position: 'relative', + left: '9px', + }, + }, 'MetaMask'), + + ]), + + h('div.network-component-wrapper', { + style: {}, + }, [ + // Network Indicator h(NetworkIndicator, { network: this.props.network, provider: this.props.provider, @@ -160,138 +217,132 @@ App.prototype.renderAppBar = function () { this.setState({ isNetworkMenuOpen: !isNetworkMenuOpen }) }, }), - ]), - - // metamask name - props.isUnlocked && h('h1', { - style: { - position: 'relative', - left: '9px', - }, - }, 'MetaMask'), - - props.isUnlocked && h('div', { - style: { - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - }, - }, [ - // small accounts nav - props.isUnlocked && h(Tooltip, { title: 'Switch Accounts' }, [ - h('img.cursor-pointer.color-orange', { - src: 'images/switch_acc.svg', - style: { - width: '23.5px', - marginRight: '8px', - }, - onClick: (event) => { - event.stopPropagation() - this.props.dispatch(actions.showAccountsPage()) - }, - }), - ]), - - // hamburger - props.isUnlocked && h(SandwichExpando, { - width: 16, - barHeight: 2, - padding: 0, - isOpen: state.isMainMenuOpen, - color: 'rgb(247,146,30)', - onClick: (event) => { - event.preventDefault() - event.stopPropagation() - this.setState({ isMainMenuOpen: !state.isMainMenuOpen }) - }, - }), ]), ]), + ]) ) } App.prototype.renderNetworkDropdown = function () { const props = this.props + const { provider: { type: providerType, rpcTarget: activeNetwork } } = props const rpcList = props.frequentRpcList const state = this.state || {} const isOpen = state.isNetworkMenuOpen - return h(MenuDroppo, { + return h(Dropdown, { isOpen, onClickOutside: (event) => { - this.setState({ isNetworkMenuOpen: !isOpen }) + 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: { position: 'absolute', - left: 0, - top: '36px', + right: '2px', + top: '38px', }, innerStyle: { - background: 'white', - boxShadow: '1px 1px 2px rgba(0,0,0,0.1)', + padding: '2px 16px 2px 0px', }, - }, [ // DROP MENU ITEMS - h('style', ` - .drop-menu-item:hover { background:rgb(235, 235, 235); } - .drop-menu-item i { margin: 11px; } - `), - - h(DropMenuItem, { - label: 'Main Ethereum Network', - closeMenu: () => this.setState({ isNetworkMenuOpen: false }), - action: () => props.dispatch(actions.setProviderType('mainnet')), - icon: h('.menu-icon.diamond'), - activeNetworkRender: props.network, - provider: props.provider, - }), - - h(DropMenuItem, { - label: 'Ropsten Test Network', - closeMenu: () => this.setState({ isNetworkMenuOpen: false }), - action: () => props.dispatch(actions.setProviderType('ropsten')), - icon: h('.menu-icon.red-dot'), - activeNetworkRender: props.network, - provider: props.provider, - }), - - h(DropMenuItem, { - label: 'Kovan Test Network', - closeMenu: () => this.setState({ isNetworkMenuOpen: false}), - action: () => props.dispatch(actions.setProviderType('kovan')), - icon: h('.menu-icon.hollow-diamond'), - activeNetworkRender: props.network, - provider: props.provider, - }), - - h(DropMenuItem, { - label: 'Rinkeby Test Network', - closeMenu: () => this.setState({ isNetworkMenuOpen: false}), - action: () => props.dispatch(actions.setProviderType('rinkeby')), - icon: h('.menu-icon.golden-square'), - activeNetworkRender: props.network, - provider: props.provider, - }), - - h(DropMenuItem, { - label: 'Localhost 8545', - closeMenu: () => this.setState({ isNetworkMenuOpen: false }), - action: () => props.dispatch(actions.setDefaultRpcTarget(rpcList)), - icon: h('i.fa.fa-question-circle.fa-lg'), - activeNetworkRender: props.provider.rpcTarget, - }), + }, [ + + h( + DropdownMenuItem, + { + key: 'main', + 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, + { + key: 'ropsten', + 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, + { + key: 'kovan', + 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, + { + key: 'rinkeby', + 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, + { + key: 'default', + closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }), + onClick: () => props.dispatch(actions.setDefaultRpcTarget()), + }, + [ + h('i.fa.fa-question-circle.fa-lg.menu-icon'), + 'Localhost 8545', + activeNetwork === 'http://localhost:8545' ? h('.check', '✓') : null, + ] + ), this.renderCustomOption(props.provider), this.renderCommonRpc(rpcList, props.provider), - h(DropMenuItem, { - label: 'Custom RPC', - closeMenu: () => this.setState({ isNetworkMenuOpen: false }), - action: () => this.props.dispatch(actions.showConfigPage()), - icon: h('i.fa.fa-question-circle.fa-lg'), - }), + 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, + ] + ), ]) } @@ -300,54 +351,37 @@ App.prototype.renderDropdown = function () { const state = this.state || {} const isOpen = state.isMainMenuOpen - return h(MenuDroppo, { + return h(Dropdown, { isOpen: isOpen, zIndex: 11, onClickOutside: (event) => { - this.setState({ isMainMenuOpen: !isOpen }) + const { classList } = event.target + const isNotToggleElement = !classList.contains('sandwich-expando') + if (isNotToggleElement) { + this.setState({ isMainMenuOpen: false }) + } }, style: { position: 'absolute', - right: 0, - top: '36px', + right: '2px', + top: '38px', }, - 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; } - `), - - h(DropMenuItem, { - label: 'Settings', - closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), - action: () => this.props.dispatch(actions.showConfigPage()), - icon: h('i.fa.fa-gear.fa-lg'), - }), - - h(DropMenuItem, { - label: 'Import Account', + innerStyle: {}, + }, [ + h(DropdownMenuItem, { closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), - action: () => this.props.dispatch(actions.showImportPage()), - icon: h('i.fa.fa-arrow-circle-o-up.fa-lg'), - }), + onClick: () => { this.props.dispatch(actions.showConfigPage()) }, + }, 'Settings'), - h(DropMenuItem, { - label: 'Lock', + h(DropdownMenuItem, { closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), - action: () => this.props.dispatch(actions.lockMetamask()), - icon: h('i.fa.fa-lock.fa-lg'), - }), + onClick: () => { this.props.dispatch(actions.lockMetamask()) }, + }, 'Lock'), - h(DropMenuItem, { - label: 'Info/Help', + h(DropdownMenuItem, { closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), - action: () => this.props.dispatch(actions.showInfoPage()), - icon: h('i.fa.fa-question.fa-lg'), - }), + onClick: () => { this.props.dispatch(actions.showInfoPage()) }, + }, 'Info/Help'), ]) } @@ -414,32 +448,22 @@ App.prototype.renderPrimary = function () { // show unlock screen if (!props.isUnlocked) { - switch (props.currentView.name) { - - case 'restoreVault': - log.debug('rendering restore vault screen') - return h(HDRestoreVaultScreen, {key: 'HDRestoreVaultScreen'}) - - case 'config': - log.debug('rendering config screen from unlock screen.') - return h(ConfigScreen, {key: 'config'}) - - default: - log.debug('rendering locked screen') - return h(UnlockScreen, {key: 'locked'}) - } + return h(MainContainer, { + currentViewName: props.currentView.name, + isUnlocked: props.isUnlocked, + }) } + // Note for @Zanibar - isUnlocked stays true. + // console.log("props.isUnlocked", props.isUnlocked) + // console.log("props.isUnlocked", props.currentView.name) + // show current view switch (props.currentView.name) { - case 'accounts': - log.debug('rendering accounts screen') - return h(AccountsScreen, {key: 'accounts'}) - case 'accountDetail': - log.debug('rendering account detail screen') - return h(AccountDetailScreen, {key: 'account-detail'}) + log.debug('rendering main container') + return h(MainContainer, {key: 'account-detail'}) case 'sendTransaction': log.debug('rendering send tx screen') @@ -507,7 +531,7 @@ App.prototype.renderPrimary = function () { default: log.debug('rendering default, account detail screen') - return h(AccountDetailScreen, {key: 'account-detail'}) + return h(MainContainer, {key: 'account-detail'}) } } @@ -525,6 +549,8 @@ App.prototype.toggleMetamaskActive = function () { App.prototype.renderCustomOption = function (provider) { const { rpcTarget, type } = provider + const props = this.props + if (type !== 'rpc') return null // Concatenate long URLs @@ -539,13 +565,19 @@ App.prototype.renderCustomOption = function (provider) { return null default: - return h(DropMenuItem, { - label, - key: rpcTarget, - closeMenu: () => this.setState({ isNetworkMenuOpen: false }), - icon: h('i.fa.fa-question-circle.fa-lg'), - activeNetworkRender: 'custom', - }) + return h( + DropdownMenuItem, + { + key: rpcTarget, + onClick: () => props.dispatch(actions.setRpcTarget(rpcTarget)), + closeMenu: () => this.setState({ isNetworkMenuOpen: false }), + }, + [ + h('i.fa.fa-question-circle.fa-lg.menu-icon'), + label, + h('.check', '✓'), + ] + ) } } @@ -571,21 +603,27 @@ App.prototype.getNetworkName = function () { } App.prototype.renderCommonRpc = function (rpcList, provider) { - const { rpcTarget } = provider const props = this.props + const rpcTarget = provider.rpcTarget return rpcList.map((rpc) => { if ((rpc === 'http://localhost:8545') || (rpc === rpcTarget)) { return null } else { - return h(DropMenuItem, { - label: rpc, - key: rpc, - closeMenu: () => this.setState({ isNetworkMenuOpen: false }), - action: () => props.dispatch(actions.setRpcTarget(rpc)), - icon: h('i.fa.fa-question-circle.fa-lg'), - activeNetworkRender: rpc, - }) + + return h( + DropdownMenuItem, + { + key: `common${rpc}`, + closeMenu: () => this.setState({ isNetworkMenuOpen: false }), + onClick: () => props.dispatch(actions.setRpcTarget(rpc)), + }, + [ + h('i.fa.fa-question-circle.fa-lg.menu-icon'), + rpc, + rpcTarget === rpc ? h('.check', '✓') : null, + ] + ) } }) } diff --git a/ui/app/components/account-dropdowns.js b/ui/app/components/account-dropdowns.js new file mode 100644 index 000000000..2813f4752 --- /dev/null +++ b/ui/app/components/account-dropdowns.js @@ -0,0 +1,235 @@ +const Component = require('react').Component +const PropTypes = require('react').PropTypes +const h = require('react-hyperscript') +const actions = require('../actions') +const genAccountLink = require('../../lib/account-link.js') +const connect = require('react-redux').connect +const Dropdown = require('./dropdown').Dropdown +const DropdownMenuItem = require('./dropdown').DropdownMenuItem +const Identicon = require('./identicon') +const ethUtil = require('ethereumjs-util') +const copyToClipboard = require('copy-to-clipboard') + +class AccountDropdowns extends Component { + constructor (props) { + super(props) + this.state = { + accountSelectorActive: false, + optionsMenuActive: false, + } + this.accountSelectorToggleClassName = 'fa-angle-down'; + this.optionsMenuToggleClassName = 'fa-ellipsis-h'; + } + + renderAccounts () { + const { identities, selected } = this.props + + return Object.keys(identities).map((key) => { + const identity = identities[key] + const isSelected = identity.address === selected + + return h( + DropdownMenuItem, + { + closeMenu: () => {}, + onClick: () => { + this.props.actions.showAccountDetail(identity.address) + }, + }, + [ + h( + Identicon, + { + address: identity.address, + diameter: 16, + }, + ), + h('span', { style: { marginLeft: '10px' } }, identity.name || ''), + h('span', { style: { marginLeft: '10px' } }, isSelected ? h('.check', '✓') : null), + ] + ) + }) + } + + renderAccountSelector () { + const { actions } = this.props + const { accountSelectorActive } = this.state + + return h( + Dropdown, + { + style: { + marginLeft: '-125px', + minWidth: '180px', + overflowY: 'auto', + maxHeight: '300px', + }, + isOpen: accountSelectorActive, + onClickOutside: (event) => { + const { classList } = event.target + const isNotToggleElement = !classList.contains(this.accountSelectorToggleClassName) + if (accountSelectorActive && isNotToggleElement) { + this.setState({ accountSelectorActive: false }) + } + }, + }, + [ + ...this.renderAccounts(), + h( + DropdownMenuItem, + { + closeMenu: () => {}, + onClick: () => actions.addNewAccount(), + }, + [ + h( + Identicon, + { + diameter: 16, + }, + ), + h('span', { style: { marginLeft: '10px' } }, 'Create Account'), + ], + ), + h( + DropdownMenuItem, + { + closeMenu: () => {}, + onClick: () => actions.showImportPage(), + }, + [ + h( + Identicon, + { + diameter: 16, + }, + ), + h('span', { style: { marginLeft: '10px' } }, 'Import Account'), + ] + ), + ] + ) + } + + renderAccountOptions () { + const { actions } = this.props + const { optionsMenuActive } = this.state + + return h( + Dropdown, + { + style: { + marginLeft: '-162px', + minWidth: '180px', + }, + isOpen: optionsMenuActive, + onClickOutside: () => { + const { classList } = event.target + const isNotToggleElement = !classList.contains(this.optionsMenuToggleClassName) + if (optionsMenuActive && isNotToggleElement) { + this.setState({ optionsMenuActive: false }) + } + }, + }, + [ + h( + DropdownMenuItem, + { + closeMenu: () => {}, + onClick: () => { + const { selected, network } = this.props + const url = genAccountLink(selected, network) + global.platform.openWindow({ url }) + }, + }, + 'View account on Etherscan', + ), + h( + DropdownMenuItem, + { + closeMenu: () => {}, + onClick: () => { + const { selected } = this.props + const checkSumAddress = selected && ethUtil.toChecksumAddress(selected) + copyToClipboard(checkSumAddress) + }, + }, + 'Copy Address to clipboard', + ), + h( + DropdownMenuItem, + { + closeMenu: () => {}, + onClick: () => { + actions.requestAccountExport() + }, + }, + 'Export Private Key', + ), + ] + ) + } + + render () { + const { style } = this.props + const { optionsMenuActive, accountSelectorActive } = this.state + + return h( + 'span', + { + style: style, + }, + [ + h( + 'i.fa.fa-angle-down', + { + style: {}, + onClick: (event) => { + event.stopPropagation() + this.setState({ + accountSelectorActive: !accountSelectorActive, + optionsMenuActive: false, + }) + }, + }, + this.renderAccountSelector(), + ), + h( + 'i.fa.fa-ellipsis-h', + { + style: { 'marginLeft': '10px'}, + onClick: (event) => { + event.stopPropagation() + this.setState({ + accountSelectorActive: false, + optionsMenuActive: !optionsMenuActive, + }) + }, + }, + this.renderAccountOptions() + ), + ] + ) + } +} + +AccountDropdowns.propTypes = { + identities: PropTypes.objectOf(PropTypes.object), + selected: PropTypes.string, +} + +const mapDispatchToProps = (dispatch) => { + return { + actions: { + showConfigPage: () => dispatch(actions.showConfigPage()), + requestAccountExport: () => dispatch(actions.requestExportAccount()), + showAccountDetail: (address) => dispatch(actions.showAccountDetail(address)), + addNewAccount: () => dispatch(actions.addNewAccount()), + showImportPage: () => dispatch(actions.showImportPage()), + }, + } +} + +module.exports = { + AccountDropdowns: connect(null, mapDispatchToProps)(AccountDropdowns), +} diff --git a/ui/app/components/account-info-link.js b/ui/app/components/account-info-link.js deleted file mode 100644 index 6526ab502..000000000 --- a/ui/app/components/account-info-link.js +++ /dev/null @@ -1,41 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits -const Tooltip = require('./tooltip') -const genAccountLink = require('../../lib/account-link') - -module.exports = AccountInfoLink - -inherits(AccountInfoLink, Component) -function AccountInfoLink () { - Component.call(this) -} - -AccountInfoLink.prototype.render = function () { - const { selected, network } = this.props - const title = 'View account on Etherscan' - const url = genAccountLink(selected, network) - - if (!url) { - return null - } - - return h('.account-info-link', { - style: { - display: 'flex', - alignItems: 'center', - }, - }, [ - - h(Tooltip, { - title, - }, [ - h('i.fa.fa-info-circle.cursor-pointer.color-orange', { - style: { - margin: '5px', - }, - onClick () { global.platform.openWindow({ url }) }, - }), - ]), - ]) -} diff --git a/ui/app/components/drop-menu-item.js b/ui/app/components/drop-menu-item.js deleted file mode 100644 index e42948209..000000000 --- a/ui/app/components/drop-menu-item.js +++ /dev/null @@ -1,59 +0,0 @@ -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: 'Montserrat Regular', - color: 'rgb(125, 128, 130)', - cursor: 'pointer', - display: 'flex', - justifyContent: 'flex-start', - }, - }, [ - this.props.icon, - this.props.label, - this.activeNetworkRender(), - ]) -} - -DropMenuItem.prototype.activeNetworkRender = function () { - const activeNetwork = this.props.activeNetworkRender - const { provider } = this.props - const providerType = provider ? provider.type : null - if (activeNetwork === undefined) return - - switch (this.props.label) { - case 'Main Ethereum Network': - if (providerType === 'mainnet') return h('.check', '✓') - break - case 'Ropsten Test Network': - if (providerType === 'ropsten') return h('.check', '✓') - break - case 'Kovan Test Network': - if (providerType === 'kovan') return h('.check', '✓') - break - case 'Rinkeby Test Network': - if (providerType === 'rinkeby') return h('.check', '✓') - break - case 'Localhost 8545': - if (activeNetwork === 'http://localhost:8545') return h('.check', '✓') - break - default: - if (activeNetwork === 'custom') return h('.check', '✓') - } -} diff --git a/ui/app/components/dropdown.js b/ui/app/components/dropdown.js new file mode 100644 index 000000000..759800fd6 --- /dev/null +++ b/ui/app/components/dropdown.js @@ -0,0 +1,92 @@ +const Component = require('react').Component +const PropTypes = require('react').PropTypes +const h = require('react-hyperscript') +const MenuDroppo = require('menu-droppo') +const extend = require('xtend') + +const noop = () => {} + +class Dropdown extends Component { + render () { + const { isOpen, onClickOutside, style, innerStyle, children } = this.props + + const innerStyleDefaults = extend({ + borderRadius: '4px', + padding: '8px 16px', + background: 'rgba(0, 0, 0, 0.8)', + boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', + }, innerStyle) + + return h( + MenuDroppo, + { + isOpen, + zIndex: 11, + onClickOutside, + style, + innerStyle: innerStyleDefaults, + }, + [ + h( + 'style', + ` + li.dropdown-menu-item:hover { color:rgb(225, 225, 225); } + li.dropdown-menu-item { color: rgb(185, 185, 185); } + ` + ), + ...children, + ] + ) + } +} + +Dropdown.defaultProps = { + isOpen: false, + onClick: noop, +} + +Dropdown.propTypes = { + isOpen: PropTypes.bool.isRequired, + onClick: PropTypes.func.isRequired, + children: PropTypes.node, + style: PropTypes.object.isRequired, +} + +class DropdownMenuItem extends Component { + render () { + const { onClick, closeMenu, children } = this.props + + return h( + 'li.dropdown-menu-item', + { + onClick: () => { + onClick() + closeMenu() + }, + style: { + listStyle: 'none', + padding: '8px 0px 8px 0px', + fontSize: '12px', + fontStyle: 'normal', + fontFamily: 'Montserrat Regular', + cursor: 'pointer', + display: 'flex', + justifyContent: 'flex-start', + alignItems: 'center', + }, + }, + children + ) + } +} + +DropdownMenuItem.propTypes = { + closeMenu: PropTypes.func.isRequired, + onClick: PropTypes.func.isRequired, + children: PropTypes.node, +} + +module.exports = { + Dropdown, + DropdownMenuItem, +} diff --git a/ui/app/components/editable-label.js b/ui/app/components/editable-label.js index 41936f5e0..167be7eaf 100644 --- a/ui/app/components/editable-label.js +++ b/ui/app/components/editable-label.js @@ -30,7 +30,12 @@ EditableLabel.prototype.render = function () { } else { return h('div.name-label', { onClick: (event) => { - this.setState({ isEditingLabel: true }) + const nameAttribute = event.target.getAttribute('name') + // checks for class to handle smaller CTA above the account name + const classAttribute = event.target.getAttribute('class') + if (nameAttribute === 'edit' || classAttribute === 'edit-text') { + this.setState({ isEditingLabel: true }) + } }, }, this.props.children) } diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js index 3a33ebf74..93c07599d 100644 --- a/ui/app/components/ens-input.js +++ b/ui/app/components/ens-input.js @@ -44,6 +44,13 @@ EnsInput.prototype.render = function () { return h('div', { style: { width: '100%' }, }, [ + h('span', { + style: { + textAlign: 'left', + } + }, [ + 'To:' + ]), h('input.large-input', opts), // The address book functionality. h('datalist#addresses', diff --git a/ui/app/components/loading.js b/ui/app/components/loading.js index 87d6f5d20..163792584 100644 --- a/ui/app/components/loading.js +++ b/ui/app/components/loading.js @@ -1,7 +1,6 @@ const inherits = require('util').inherits const Component = require('react').Component const h = require('react-hyperscript') -const ReactCSSTransitionGroup = require('react-addons-css-transition-group') inherits(LoadingIndicator, Component) @@ -15,35 +14,28 @@ LoadingIndicator.prototype.render = function () { const { isLoading, loadingMessage } = this.props return ( - h(ReactCSSTransitionGroup, { - className: 'css-transition-group', - transitionName: 'loader', - transitionEnterTimeout: 150, - transitionLeaveTimeout: 150, + isLoading ? h('.full-flex-height', { + style: { + left: '0px', + zIndex: 10, + position: 'absolute', + flexDirection: 'column', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + height: '100%', + width: '100%', + background: 'rgba(255, 255, 255, 0.8)', + }, }, [ + h('img', { + src: 'images/loading.svg', + }), - isLoading ? h('div', { - style: { - zIndex: 10, - position: 'absolute', - flexDirection: 'column', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - height: '100%', - width: '100%', - background: 'rgba(255, 255, 255, 0.8)', - }, - }, [ - h('img', { - src: 'images/loading.svg', - }), - - h('br'), - - showMessageIfAny(loadingMessage), - ]) : null, - ]) + h('br'), + + showMessageIfAny(loadingMessage), + ]) : null ) } diff --git a/ui/app/components/network.js b/ui/app/components/network.js index d5d3e18cd..ba1d0ea11 100644 --- a/ui/app/components/network.js +++ b/ui/app/components/network.js @@ -39,7 +39,6 @@ Network.prototype.render = function () { }), h('i.fa.fa-sort-desc'), ]) - } else if (providerName === 'mainnet') { hoverText = 'Main Ethereum Network' iconName = 'ethereum-network' @@ -61,7 +60,7 @@ Network.prototype.render = function () { } return ( - h('#network_component.pointer', { + h('.network-component.pointer', { title: hoverText, onClick: (event) => this.props.onClick(event), }, [ diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 5324ccd64..1c47440f2 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -20,6 +20,34 @@ const GWEI_FACTOR = new BN(1e9) const MIN_GAS_PRICE_BN = MIN_GAS_PRICE_GWEI_BN.mul(GWEI_FACTOR) const MIN_GAS_LIMIT_BN = new BN(21000) + +// Faked, for Icon +const Identicon = require('./identicon') +const ARAGON = '960b236A07cf122663c4303350609A66A7B288C0' + +// Next: create separate react components +// roughly 5 components: +// heroIcon +// numericDisplay (contains symbol + currency) +// divider +// contentBox +// actionButtons +const sectionDivider = h('div', { + style: { + height:'1px', + background:'#E7E7E7', + }, +}) + +const contentDivider = h('div', { + style: { + marginLeft: '16px', + marginRight: '16px', + height:'1px', + background:'#E7E7E7', + }, +}) + module.exports = PendingTx inherits(PendingTx, Component) function PendingTx () { @@ -70,344 +98,342 @@ PendingTx.prototype.render = function () { this.inputs = [] return ( - - h('div', { - key: txMeta.id, + h('div.flex-column.flex-grow', { + style: { + // overflow: 'scroll', + minWidth: '355px', // TODO: maxWidth TBD, use home.html + }, }, [ - h('form#pending-tx-form', { - onSubmit: this.onSubmit.bind(this), - + // Main Send token Card + h('div.send-screen.flex-column.flex-grow', { + style: { + marginLeft: '3.5%', + marginRight: '3.5%', + background: '#FFFFFF', // $background-white + boxShadow: '0 2px 4px 0 rgba(0,0,0,0.08)', + } }, [ + h('section.flex-center.flex-row', { + style: { + zIndex: 15, // $token-icon-z-index + marginTop: '-35px', + } + }, [ + h(Identicon, { + address: ARAGON, + diameter: 76, + }), + ]), - // tx info - h('div', [ + // + // Required Fields + // - h('.flex-row.flex-center', { - style: { - maxWidth: '100%', - }, - }, [ + h('h3.flex-center', { + style: { + marginTop: '-18px', + fontSize: '16px', + }, + }, [ + 'Confirm Transaction', + ]), - h(MiniAccountPanel, { - imageSeed: address, - picOrder: 'right', - }, [ - h('span.font-small', { - style: { - fontFamily: 'Montserrat Bold, Montserrat, sans-serif', - }, - }, identity.name), - - h(Copyable, { - value: ethUtil.toChecksumAddress(address), - }, [ - h('span.font-small', { - style: { - fontFamily: 'Montserrat Light, Montserrat, sans-serif', - }, - }, addressSummary(address, 6, 4, false)), - ]), - - h('span.font-small', { - style: { - fontFamily: 'Montserrat Light, Montserrat, sans-serif', - }, - }, [ - h(EthBalance, { - value: balance, - conversionRate, - currentCurrency, - inline: true, - labelColor: '#F7861C', - }), - ]), - ]), + h('h3.flex-center', { + style: { + textAlign: 'center', + fontSize: '12px', + }, + }, [ + 'You\'re sending to Recipient ...5924', + ]), - forwardCarrat(), + h('h3.flex-center', { + style: { + textAlign: 'center', + fontSize: '36px', + marginTop: '8px', + }, + }, [ + '0.24', + ]), - this.miniAccountPanelForRecipient(), - ]), + h('h3.flex-center', { + style: { + textAlign: 'center', + fontSize: '12px', + marginTop: '4px', + }, + }, [ + 'ANT', + ]), - h('style', ` - .table-box { - margin: 7px 0px 0px 0px; - width: 100%; - } - .table-box .row { - margin: 0px; - background: rgb(236,236,236); - display: flex; - justify-content: space-between; - font-family: Montserrat Light, sans-serif; - font-size: 13px; - padding: 5px 25px; - } - .table-box .row .value { - font-family: Montserrat Regular; - } - `), + // error message + props.error && h('span.error.flex-center', props.error), - h('.table-box', [ + sectionDivider, - // Ether Value - // Currently not customizable, but easily modified - // in the way that gas and gasLimit currently are. - h('.row', [ - h('.cell.label', 'Amount'), - h(EthBalance, { value: txParams.value, currentCurrency, conversionRate }), - ]), + h('section.flex-row.flex-center', { + }, [ + h('div', { + style: { + width: '50%', + } + }, [ + h('span', { + style: { + textAlign: 'left', + fontSize: '12px', + } + }, [ + 'From' + ]) + ]), - // Gas Limit (customizable) - h('.cell.row', [ - h('.cell.label', 'Gas Limit'), - h('.cell.value', { - }, [ - h(BNInput, { - name: 'Gas Limit', - value: gasBn, - precision: 0, - scale: 0, - // The hard lower limit for gas. - min: MIN_GAS_LIMIT_BN.toString(10), - max: safeGasLimit, - suffix: 'UNITS', - style: { - position: 'relative', - top: '5px', - }, - onChange: this.gasLimitChanged.bind(this), - - ref: (hexInput) => { this.inputs.push(hexInput) }, - }), - ]), - ]), + h('div', { + style: { + width: '50%', + } + },[ + h('div', { + style: { + textAlign: 'left', + fontSize: '10px', + marginBottom: '-10px', + }, + }, 'Aragon Token'), - // Gas Price (customizable) - h('.cell.row', [ - h('.cell.label', 'Gas Price'), - h('.cell.value', { - }, [ - h(BNInput, { - name: 'Gas Price', - value: gasPriceBn, - precision: 9, - scale: 9, - suffix: 'GWEI', - min: MIN_GAS_PRICE_GWEI_BN.toString(10), - style: { - position: 'relative', - top: '5px', - }, - onChange: this.gasPriceChanged.bind(this), - ref: (hexInput) => { this.inputs.push(hexInput) }, - }), - ]), - ]), + h('div', { + style: { + textAlign: 'left', + fontSize: '8px', + }, + }, 'Your Balance 2.34 ANT') + ]) + ]), - // Max Transaction Fee (calculated) - h('.cell.row', [ - h('.cell.label', 'Max Transaction Fee'), - h(EthBalance, { value: txFeeBn.toString(16), currentCurrency, conversionRate }), - ]), + contentDivider, - h('.cell.row', { + h('section.flex-row.flex-center', { + }, [ + h('div', { + style: { + width: '50%', + } + }, [ + h('span', { style: { - fontFamily: 'Montserrat Regular', - background: 'white', - padding: '10px 25px', - }, + textAlign: 'left', + fontSize: '12px', + } }, [ - h('.cell.label', 'Max Total'), - h('.cell.value', { - style: { - display: 'flex', - alignItems: 'center', - }, - }, [ - h(EthBalance, { - value: maxCost.toString(16), - currentCurrency, - conversionRate, - inline: true, - labelColor: 'black', - fontSize: '16px', - }), - ]), - ]), + 'To' + ]) + ]), - // Data size row: - h('.cell.row', { + h('div', { + style: { + width: '50%', + } + },[ + h('div', { style: { - background: '#f7f7f7', - paddingBottom: '0px', + textAlign: 'left', + fontSize: '10px', + marginBottom: '-10px', }, - }, [ - h('.cell.label'), - h('.cell.value', { - style: { - fontFamily: 'Montserrat Light', - fontSize: '11px', - }, - }, `Data included: ${dataLength} bytes`), - ]), - ]), // End of Table + }, 'Ethereum Address'), + h('div', { + style: { + textAlign: 'left', + fontSize: '8px', + }, + }, '...5924') + ]) ]), - h('style', ` - .conf-buttons button { - margin-left: 10px; - text-transform: uppercase; - } - `), + contentDivider, - txMeta.simulationFails ? - h('.error', { - style: { - marginLeft: 50, - fontSize: '0.9em', - }, - }, 'Transaction Error. Exception thrown in contract code.') - : null, - - !isValidAddress ? - h('.error', { + h('section.flex-row.flex-center', { + }, [ + h('div', { style: { - marginLeft: 50, - fontSize: '0.9em', - }, - }, 'Recipient address is invalid. Sending this transaction will result in a loss of ETH.') - : null, - - insufficientBalance ? - h('span.error', { + width: '50%', + } + }, [ + h('span', { + style: { + textAlign: 'left', + fontSize: '12px', + } + }, [ + 'Gas Fee' + ]) + ]), + + h('div', { style: { - marginLeft: 50, - fontSize: '0.9em', - }, - }, 'Insufficient balance for transaction') - : null, - - // send + cancel - h('.flex-row.flex-space-around.conf-buttons', { + width: '50%', + } + },[ + h('div', { + style: { + textAlign: 'left', + fontSize: '10px', + marginBottom: '-10px', + }, + }, '$0.04 USD'), + + h('div', { + style: { + textAlign: 'left', + fontSize: '8px', + }, + }, '0.001575 ETH') + ]) + ]), + + contentDivider, + + h('section.flex-row.flex-center', { style: { - display: 'flex', - justifyContent: 'flex-end', - margin: '14px 25px', - }, + backgroundColor: '#F6F6F6', // $wild-sand + borderRadius: '8px', + marginLeft: '10px', + marginRight: '10px', + paddingLeft: '6px', + paddingRight: '6px', + marginBottom: '10px', + } }, [ + h('div', { + style: { + width: '50%', + } + }, [ + h('div', { + style: { + textAlign: 'left', + fontSize: '12px', + marginBottom: '-10px', + } + }, [ + 'Total Tokens' + ]), + h('div', { + style: { + textAlign: 'left', + fontSize: '8px', + } + }, [ + 'Total Gas' + ]) - insufficientBalance ? - h('button.btn-green', { - onClick: props.buyEth, - }, 'Buy Ether') - : null, - - h('button', { - onClick: (event) => { - this.resetGasFields() - event.preventDefault() - }, - }, 'Reset'), - - // Accept Button - h('input.confirm.btn-green', { - type: 'submit', - value: 'SUBMIT', - style: { marginLeft: '10px' }, - disabled: insufficientBalance || !this.state.valid || !isValidAddress || this.state.submitting, - }), + ]), - h('button.cancel.btn-red', { - onClick: props.cancelTransaction, - }, 'Reject'), - ]), - ]), - ]) - ) -} + h('div', { + style: { + width: '50%', + } + },[ + h('div', { + style: { + textAlign: 'left', + fontSize: '10px', + marginBottom: '-10px', + }, + }, '0.24 ANT (127.00 USD)'), -PendingTx.prototype.miniAccountPanelForRecipient = function () { - const props = this.props - const txData = props.txData - const txParams = txData.txParams || {} - const isContractDeploy = !('to' in txParams) - - // If it's not a contract deploy, send to the account - if (!isContractDeploy) { - return h(MiniAccountPanel, { - imageSeed: txParams.to, - picOrder: 'left', - }, [ + h('div', { + style: { + textAlign: 'left', + fontSize: '8px', + }, + }, '0.249 ETH') + ]) + ]), - h('span.font-small', { - style: { - fontFamily: 'Montserrat Bold, Montserrat, sans-serif', - }, - }, nameForAddress(txParams.to, props.identities)), + ]), // end of container - h(Copyable, { - value: ethUtil.toChecksumAddress(txParams.to), + h('form#pending-tx-form.flex-column.flex-center', { + onSubmit: this.onSubmit.bind(this), }, [ - h('span.font-small', { + // Reset Button + // h('button', { + // onClick: (event) => { + // this.resetGasFields() + // event.preventDefault() + // }, + // }, 'Reset'), + + // Accept Button + h('input.confirm.btn-green', { + type: 'submit', + value: 'CONFIRM', style: { - fontFamily: 'Montserrat Light, Montserrat, sans-serif', + marginTop: '8px', + width: '8em', + color: '#FFFFFF', + borderRadius: '2px', + fontSize: '12px', + lineHeight: '20px', + textAlign: 'center', + borderStyle: 'none', }, - }, addressSummary(txParams.to, 6, 4, false)), - ]), - - ]) - } else { - return h(MiniAccountPanel, { - picOrder: 'left', - }, [ + disabled: insufficientBalance || !this.state.valid || !isValidAddress || this.state.submitting, + }), - h('span.font-small', { - style: { - fontFamily: 'Montserrat Bold, Montserrat, sans-serif', - }, - }, 'New Contract'), - - ]) - } -} - -PendingTx.prototype.gasPriceChanged = function (newBN, valid) { - log.info(`Gas price changed to: ${newBN.toString(10)}`) - const txMeta = this.gatherTxMeta() - txMeta.txParams.gasPrice = '0x' + newBN.toString('hex') - this.setState({ - txData: clone(txMeta), - valid, - }) -} - -PendingTx.prototype.gasLimitChanged = function (newBN, valid) { - log.info(`Gas limit changed to ${newBN.toString(10)}`) - const txMeta = this.gatherTxMeta() - txMeta.txParams.gas = '0x' + newBN.toString('hex') - this.setState({ - txData: clone(txMeta), - valid, - }) + // Cancel Button + h('button.cancel.btn-light', { + style: { + background: '#F7F7F7', // $alabaster + border: 'none', + opacity: 1, + width: '8em', + }, + onClick: props.cancelTransaction, + }, 'CANCEL'), + ]), + ]) // end of minwidth wrapper + ) } -PendingTx.prototype.resetGasFields = function () { - log.debug(`pending-tx resetGasFields`) - - this.inputs.forEach((hexInput) => { - if (hexInput) { - hexInput.setValid() - } - }) - - this.setState({ - txData: null, - valid: true, - }) -} +// PendingTx.prototype.gasPriceChanged = function (newBN, valid) { +// log.info(`Gas price changed to: ${newBN.toString(10)}`) +// const txMeta = this.gatherTxMeta() +// txMeta.txParams.gasPrice = '0x' + newBN.toString('hex') +// this.setState({ +// txData: clone(txMeta), +// valid, +// }) +// } + +// PendingTx.prototype.gasLimitChanged = function (newBN, valid) { +// log.info(`Gas limit changed to ${newBN.toString(10)}`) +// const txMeta = this.gatherTxMeta() +// txMeta.txParams.gas = '0x' + newBN.toString('hex') +// this.setState({ +// txData: clone(txMeta), +// valid, +// }) +// } + +// PendingTx.prototype.resetGasFields = function () { +// log.debug(`pending-tx resetGasFields`) + +// this.inputs.forEach((hexInput) => { +// if (hexInput) { +// hexInput.setValid() +// } +// }) + +// this.setState({ +// txData: null, +// valid: true, +// }) +// } PendingTx.prototype.onSubmit = function (event) { event.preventDefault() @@ -466,15 +492,3 @@ PendingTx.prototype.bnMultiplyByFraction = function (targetBN, numerator, denomi const denomBN = new BN(denominator) return targetBN.mul(numBN).div(denomBN) } - -function forwardCarrat () { - return ( - h('img', { - src: 'images/forward-carrat.svg', - style: { - padding: '5px 6px 0px 10px', - height: '37px', - }, - }) - ) -} diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js index e0a720426..76a265d63 100644 --- a/ui/app/components/shapeshift-form.js +++ b/ui/app/components/shapeshift-form.js @@ -2,7 +2,6 @@ const PersistentForm = require('../../lib/persistent-form') const h = require('react-hyperscript') const inherits = require('util').inherits const connect = require('react-redux').connect -const ReactCSSTransitionGroup = require('react-addons-css-transition-group') const actions = require('../actions') const Qr = require('./qr-code') const isValidAddress = require('../util').isValidAddress @@ -24,14 +23,7 @@ function ShapeshiftForm () { } ShapeshiftForm.prototype.render = function () { - return h(ReactCSSTransitionGroup, { - className: 'css-transition-group', - transitionName: 'main', - transitionEnterTimeout: 300, - transitionLeaveTimeout: 300, - }, [ - this.props.qrRequested ? h(Qr, {key: 'qr'}) : this.renderMain(), - ]) + return this.props.qrRequested ? h(Qr, {key: 'qr'}) : this.renderMain() } ShapeshiftForm.prototype.renderMain = function () { diff --git a/ui/app/components/tab-bar.js b/ui/app/components/tab-bar.js index 6295e7dd9..bef444a48 100644 --- a/ui/app/components/tab-bar.js +++ b/ui/app/components/tab-bar.js @@ -21,6 +21,7 @@ TabBar.prototype.render = function () { background: '#EBEBEB', color: '#AEAEAE', paddingTop: '4px', + minHeight: '30px', }, }, tabs.map((tab) => { const { key, content } = tab diff --git a/ui/app/components/token-list.js b/ui/app/components/token-list.js index 20cfa897e..5ea31ae8d 100644 --- a/ui/app/components/token-list.js +++ b/ui/app/components/token-list.js @@ -47,10 +47,11 @@ TokenList.prototype.render = function () { return h(TokenCell, tokenData) }) - return h('div', [ - h('ol', { + return h('.full-flex-height', [ + this.renderTokenStatusBar(), + + h('ol.full-flex-height.flex-column', { style: { - height: '260px', overflowY: 'auto', display: 'flex', flexDirection: 'column', @@ -63,6 +64,7 @@ TokenList.prototype.render = function () { flex-direction: row; align-items: center; padding: 10px; + min-height: 50px; } li.token-cell > h3 { @@ -76,17 +78,37 @@ TokenList.prototype.render = function () { `), ...tokenViews, - tokenViews.length ? null : this.message('No Tokens Found.'), + h('.flex-grow'), ]), - this.addTokenButtonElement(), ]) } -TokenList.prototype.addTokenButtonElement = function () { - return h('div', [ - h('div.footer.hover-white.pointer', { +TokenList.prototype.renderTokenStatusBar = function () { + const { tokens } = this.state + + let msg + if (tokens.length === 1) { + msg = `You own 1 token` + } else if (tokens.length === 1) { + msg = `You own ${tokens.length} tokens` + } else { + msg = `No tokens found` + } + + return h('div', { + style: { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + minHeight: '70px', + padding: '10px', + }, + }, [ + h('span', msg), + h('button', { key: 'reveal-account-bar', - onClick: () => { + onClick: (event) => { + event.preventDefault() this.props.addToken() }, style: { @@ -97,7 +119,7 @@ TokenList.prototype.addTokenButtonElement = function () { alignItems: 'center', }, }, [ - h('i.fa.fa-plus.fa-lg'), + 'ADD TOKEN', ]), ]) } diff --git a/ui/app/components/transaction-list.js b/ui/app/components/transaction-list.js index 3b4ba741e..69b72614c 100644 --- a/ui/app/components/transaction-list.js +++ b/ui/app/components/transaction-list.js @@ -24,7 +24,11 @@ TransactionList.prototype.render = function () { return ( - h('section.transaction-list', [ + h('section.transaction-list.full-flex-height', { + style: { + justifyContent: 'center', + }, + }, [ h('style', ` .transaction-list .transaction-list-item:not(:last-of-type) { @@ -39,7 +43,7 @@ TransactionList.prototype.render = function () { h('.tx-list', { style: { overflowY: 'auto', - height: '300px', + height: '100%', padding: '0 20px', textAlign: 'center', }, @@ -64,13 +68,17 @@ TransactionList.prototype.render = function () { }, }) }) - : h('.flex-center', { + : h('.flex-center.full-flex-height', { style: { flexDirection: 'column', - height: '100%', + justifyContent: 'center', }, }, [ - 'No transaction history.', + h('p', { + style: { + marginTop: '50px', + }, + }, 'No transaction history.'), ]), ]), ]) diff --git a/ui/app/components/tx-view.js b/ui/app/components/tx-view.js new file mode 100644 index 000000000..2bc6daae5 --- /dev/null +++ b/ui/app/components/tx-view.js @@ -0,0 +1,228 @@ +const Component = require('react').Component +const connect = require('react-redux').connect +const h = require('react-hyperscript') +const inherits = require('util').inherits +const actions = require('../actions') +// slideout menu +const SlideoutMenu = require('react-burger-menu').slide +const WalletView = require('./wallet-view') + +const Identicon = require('./identicon') +// const AccountDropdowns = require('./account-dropdowns').AccountDropdowns +// const Content = require('./wallet-content-display') + +module.exports = connect(mapStateToProps, mapDispatchToProps)(TxView) + +function mapStateToProps (state) { + return { + sidebarOpen: state.appState.sidebarOpen, + } +} + +function mapDispatchToProps (dispatch) { + return { + showSidebar: () => {dispatch(actions.showSidebar())}, + hideSidebar: () => {dispatch(actions.hideSidebar())}, + } +} + +const contentDivider = h('div', { + style: { + marginLeft: '1.3em', + marginRight: '1.3em', + height:'1px', + background:'#E7E7E7', // TODO: make custom color + }, +}) + +inherits(TxView, Component) +function TxView () { + Component.call(this) +} + +TxView.prototype.render = function () { + const selected = '0x82df11beb942BEeeD58d466fCb0F0791365C7684' // TODO: remove fake address + + return h('div.tx-view.flex-column', { + style: { + flex: '62 0 62%', + background: '#FFFFFF', + } + }, [ + + h('div.flex-row.phone-visible', { + style: { + margin: '1em 0.9em', + alignItems: 'center' + }, + onClick: () => { + this.props.sidebarOpen ? this.props.hideSidebar() : this.props.showSidebar() + }, + }, [ + // burger + h('div.fa.fa-bars', { + style: { + fontSize: '1.3em', + }, + }, []), + + //account display + h('.identicon-wrapper.select-none', { + style: { + marginLeft: '0.9em', + }, + }, [ + h(Identicon, { + diameter: 24, + address: selected, + }), + ]), + + h('span.account-name', { + style: {} + }, [ + 'Account 1' + ]), + + ]), + + // laptop: flex-row, flex-center + // mobile: flex-column + h('div.hero-balance', { + style: {}, + }, [ + + // laptop: 50px 50px + // mobile: 100px 100px + h('img.hero-balance-icon', { + src: '../images/eth_logo.svg', + style: {} + }), + + // laptop: 5vw? + // phone: 50vw? + h('div.flex-column.hero-balance-display', {}, [ + h('div.token-amount', { + style: {} + }, '1001.124 ETH'), + + h('div.fiat-amount', { + style: {} + }, '$300,000 USD'), + ]), + + // laptop: 10vw? + // phone: 75vw? + h('div.flex-row.flex-center.hero-balance-buttons', { + style: {} + }, [ + h('button.btn-clear', { + style: { + textAlign: 'center', + }, + }, 'BUY'), + + h('button.btn-clear', { + style: { + textAlign: 'center', + marginLeft: '1.4em', + }, + }, 'SEND'), + + ]), + ]), + + h('div.flex-row', { + style: { + margin: '1.8em 0.9em 0.8em 0.9em', + } + }, [ + + // tx-view-tab.js + h('div.flex-row', { + }, [ + + h('div', { + style: { + borderBottom: '0.07em solid black', + paddingBottom: '0.015em', + } + }, 'TRANSACTIONS'), + + h('div', { + style: { + marginLeft: '1.25em', + } + }, 'TOKENS'), + + ]), + ]), + + contentDivider, + + this.renderTransactionListItem(), + + contentDivider, + + this.renderTransactionListItem(), + + contentDivider, + + ]) + // column + // tab row + // divider + // item +} + +TxView.prototype.renderTransactionListItem = function () { + return h('div.flex-column', { + style: { + alignItems: 'stretch', + margin: '0.6em 1.3em 0.6em 1.3em', + } + }, [ + + h('div', { + style: { + flexGrow: 1, + marginTop: '0.3em', + } + }, 'Jul 01, 2017'), + + h('div.flex-row', { + style: { + alignItems: 'stretch', + } + }, [ + + h('div', { + style: { + flexGrow: 1, + } + }, 'icon'), + + h('div', { + style: { + flexGrow: 3, + } + }, 'Hash'), + + h('div', { + style: { + flexGrow: 5, + } + }, 'Status'), + + h('div', { + style: { + flexGrow: 2, + } + }, 'Details'), + + ]) + + ]) +} + + diff --git a/ui/app/components/wallet-content-display.js b/ui/app/components/wallet-content-display.js new file mode 100644 index 000000000..3baffad69 --- /dev/null +++ b/ui/app/components/wallet-content-display.js @@ -0,0 +1,56 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits + +module.exports = WalletContentDisplay + +inherits(WalletContentDisplay, Component) +function WalletContentDisplay () { + Component.call(this) +} + +WalletContentDisplay.prototype.render = function () { + const { title, amount, fiatValue, active, style } = this.props + + // TODO: Separate component: wallet-content-account + return h('div.flex-column', { + style: { + marginLeft: '1.3em', + alignItems: 'flex-start', + ...style, + } + }, [ + + h('span', { + style: { + fontSize: '1.1em', + }, + }, title), + + h('span', { + style: { + fontSize: '1.8em', + margin: '0.4em 0em', + }, + }, amount), + + h('span', { + style: { + fontSize: '1.3em', + }, + }, fiatValue), + + active && h('div', { + style: { + position: 'absolute', + marginLeft: '-1.3em', + height: '6em', + width: '0.3em', + background: '#D8D8D8', // TODO: add to resuable colors + } + }, [ + ]) + ]) + +} + diff --git a/ui/app/components/wallet-view.js b/ui/app/components/wallet-view.js new file mode 100644 index 000000000..cb1448598 --- /dev/null +++ b/ui/app/components/wallet-view.js @@ -0,0 +1,97 @@ +const Component = require('react').Component +const connect = require('react-redux').connect +const h = require('react-hyperscript') +const inherits = require('util').inherits +const Identicon = require('./identicon') +const AccountDropdowns = require('./account-dropdowns').AccountDropdowns +const Content = require('./wallet-content-display') +const actions = require('../actions') + +module.exports = connect(mapStateToProps, mapDispatchToProps)(WalletView) + +function mapStateToProps (state) { + return { + network: state.metamask.network, + sidebarOpen: state.appState.sidebarOpen, + } +} + +function mapDispatchToProps (dispatch) { + return { + showSendPage: () => {dispatch(actions.showSendPage())}, + hideSidebar: () => {dispatch(actions.hideSidebar())}, + } +} + +inherits(WalletView, Component) +function WalletView () { + Component.call(this) +} + +const noop = () => {} + +WalletView.prototype.render = function () { + const selected = '0x82df11beb942BEeeD58d466fCb0F0791365C7684' // TODO: remove fake address + const { network, responsiveDisplayClassname, style } = this.props + + return h('div.wallet-view.flex-column' + (responsiveDisplayClassname || ''), { + style: { + flex: '28 0 28%', + background: '#FAFAFA', // TODO: add to reusable colors + ...style, + } + }, [ + + // TODO: Separate component: wallet account details + h('div.flex-column', { + style: {} + }, [ + + h('.identicon-wrapper.select-none', [ + h(Identicon, { + diameter: 24, + address: selected, + }), + ]), + + h('span.account-name', { + style: {} + }, [ + 'Account 1' + ]), + + h( + AccountDropdowns, + { + style: { + marginLeft: 'auto', + cursor: 'pointer', + }, + selected, + network, // TODO: this prop could be in the account dropdown container + identities: {}, + }, + ), + + ]), + + h(Content, { + title: 'Wallet', + amount: '1001.124 ETH', + fiatValue: '$300,000.00 USD', + active: true, + }), + + // Wallet contents + h(Content, { + title: "Total Token Balance", + amount: "45.439 ETH", + fiatValue: "$13,000.00 USD", + active: false, + style: { + marginTop: '1.3em', + } + }) + + ]) +} diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 747d3ce2b..4a8c616e2 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -1,6 +1,5 @@ const inherits = require('util').inherits const Component = require('react').Component -const ReactCSSTransitionGroup = require('react-addons-css-transition-group') const h = require('react-hyperscript') const connect = require('react-redux').connect const actions = require('./actions') @@ -53,75 +52,25 @@ ConfirmTxScreen.prototype.render = function () { log.info(`rendering a combined ${unconfTxList.length} unconf msg & txs`) if (unconfTxList.length === 0) return h(Loading, { isLoading: true }) - return ( - - h('.flex-column.flex-grow', [ - - // subtitle and nav - h('.section-title.flex-row.flex-center', [ - !isNotification ? h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', { - onClick: this.goHome.bind(this), - }) : null, - h('h2.page-subtitle', 'Confirm Transaction'), - isNotification ? h(NetworkIndicator, { - network: network, - provider: provider, - }) : null, - ]), - - h('h3', { - style: { - alignSelf: 'center', - display: unconfTxList.length > 1 ? 'block' : 'none', - }, - }, [ - h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', { - style: { - display: props.index === 0 ? 'none' : 'inline-block', - }, - onClick: () => props.dispatch(actions.previousTx()), - }), - ` ${props.index + 1} of ${unconfTxList.length} `, - h('i.fa.fa-arrow-right.fa-lg.cursor-pointer', { - style: { - display: props.index + 1 === unconfTxList.length ? 'none' : 'inline-block', - }, - onClick: () => props.dispatch(actions.nextTx()), - }), - ]), - - warningIfExists(props.warning), - - h(ReactCSSTransitionGroup, { - className: 'css-transition-group', - transitionName: 'main', - transitionEnterTimeout: 300, - transitionLeaveTimeout: 300, - }, [ - - currentTxView({ - // Properties - txData: txData, - key: txData.id, - selectedAddress: props.selectedAddress, - accounts: props.accounts, - identities: props.identities, - conversionRate, - currentCurrency, - blockGasLimit, - // Actions - buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress), - sendTransaction: this.sendTransaction.bind(this), - cancelTransaction: this.cancelTransaction.bind(this, txData), - signMessage: this.signMessage.bind(this, txData), - signPersonalMessage: this.signPersonalMessage.bind(this, txData), - cancelMessage: this.cancelMessage.bind(this, txData), - cancelPersonalMessage: this.cancelPersonalMessage.bind(this, txData), - }), - - ]), - ]) - ) + return currentTxView({ + // Properties + txData: txData, + key: txData.id, + selectedAddress: props.selectedAddress, + accounts: props.accounts, + identities: props.identities, + conversionRate, + currentCurrency, + blockGasLimit, + // Actions + buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress), + sendTransaction: this.sendTransaction.bind(this), + cancelTransaction: this.cancelTransaction.bind(this, txData), + signMessage: this.signMessage.bind(this, txData), + signPersonalMessage: this.signPersonalMessage.bind(this, txData), + cancelMessage: this.cancelMessage.bind(this, txData), + cancelPersonalMessage: this.cancelPersonalMessage.bind(this, txData), + }) } function currentTxView (opts) { diff --git a/ui/app/css/debug.css b/ui/app/css/debug.css deleted file mode 100644 index 3e125bcd4..000000000 --- a/ui/app/css/debug.css +++ /dev/null @@ -1,21 +0,0 @@ -/* -debug / dev -*/ - -#app-content { - border: 2px solid green; -} - -#design-container { - position: absolute; - left: 360px; - top: -42px; - width: calc(100vw - 360px); - height: 100vh; - overflow: scroll; -} - -#design-container img { - width: 2000px; - margin-right: 600px; -}
\ No newline at end of file diff --git a/ui/app/css/index.scss b/ui/app/css/index.scss new file mode 100644 index 000000000..01899ccad --- /dev/null +++ b/ui/app/css/index.scss @@ -0,0 +1,13 @@ +/* + ITCSS + + http://www.creativebloq.com/web-design/manage-large-css-projects-itcss-101517528 + https://www.xfive.co/blog/itcss-scalable-maintainable-css-architecture/ + */ +@import './itcss/settings/index.scss'; +@import './itcss/tools/index.scss'; +@import './itcss/generic/index.scss'; +@import './itcss/base/index.scss'; +@import './itcss/objects/index.scss'; +@import './itcss/components/index.scss'; +@import './itcss/trumps/index.scss'; diff --git a/ui/app/css/itcss/base/index.scss b/ui/app/css/itcss/base/index.scss new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ui/app/css/itcss/base/index.scss diff --git a/ui/app/css/itcss/components/buttons.scss b/ui/app/css/itcss/components/buttons.scss new file mode 100644 index 000000000..114a2f892 --- /dev/null +++ b/ui/app/css/itcss/components/buttons.scss @@ -0,0 +1,83 @@ +/* + Buttons + */ + +.btn-green { + background-color: #02C9B1; // TODO: reusable color in colors.css +} + +button.btn-clear { + background: white; + border: 1px solid; +} + +// No longer used in flat design, remove when modal buttons done +// div.wallet-btn { +// border: 1px solid rgb(91, 93, 103); +// border-radius: 2px; +// height: 30px; +// width: 75px; +// font-size: 0.8em; +// text-align: center; +// line-height: 25px; +// } + +// .btn-red { +// background: rgba(254, 35, 17, 1); +// box-shadow: 0px 3px 6px rgba(254, 35, 17, 0.36); +// } + +// button[disabled], input[type="submit"][disabled] { +// cursor: not-allowed; +// background: rgba(197, 197, 197, 1); +// box-shadow: 0px 3px 6px rgba(197, 197, 197, 0.36); +// } + +// button.spaced { +// margin: 2px; +// } + +// button:not([disabled]):hover, input[type="submit"]:not([disabled]):hover { +// transform: scale(1.1); +// } +// button:not([disabled]):active, input[type="submit"]:not([disabled]):active { +// transform: scale(0.95); +// } + +button.primary { + padding: 8px 12px; + background: #F7861C; + box-shadow: 0px 3px 6px rgba(247, 134, 28, 0.36); + color: white; + font-size: 1.1em; + font-family: 'Montserrat Regular'; + text-transform: uppercase; +} + +.btn-light { + padding: 8px 12px; + // background: #FFFFFF; // $bg-white + box-shadow: 0px 3px 6px rgba(247, 134, 28, 0.36); + color: #585D67; // TODO: make reusable light button color + font-size: 1.1em; + font-family: 'Montserrat Regular'; + text-transform: uppercase; + text-align: center; + line-height: 20px; + border-radius: 2px; + border: 1px solid #979797; // #TODO: make reusable light border color + opacity: 0.5; +} + +// TODO: cleanup: not used anywhere +button.btn-thin { + border: 1px solid; + border-color: #4D4D4D; + color: #4D4D4D; + background: rgb(255, 174, 41); + border-radius: 4px; + min-width: 200px; + margin: 12px 0; + padding: 6px; + font-size: 13px; +} diff --git a/ui/app/css/itcss/components/footer.scss b/ui/app/css/itcss/components/footer.scss new file mode 100644 index 000000000..000a53eed --- /dev/null +++ b/ui/app/css/itcss/components/footer.scss @@ -0,0 +1,4 @@ +.app-footer { + padding-bottom: 10px; + align-items: center; +} diff --git a/ui/app/css/itcss/components/header.scss b/ui/app/css/itcss/components/header.scss new file mode 100644 index 000000000..405c45f7f --- /dev/null +++ b/ui/app/css/itcss/components/header.scss @@ -0,0 +1,54 @@ +.app-header { + align-items: center; + visibility: visible; + background: rgb(239, 239, 239); + padding-top: 1.5vh; + height: 12vh; + max-height: 60px; + position: relative; + z-index: 12; + padding: 6px 8px; + // background: #EFEFEF; // $gallery + + @media screen and (max-width: 575px) { + position: fixed; + height: 34px; + width: 100%; + box-shadow: 0px 2px 2px 1px rgba(0, 0, 0, 0.08); + z-index: 30; + } +} + +.app-header h1 { + font-family: 'Montserrat Regular'; + text-transform: uppercase; + color: #22232C; // $shark +} + +h2.page-subtitle { + font-family: 'Montserrat Regular'; + text-transform: uppercase; + color: #AEAEAE; + font-size: 1em; + margin: 12px; +} + +.network-component-wrapper { + display: flex; + flex-direction: row; + align-items: center; + + @media screen and (min-width: 576px) { + margin-bottom: 1.8em; + } +} + +.left-menu-wrapper { + display: flex; + flex-direction: row; + align-items: center; + + @media screen and (min-width: 576px) { + margin-bottom: 1.8em; + } +}
\ No newline at end of file diff --git a/ui/app/css/itcss/components/hero-balance.scss b/ui/app/css/itcss/components/hero-balance.scss new file mode 100644 index 000000000..fabba9ed8 --- /dev/null +++ b/ui/app/css/itcss/components/hero-balance.scss @@ -0,0 +1,96 @@ +$break-small: 575px; +$break-medium: 780px; +$break-large: 576px; + +.hero-balance { + @media screen and (max-width: $break-small) { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + margin: 0.3em 0.9em 0.8em 0.9em; + } + + @media screen and (min-width: $break-large) { + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + margin: 2.8em 0.9em 0.8em 0.9em; + } + + .hero-balance-display { + + @media screen and (max-width: $break-small) { + text-align: center; + + .token-amount { + font-size: 175%; + margin-top: 12.5%; + } + + .fiat-amount { + font-size: 115%; + margin-top: 8.5%; + color: #A0A0A0; + } + } + + @media screen and (min-width: $break-large) { + flex-grow: 3; + margin-left: 3%; + justify-content: flex-start; + align-items: flex-start; + + .token-amount { + font-size: 135%; + } + + .fiat-amount { + margin-top: 0.25%; + font-size: 105%; + } + + } + + } + + .hero-balance-icon { + border-radius: 25px; + width: 45px; + height: 45px; + // TODO: colors + border: 1px solid #DEDEDE; + } + + .hero-balance-buttons { + @media screen and (max-width: $break-small) { + width: 100%; + height: 100px; // needed a round number to set the heights of the buttons inside + } + + @media screen and (min-width: $break-large) { + flex-grow: 2; + } + + button.btn-clear { + font-size: 75%; + background: white; + border: 1px solid; + + @media screen and (max-width: $break-small) { + width: 28%; + height: 55%; + } + + @media screen and (min-width: $break-large) { + width: 5%; + flex-grow: 2; + height: 4.2vh; + min-height: 28px; + font-size: .7em; + } + } + + } +}
\ No newline at end of file diff --git a/ui/app/css/itcss/components/identicon.scss b/ui/app/css/itcss/components/identicon.scss new file mode 100644 index 000000000..2f2de6ed8 --- /dev/null +++ b/ui/app/css/itcss/components/identicon.scss @@ -0,0 +1,7 @@ +.identicon { + height: 46px; + width: 46px; + background-size: cover; + border-radius: 100%; + border: 3px solid gray; +} diff --git a/ui/app/css/itcss/components/index.scss b/ui/app/css/itcss/components/index.scss new file mode 100644 index 000000000..8b6698eed --- /dev/null +++ b/ui/app/css/itcss/components/index.scss @@ -0,0 +1,8 @@ +@import './buttons.scss'; +@import './header.scss'; +@import './footer.scss'; +@import './identicon.scss'; +@import './network.scss'; +@import './newui-sections.scss'; +@import './hero-balance.scss'; +@import './sections.scss'; diff --git a/ui/app/css/itcss/components/network.scss b/ui/app/css/itcss/components/network.scss new file mode 100644 index 000000000..fb135d190 --- /dev/null +++ b/ui/app/css/itcss/components/network.scss @@ -0,0 +1,12 @@ +.network-indicator { + display: flex; + align-items: center; + font-size: 0.6em; + +} + +.network-name { + width: 5.2em; + line-height: 9px; + text-rendering: geometricPrecision; +} diff --git a/ui/app/css/itcss/components/newui-sections.scss b/ui/app/css/itcss/components/newui-sections.scss new file mode 100644 index 000000000..761e74561 --- /dev/null +++ b/ui/app/css/itcss/components/newui-sections.scss @@ -0,0 +1,109 @@ +/* + NewUI Container Elements + */ + +// Main container +.main-container { + position: absolute; + z-index: 18; + font-family: DIN OT; + display: flex; + flex-wrap: wrap; + align-items: stretch; + overflow-y: scroll; +} + +.sidebar { + flex: 1 0 230px; + background: rgb(250, 250, 250); + z-index: 26; + position: fixed; + top: 35px; + left: 0px; + right: 0px; + bottom: 0px; + opacity: 1; + visibility: visible; + will-change: transform; + overflow-y: auto; + box-shadow: rgba(0, 0, 0, 0.15) 2px 2px 4px; + width: 85%; + height: 100%; +} + +.sidebar-overlay { + z-index: 25; + position: fixed; + top: 35px; + left: 0px; + right: 0px; + bottom: 0px; + opacity: 1; + visibility: visible; + background-color: rgba(0, 0, 0, 0.3); +} + +@media screen and (min-width: 576px) { + .lap-visible { + display: flex; + } + + .phone-visible { + display: none; + } + + .main-container { + margin-top: 35px; + width: 85%; + height: 90vh; + box-shadow: 0 0 7px 0 rgba(0,0,0,0.08); + } +} + +@media screen and (min-width: 769px) { + .main-container { + margin-top: 35px; + width: 80%; + height: 82vh; + box-shadow: 0 0 7px 0 rgba(0,0,0,0.08); + } +} + +@media screen and (min-width: 1281px) { + .main-container { + margin-top: 35px; + width: 65%; + height: 82vh; + box-shadow: 0 0 7px 0 rgba(0,0,0,0.08); + } +} + +@media screen and (max-width: 575px) { + .lap-visible { + display: none; + } + + .phone-visible { + display: flex; + } + + .main-container { + margin-top: 35px; + width: 100%; + height: 100%; + } + + button.btn-clear { + width: 93px; + height: 50px; + font-size: .7em; + background: white; + border: 1px solid; + } +} + +// wallet view +.account-name { + font-size: 102%; + margin-left: 3%; +} diff --git a/ui/app/css/index.css b/ui/app/css/itcss/components/sections.scss index 808aafb4c..4a256c65b 100644 --- a/ui/app/css/index.css +++ b/ui/app/css/itcss/components/sections.scss @@ -1,151 +1,31 @@ /* -faint orange (textfield shades) #FAF6F0 -light orange (button shades): #F5C26D -dark orange (text): #F5A623 -borders/font/any gray: #4A4A4A +App Sections + TODO: Move into separate files. */ /* -application specific styles +debug / dev */ -* { - box-sizing: border-box; -} - -html, body { - font-family: 'Montserrat Regular', Arial; - color: #4D4D4D; - font-weight: 300; - line-height: 1.4em; - background: #F7F7F7; -} - -input:focus, textarea:focus { - outline: none; -} - -#app-content { - overflow-x: hidden; - min-width: 357px; - width: 360px; - height: 500px; -} - -button, input[type="submit"] { - font-family: 'Montserrat Bold'; - outline: none; - cursor: pointer; - padding: 8px 12px; - border: none; - color: white; - transform-origin: center center; - transition: transform 50ms ease-in; - /* default orange */ - background: rgba(247, 134, 28, 1); - box-shadow: 0px 3px 6px rgba(247, 134, 28, 0.36); -} +// #app-content { +// border: 2px solid green; +// } -.btn-green, input[type="submit"].btn-green { - background: rgba(106, 195, 96, 1); - box-shadow: 0px 3px 6px rgba(106, 195, 96, 0.36); -} +// #design-container { +// position: absolute; +// left: 360px; +// top: -42px; +// width: calc(100vw - 360px); +// height: 100vh; +// overflow: scroll; +// } -.btn-red { - background: rgba(254, 35, 17, 1); - box-shadow: 0px 3px 6px rgba(254, 35, 17, 0.36); -} +// #design-container img { +// width: 2000px; +// margin-right: 600px; +// } -button[disabled], input[type="submit"][disabled] { - cursor: not-allowed; - background: rgba(197, 197, 197, 1); - box-shadow: 0px 3px 6px rgba(197, 197, 197, 0.36); -} - -button.spaced { - margin: 2px; -} - -button:not([disabled]):hover, input[type="submit"]:not([disabled]):hover { - transform: scale(1.1); -} -button:not([disabled]):active, input[type="submit"]:not([disabled]):active { - transform: scale(0.95); -} - -a { - text-decoration: none; - color: inherit; -} - -a:hover{ - color: #df6b0e; -} - -/* -app -*/ - -.active { - color: #909090; -} - -button.primary { - padding: 8px 12px; - background: #F7861C; - box-shadow: 0px 3px 6px rgba(247, 134, 28, 0.36); - color: white; - font-size: 1.1em; - font-family: 'Montserrat Regular'; - text-transform: uppercase; -} - -button.btn-thin { - border: 1px solid; - border-color: #4D4D4D; - color: #4D4D4D; - background: rgb(255, 174, 41); - border-radius: 4px; - min-width: 200px; - margin: 12px 0; - padding: 6px; - font-size: 13px; -} - -.app-header { - padding: 6px 8px; -} - -.app-header h1 { - font-family: 'Montserrat Regular'; - text-transform: uppercase; - color: #AEAEAE; -} - -h2.page-subtitle { - font-family: 'Montserrat Regular'; - text-transform: uppercase; - color: #AEAEAE; - font-size: 1em; - margin: 12px; -} - -.app-primary { - -} - -.app-footer { - padding-bottom: 10px; - align-items: center; -} - -.identicon { - height: 46px; - width: 46px; - background-size: cover; - border-radius: 100%; - border: 3px solid gray; -} +/* initialize */ textarea.twelve-word-phrase { padding: 12px; @@ -156,32 +36,6 @@ textarea.twelve-word-phrase { resize: none; } -.network-indicator { - display: flex; - align-items: center; - font-size: 0.6em; - -} - -.network-name { - width: 5.2em; - line-height: 9px; - text-rendering: geometricPrecision; -} - -.check { - margin-left: 7px; - color: #F7861C; - flex: 1 0 auto; - display: flex; - justify-content: flex-end; -} -/* -app sections -*/ - -/* initialize */ - .initialize-screen hr { width: 60px; margin: 12px; @@ -259,9 +113,6 @@ app sections .unlock-screen input[type=password] { width: 260px; - /*height: 36px; - margin-bottom: 24px; - padding: 8px;*/ } .sizing-input{ @@ -293,20 +144,6 @@ app sections font-size: 1.2em; } -input.large-input, textarea.large-input { - /*margin-bottom: 24px;*/ - padding: 8px; -} - -input.large-input { - height: 36px; -} - -.letter-spacey { - letter-spacing: 0.1em; -} - - /* accounts */ @@ -403,8 +240,16 @@ input.large-input { /* account detail screen */ .account-detail-section { + display: flex; + flex-wrap: wrap; + overflow-y: auto; + flex-direction: inherit; +} +.grow-tenx { + flex-grow: 10; } + .name-label{ } @@ -421,6 +266,7 @@ input.large-input { height: 100%; visibility: hidden; } + .editing-label { display: flex; justify-content: flex-start; @@ -430,6 +276,7 @@ input.large-input { text-rendering: geometricPrecision; color: #F7861C; } + .name-label:hover .edit-text { visibility: visible; } @@ -447,12 +294,8 @@ input.large-input { /* Send Screen */ -.send-screen { - -} - .send-screen section { - margin: 8px 16px; + margin: 4px 16px; } .send-screen input { @@ -663,5 +506,5 @@ div.message-container > div:first-child { } .pop-hover:hover { - transform: scale(1.1); -} + transform: scale(1.1); +}
\ No newline at end of file diff --git a/ui/app/css/itcss/generic/index.scss b/ui/app/css/itcss/generic/index.scss new file mode 100644 index 000000000..a0ce312cc --- /dev/null +++ b/ui/app/css/itcss/generic/index.scss @@ -0,0 +1,64 @@ +/* + Generic + */ + +@import './reset.scss'; + +* { + box-sizing: border-box; +} + +html, body { + font-family: 'Montserrat Regular', Arial; + color: #4D4D4D; + font-weight: 300; + line-height: 1.4em; + background: #F7F7F7; + width: 100%; + height: 100%; + margin: 0; + padding: 0; +} + +html { + min-height: 500px; +} + +.app-root { + overflow: hidden; + position: relative +} + +.app-primary { + display: flex; +} + +input:focus, textarea:focus { + outline: none; +} + +#app-content { + overflow-x: hidden; + min-width: 357px; + height: 100%; + display: flex; + flex-direction: column; +} + +a { + text-decoration: none; + color: inherit; +} + +a:hover{ + color: #df6b0e; +} + +input.large-input, textarea.large-input { + /*margin-bottom: 24px;*/ + padding: 8px; +} + +input.large-input { + height: 36px; +} diff --git a/ui/app/css/reset.css b/ui/app/css/itcss/generic/reset.scss index 9ce89e8bc..fef74825d 100644 --- a/ui/app/css/reset.css +++ b/ui/app/css/itcss/generic/reset.scss @@ -45,4 +45,8 @@ q:before, q:after { table { border-collapse: collapse; border-spacing: 0; -}
\ No newline at end of file +} + +button { + border-style: none; +} diff --git a/ui/app/css/itcss/objects/index.scss b/ui/app/css/itcss/objects/index.scss new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ui/app/css/itcss/objects/index.scss diff --git a/ui/app/css/itcss/settings/index.scss b/ui/app/css/itcss/settings/index.scss new file mode 100644 index 000000000..446546d0c --- /dev/null +++ b/ui/app/css/itcss/settings/index.scss @@ -0,0 +1,2 @@ +@import './variables.scss'; +@import './typography.scss'; diff --git a/ui/app/css/fonts.css b/ui/app/css/itcss/settings/typography.scss index 3b9f581b9..2afaa26e1 100644 --- a/ui/app/css/fonts.css +++ b/ui/app/css/itcss/settings/typography.scss @@ -8,7 +8,6 @@ font-weight: normal; font-style: normal; font-size: 'small'; - } @font-face { @@ -23,14 +22,21 @@ font-family: 'Montserrat Light'; src: url('/fonts/Montserrat/Montserrat-Light.woff') format('woff'); src: url('/fonts/Montserrat/Montserrat-Light.ttf') format('truetype'); - font-weight: normal; - font-style: normal; + font-weight: normal; + font-style: normal; } @font-face { font-family: 'Montserrat UltraLight'; src: url('/fonts/Montserrat/Montserrat-UltraLight.woff') format('woff'); src: url('/fonts/Montserrat/Montserrat-UltraLight.ttf') format('truetype'); - font-weight: normal; - font-style: normal; + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'DIN OT'; + src: url('/fonts/DIN_OT/DINOT-2.otf') format('opentype'); + font-weight: normal; + font-style: normal; } diff --git a/ui/app/css/itcss/settings/variables.scss b/ui/app/css/itcss/settings/variables.scss new file mode 100644 index 000000000..6d105f2e3 --- /dev/null +++ b/ui/app/css/itcss/settings/variables.scss @@ -0,0 +1,13 @@ +/* + Variables + */ + +// Colors +$white-linen: #FAF6F0; // formerly 'faint orange (textfield shades)' +$rajah: #F5C26D; // formerly 'light orange (button shades)' +$buttercup: #F5A623; // formerly 'dark orange (text)' +$tundora: #4A4A4A; // formerly 'borders/font/any gray' +$gallery: #EFEFEF; +$alabaster: #F7F7F7; +$shark: #22232C; +$wild-sand: #F6F6F6;
\ No newline at end of file diff --git a/ui/app/css/itcss/tools/index.scss b/ui/app/css/itcss/tools/index.scss new file mode 100644 index 000000000..3634c839b --- /dev/null +++ b/ui/app/css/itcss/tools/index.scss @@ -0,0 +1,2 @@ +@import './utilities.scss'; + diff --git a/ui/app/css/lib.css b/ui/app/css/itcss/tools/utilities.scss index 910a24ee2..5e26ede64 100644 --- a/ui/app/css/lib.css +++ b/ui/app/css/itcss/tools/utilities.scss @@ -1,15 +1,38 @@ +/* + Utility Classes + */ + /* color */ .color-orange { - color: #F7861C; + color: #F7861C; // TODO: move to settings/variables } .color-forest { - color: #0A5448; + color: #0A5448; // TODO: move to settings/variables } /* lib */ +.full-size { + height: 100%; + width: 100%; +} + +.full-width { + width: 100%; +} + +.full-height { + height: 100%; +} + +.full-flex-height { + display: flex; + flex: 1 1 auto; + flex-direction: column; +} + .full-width { width: 100%; } @@ -232,6 +255,10 @@ hr.horizontal-line { align-items: center; } +.tabSection { + min-width: 350px; +} + .menu-icon { display: inline-block; height: 9px; @@ -266,3 +293,25 @@ hr.horizontal-line { margin-top: 20px; color: red; } + + +/* + Misc + */ + +// Move into component-level contextual 'active' state later +.letter-spacey { + letter-spacing: 0.1em; +} + +.active { + color: #909090; +} + +.check { + margin-left: 7px; + color: #F7861C; + flex: 1 0 auto; + display: flex; + justify-content: flex-end; +} diff --git a/ui/app/css/transitions.css b/ui/app/css/itcss/trumps/index.scss index 393a944f9..6b0c999be 100644 --- a/ui/app/css/transitions.css +++ b/ui/app/css/itcss/trumps/index.scss @@ -1,3 +1,10 @@ +/* + Trumps + */ + + +// Transitions + /* universal */ .app-primary .main-enter { position: absolute; @@ -22,6 +29,11 @@ transition: transform 300ms ease-in; } +.sidebar.from-left { + transform: translateX(-320px); + transition: transform 300ms ease-in; +} + /* loader transitions */ .loader-enter, .loader-leave-active { opacity: 0.0; @@ -39,4 +51,3 @@ .app-primary.from-left .main-enter:not(.main-enter-active) { transform: translateX(-360px); } - diff --git a/ui/app/info.js b/ui/app/info.js index cb2e41f5b..899841c83 100644 --- a/ui/app/info.js +++ b/ui/app/info.js @@ -20,7 +20,11 @@ InfoScreen.prototype.render = function () { const version = global.platform.getVersion() return ( - h('.flex-column.flex-grow', [ + h('.flex-column.flex-grow', { + style: { + maxWidth: '400px', + }, + }, [ // subtitle and nav h('.section-title.flex-row.flex-center', [ @@ -103,12 +107,7 @@ InfoScreen.prototype.render = function () { target: '_blank', }, 'Visit our Support Center'), ]), - h('div.fa.fa-github', [ - h('a.info', { - href: 'https://github.com/MetaMask/metamask-extension/issues/new', - target: '_blank', - }, 'Found a bug? Report it!'), - ]), + h('div', [ h('a', { href: 'https://metamask.io/', @@ -126,6 +125,7 @@ InfoScreen.prototype.render = function () { h('div.info', 'Visit our web site'), ]), ]), + h('div.fa.fa-slack', [ h('a.info', { href: 'http://slack.metamask.io', @@ -133,11 +133,13 @@ InfoScreen.prototype.render = function () { }, 'Join the conversation on Slack'), ]), - h('div.fa.fa-twitter', [ - h('a.info', { - href: 'https://twitter.com/metamask_io', - target: '_blank', - }, 'Follow us on Twitter'), + h('div', [ + h('.fa.fa-twitter', [ + h('a.info', { + href: 'https://twitter.com/metamask_io', + target: '_blank', + }, 'Follow us on Twitter'), + ]), ]), h('div.fa.fa-envelope', [ diff --git a/ui/app/keychains/hd/create-vault-complete.js b/ui/app/keychains/hd/create-vault-complete.js index a318a9b50..c32751fff 100644 --- a/ui/app/keychains/hd/create-vault-complete.js +++ b/ui/app/keychains/hd/create-vault-complete.js @@ -47,8 +47,6 @@ CreateVaultCompleteScreen.prototype.render = function () { h('div', { style: { - width: '360px', - height: '78px', fontSize: '1em', marginTop: '10px', textAlign: 'center', diff --git a/ui/app/keychains/hd/recover-seed/confirmation.js b/ui/app/keychains/hd/recover-seed/confirmation.js index 4ccbec9fc..4335186a5 100644 --- a/ui/app/keychains/hd/recover-seed/confirmation.js +++ b/ui/app/keychains/hd/recover-seed/confirmation.js @@ -23,7 +23,9 @@ RevealSeedConfirmation.prototype.render = function () { return ( - h('.initialize-screen.flex-column.flex-center.flex-grow', [ + h('.initialize-screen.flex-column.flex-center.flex-grow', { + style: { maxWidth: '420px' }, + }, [ h('h3.flex-center.text-transform-uppercase', { style: { @@ -61,7 +63,7 @@ RevealSeedConfirmation.prototype.render = function () { }, }), - h('.flex-row.flex-space-between', { + h('.flex-row.flex-start', { style: { marginTop: 30, width: '50%', @@ -74,6 +76,7 @@ RevealSeedConfirmation.prototype.render = function () { // submit h('button.primary', { + style: { marginLeft: '10px' }, onClick: this.revealSeedWords.bind(this), }, 'OK'), diff --git a/ui/app/main-container.js b/ui/app/main-container.js new file mode 100644 index 000000000..b00349c05 --- /dev/null +++ b/ui/app/main-container.js @@ -0,0 +1,72 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits +const TxView = require('./components/tx-view') +const WalletView = require('./components/wallet-view') +const SlideoutMenu = require('react-burger-menu').slide +const AccountAndTransactionDetails = require('./account-and-transaction-details') +const HDRestoreVaultScreen = require('./keychains/hd/restore-vault') +const ConfigScreen = require('./config') +const UnlockScreen = require('./unlock') + +module.exports = MainContainer + +inherits(MainContainer, Component) +function MainContainer () { + Component.call(this) +} + +MainContainer.prototype.render = function () { + // 3. summarize: + // switch statement goes inside MainContainer, + // or a method in renderPrimary + // - pass resulting h() to MainContainer + // - error checking in separate func + // - router in separate func + let contents = { + component: AccountAndTransactionDetails, + key: 'account-detail', + style: {}, + } + + if (this.props.isUnlocked === false) { + switch (this.props.currentViewName) { + case 'restoreVault': + log.debug('rendering restore vault screen') + contents = { + component: HDRestoreVaultScreen, + key: 'HDRestoreVaultScreen', + } + case 'config': + log.debug('rendering config screen from unlock screen.') + contents = { + component: ConfigScreen, + key: 'config', + } + default: + log.debug('rendering locked screen') + contents = { + component: UnlockScreen, + style: { + boxShadow: 'none', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + background: '#F7F7F7', + // must force 100%, because lock screen is full-width + width: '100%', + }, + key: 'locked', + } + } + } + + return h('div.main-container', { + style: contents.style, + }, [ + h(contents.component, { + key: contents.key, + }, []) + ]) +} + diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 2fcc9bfe0..bf1de4577 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -36,6 +36,7 @@ function reduceApp (state, action) { var appState = extend({ shouldClose: false, menuOpen: false, + sidebarOpen: false, currentView: seedWords ? seedConfView : defaultView, accountDetail: { subview: 'transactions', @@ -46,6 +47,16 @@ function reduceApp (state, action) { }, state.appState) switch (action.type) { + // sidebar methods + case actions.SIDEBAR_OPEN: + return extend(appState, { + sidebarOpen: true, + }) + + case actions.SIDEBAR_CLOSE: + return extend(appState, { + sidebarOpen: false, + }) // transition methods diff --git a/ui/app/send.js b/ui/app/send.js index a21a219eb..ab527019f 100644 --- a/ui/app/send.js +++ b/ui/app/send.js @@ -11,6 +11,9 @@ const isHex = require('./util').isHex const EthBalance = require('./components/eth-balance') const EnsInput = require('./components/ens-input') const ethUtil = require('ethereumjs-util') + +const ARAGON = '960b236A07cf122663c4303350609A66A7B288C0' + module.exports = connect(mapStateToProps)(SendTransactionScreen) function mapStateToProps (state) { @@ -56,172 +59,425 @@ SendTransactionScreen.prototype.render = function () { return ( - h('.send-screen.flex-column.flex-grow', [ - - // - // Sender Profile - // + h('div.flex-column.flex-grow', { + style: { + minWidth: '355px', // TODO: maxWidth TBD, use home.html + }, + }, [ - h('.account-data-subsection.flex-row.flex-grow', { + // Main Send token Card + h('div.send-screen.flex-column.flex-grow', { style: { - margin: '0 20px', - }, + marginLeft: '3.5%', + marginRight: '3.5%', + background: '#FFFFFF', // $background-white + boxShadow: '0 2px 4px 0 rgba(0,0,0,0.08)', + } }, [ + h('section.flex-center.flex-row', { + style: { + zIndex: 15, // $token-icon-z-index + marginTop: '-35px', + } + }, [ + h(Identicon, { + address: ARAGON, + diameter: 76, + }), + ]), - // header - identicon + nav - h('.flex-row.flex-space-between', { + h('h3.flex-center', { style: { - marginTop: '15px', + marginTop: '-18px', + fontSize: '16px', }, }, [ - // back button - h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', { - onClick: this.back.bind(this), + 'Send Tokens', + ]), + + h('h3.flex-center', { + style: { + textAlign: 'center', + fontSize: '12px', + }, + }, [ + 'Send Tokens to anyone with an Ethereum account', + ]), + + h('h3.flex-center', { + style: { + textAlign: 'center', + marginTop: '2px', + fontSize: '12px', + }, + }, [ + 'Your Aragon Token Balance is:', + ]), + + h('h3.flex-center', { + style: { + textAlign: 'center', + fontSize: '36px', + marginTop: '8px', + }, + }, [ + '2.34', + ]), + + h('h3.flex-center', { + style: { + textAlign: 'center', + fontSize: '12px', + marginTop: '4px', + }, + }, [ + 'ANT', + ]), + + // error message + props.error && h('span.error.flex-center', props.error), + + // 'to' field + h('section.flex-row.flex-center', { + style: { + fontSize: '12px', + }, + }, [ + h(EnsInput, { + name: 'address', + placeholder: 'Recipient Address', + onChange: this.recipientDidChange.bind(this), + network, + identities, + addressBook, }), + ]), - // large identicon - h('.identicon-wrapper.flex-column.flex-center.select-none', [ - h(Identicon, { - diameter: 62, - address: address, - }), + // 'amount' and send button + h('section.flex-column.flex-center', [ + h('div.flex-row.flex-center', { + style: { + fontSize: '12px', + width: '100%', + justifyContent: 'space-between', + } + },[ + h('span', { style: {} }, ['Amount']), + h('span', { style: {} }, ['Token <> USD']), ]), - // invisible place holder - h('i.fa.fa-users.fa-lg.invisible', { + h('input.large-input', { + name: 'amount', + placeholder: '0', + type: 'number', style: { - marginTop: '28px', + marginRight: '6px', + fontSize: '12px', + }, + dataset: { + persistentFormId: 'tx-amount', }, }), ]), - // account label + h('section.flex-column.flex-center', [ + h('div.flex-row.flex-center', { + style: { + fontSize: '12px', + width: '100%', + justifyContent: 'space-between', + } + },[ + h('span', { style: {} }, ['Gas Fee:']), + h('span', { style: { fontSize: '8px' } }, ['What\'s this?']), + ]), + + h('input.large-input', { + name: 'Gas Fee', + placeholder: '0', + type: 'number', + style: { + fontSize: '12px', + marginRight: '6px', + }, + // dataset: { + // persistentFormId: 'tx-amount', + // }, + }), + + ]), - h('.flex-column', { + h('section.flex-column.flex-center', { style: { - marginTop: '10px', - alignItems: 'flex-start', + marginBottom: '10px', }, }, [ - h('h2.font-medium.color-forest.flex-center', { + h('div.flex-row.flex-center', { style: { - paddingTop: '8px', - marginBottom: '8px', - }, - }, identity && identity.name), + fontSize: '12px', + width: '100%', + justifyContent: 'flex-start', + } + },[ + h('span', { style: {} }, ['Transaction Memo (optional)']), + ]), - // address and getter actions - h('.flex-row.flex-center', { + h('input.large-input', { + name: 'memo', + placeholder: '', + type: 'string', style: { - marginBottom: '8px', + marginRight: '6px', }, - }, [ + }), + ]), + ]), - h('div', { - style: { - lineHeight: '16px', - }, - }, addressSummary(address)), + // Buttons underneath card + h('section.flex-column.flex-center', [ - ]), + h('button.btn-light', { + onClick: this.onSubmit.bind(this), + style: { + marginTop: '8px', + width: '8em', + background: '#FFFFFF' + }, + }, 'Next'), - // balance - h('.flex-row.flex-center', [ + h('button.btn-light', { + onClick: this.back.bind(this), + style: { + background: '#F7F7F7', // $alabaster + border: 'none', + opacity: 1, + width: '8em', + }, + }, 'Cancel'), + ]), + ]) - h(EthBalance, { - value: account && account.balance, - conversionRate, - currentCurrency, - }), + ) +} - ]), - ]), - ]), +// WIP - hyperscript for renderSendToken - hook up later +SendTransactionScreen.prototype.renderSendToken = function () { + this.persistentFormParentId = 'send-tx-form' + + const props = this.props + const { + address, + account, + identity, + network, + identities, + addressBook, + conversionRate, + currentCurrency, + } = props + + return ( - // - // Required Fields - // + h('div.flex-column.flex-grow', { + style: { + minWidth: '355px', // TODO: maxWidth TBD, use home.html + }, + }, [ - h('h3.flex-center.text-transform-uppercase', { + // Main Send token Card + h('div.send-screen.flex-column.flex-grow', { style: { - background: '#EBEBEB', - color: '#AEAEAE', - marginTop: '15px', - marginBottom: '16px', - }, + marginLeft: '3.5%', + marginRight: '3.5%', + background: '#FFFFFF', // $background-white + boxShadow: '0 2px 4px 0 rgba(0,0,0,0.08)', + } }, [ - 'Send Transaction', - ]), + h('section.flex-center.flex-row', { + style: { + zIndex: 15, // $token-icon-z-index + marginTop: '-35px', + } + }, [ + h(Identicon, { + address: ARAGON, + diameter: 76, + }), + ]), - // error message - props.error && h('span.error.flex-center', props.error), - - // 'to' field - h('section.flex-row.flex-center', [ - h(EnsInput, { - name: 'address', - placeholder: 'Recipient Address', - onChange: this.recipientDidChange.bind(this), - network, - identities, - addressBook, - }), - ]), + h('h3.flex-center', { + style: { + marginTop: '-18px', + fontSize: '16px', + }, + }, [ + 'Send Tokens', + ]), - // 'amount' and send button - h('section.flex-row.flex-center', [ + h('h3.flex-center', { + style: { + textAlign: 'center', + fontSize: '12px', + }, + }, [ + 'Send Tokens to anyone with an Ethereum account', + ]), - h('input.large-input', { - name: 'amount', - placeholder: 'Amount', - type: 'number', + h('h3.flex-center', { style: { - marginRight: '6px', + textAlign: 'center', + marginTop: '2px', + fontSize: '12px', }, - dataset: { - persistentFormId: 'tx-amount', + }, [ + 'Your Aragon Token Balance is:', + ]), + + h('h3.flex-center', { + style: { + textAlign: 'center', + fontSize: '36px', + marginTop: '8px', }, - }), + }, [ + '2.34', + ]), - h('button.primary', { - onClick: this.onSubmit.bind(this), + h('h3.flex-center', { style: { - textTransform: 'uppercase', + textAlign: 'center', + fontSize: '12px', + marginTop: '4px', }, - }, 'Next'), + }, [ + 'ANT', + ]), - ]), + // error message + props.error && h('span.error.flex-center', props.error), - // - // Optional Fields - // - h('h3.flex-center.text-transform-uppercase', { - style: { - background: '#EBEBEB', - color: '#AEAEAE', - marginTop: '16px', - marginBottom: '16px', - }, - }, [ - 'Transaction Data (optional)', + // 'to' field + h('section.flex-row.flex-center', { + style: { + fontSize: '12px', + }, + }, [ + h(EnsInput, { + name: 'address', + placeholder: 'Recipient Address', + onChange: this.recipientDidChange.bind(this), + network, + identities, + addressBook, + }), + ]), + + // 'amount' and send button + h('section.flex-column.flex-center', [ + h('div.flex-row.flex-center', { + style: { + fontSize: '12px', + width: '100%', + justifyContent: 'space-between', + } + },[ + h('span', { style: {} }, ['Amount']), + h('span', { style: {} }, ['Token <> USD']), + ]), + + h('input.large-input', { + name: 'amount', + placeholder: '0', + type: 'number', + style: { + marginRight: '6px', + fontSize: '12px', + }, + dataset: { + persistentFormId: 'tx-amount', + }, + }), + + ]), + + h('section.flex-column.flex-center', [ + h('div.flex-row.flex-center', { + style: { + fontSize: '12px', + width: '100%', + justifyContent: 'space-between', + } + },[ + h('span', { style: {} }, ['Gas Fee:']), + h('span', { style: { fontSize: '8px' } }, ['What\'s this?']), + ]), + + h('input.large-input', { + name: 'Gas Fee', + placeholder: '0', + type: 'number', + style: { + fontSize: '12px', + marginRight: '6px', + }, + // dataset: { + // persistentFormId: 'tx-amount', + // }, + }), + + ]), + + h('section.flex-column.flex-center', { + style: { + marginBottom: '10px', + }, + }, [ + h('div.flex-row.flex-center', { + style: { + fontSize: '12px', + width: '100%', + justifyContent: 'flex-start', + } + },[ + h('span', { style: {} }, ['Transaction Memo (optional)']), + ]), + + h('input.large-input', { + name: 'memo', + placeholder: '', + type: 'string', + style: { + marginRight: '6px', + }, + }), + ]), ]), - // 'data' field + // Buttons underneath card h('section.flex-column.flex-center', [ - h('input.large-input', { - name: 'txData', - placeholder: '0x01234', + + h('button.btn-light', { + onClick: this.onSubmit.bind(this), style: { - width: '100%', - resize: 'none', + marginTop: '8px', + width: '8em', + background: '#FFFFFF' }, - dataset: { - persistentFormId: 'tx-data', + }, 'Next'), + + h('button.btn-light', { + onClick: this.back.bind(this), + style: { + background: '#F7F7F7', // $alabaster + border: 'none', + opacity: 1, + width: '8em', }, - }), + }, 'Cancel'), ]), ]) + ) } @@ -248,7 +504,11 @@ SendTransactionScreen.prototype.onSubmit = function () { const nickname = state.nickname || ' ' const input = document.querySelector('input[name="amount"]').value const value = util.normalizeEthStringToWei(input) - const txData = document.querySelector('input[name="txData"]').value + // TODO: check with team on whether txData is removed completely. + const txData = false; + // Must replace with memo data. + // const txData = document.querySelector('input[name="txData"]').value + const balance = this.props.balance let message @@ -267,7 +527,7 @@ SendTransactionScreen.prototype.onSubmit = function () { return this.props.dispatch(actions.displayWarning(message)) } - if (!isHex(ethUtil.stripHexPrefix(txData)) && txData) { + if (txData && !isHex(ethUtil.stripHexPrefix(txData))) { message = 'Transaction data must be hex string.' return this.props.dispatch(actions.displayWarning(message)) } diff --git a/ui/app/unlock.js b/ui/app/unlock.js index 1aee3c5d0..08235f0ea 100644 --- a/ui/app/unlock.js +++ b/ui/app/unlock.js @@ -46,7 +46,7 @@ UnlockScreen.prototype.render = function () { id: 'password-box', placeholder: 'enter password', style: { - + background: 'white', }, onKeyPress: this.onKeyPress.bind(this), onInput: this.inputChanged.bind(this), @@ -4,11 +4,7 @@ const path = require('path') module.exports = bundleCss var cssFiles = { - 'fonts.css': fs.readFileSync(path.join(__dirname, '/app/css/fonts.css'), 'utf8'), - 'reset.css': fs.readFileSync(path.join(__dirname, '/app/css/reset.css'), 'utf8'), - 'lib.css': fs.readFileSync(path.join(__dirname, '/app/css/lib.css'), 'utf8'), - 'index.css': fs.readFileSync(path.join(__dirname, '/app/css/index.css'), 'utf8'), - 'transitions.css': fs.readFileSync(path.join(__dirname, '/app/css/transitions.css'), 'utf8'), + 'index.css': fs.readFileSync(path.join(__dirname, '/app/css/output/index.css'), 'utf8'), 'react-tooltip-component.css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-tooltip-component', 'dist', 'react-tooltip-component.css'), 'utf8'), 'react-css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-select', 'dist', 'react-select.css'), 'utf8'), } diff --git a/ui/lib/tx-helper.js b/ui/lib/tx-helper.js index afc62e7b6..5def23e51 100644 --- a/ui/lib/tx-helper.js +++ b/ui/lib/tx-helper.js @@ -18,4 +18,3 @@ module.exports = function (unapprovedTxs, unapprovedMsgs, personalMsgs, network) return allValues } - |