Add Add Token UI; Add Fuzzy search for tokens

This commit is contained in:
Chi Kei Chan 2017-09-19 21:18:36 -07:00
parent e7f1fc4436
commit 0204aa2001
5 changed files with 467 additions and 149 deletions

View File

@ -74,7 +74,7 @@
"end-of-stream": "^1.1.0",
"ensnare": "^1.0.0",
"eth-bin-to-ops": "^1.0.1",
"eth-contract-metadata": "^1.1.4",
"eth-contract-metadata": "^1.1.5",
"eth-hd-keyring": "^1.1.1",
"eth-json-rpc-filters": "^1.1.0",
"eth-phishing-detect": "^1.1.4",
@ -93,6 +93,7 @@
"extensionizer": "^1.0.0",
"fast-json-patch": "^2.0.4",
"fast-levenshtein": "^2.0.6",
"fuse.js": "^3.1.0",
"gulp": "github:gulpjs/gulp#4.0",
"gulp-autoprefixer": "^4.0.0",
"gulp-eslint": "^4.0.0",
@ -212,8 +213,8 @@
"react-addons-test-utils": "^15.5.1",
"react-test-renderer": "^15.5.4",
"react-testutils-additions": "^15.2.0",
"stylelint-config-standard": "^17.0.0",
"sinon": "^3.2.0",
"stylelint-config-standard": "^17.0.0",
"tape": "^4.5.1",
"testem": "^1.10.3",
"uglifyify": "^4.0.2",

View File

@ -2,8 +2,20 @@ 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 Tooltip = require('./components/tooltip.js')
const Fuse = require('fuse.js')
const contractMap = require('eth-contract-metadata')
const contractList = Object.entries(contractMap).map(([ _, tokenData]) => tokenData)
const fuse = new Fuse(contractList, {
shouldSort: true,
threshold: 0.3,
location: 0,
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 1,
keys: ['address', 'name', 'symbol'],
})
// const actions = require('./actions')
// const Tooltip = require('./components/tooltip.js')
const ethUtil = require('ethereumjs-util')
@ -24,146 +36,232 @@ function mapStateToProps (state) {
inherits(AddTokenScreen, Component)
function AddTokenScreen () {
this.state = {
warning: null,
address: null,
symbol: 'TOKEN',
decimals: 18,
// warning: null,
// address: null,
// symbol: 'TOKEN',
// decimals: 18,
searchQuery: '',
isCollapsed: true,
}
Component.call(this)
}
AddTokenScreen.prototype.render = function () {
const state = this.state
const props = this.props
const { warning, symbol, decimals } = state
return (
h('.flex-column.flex-grow', [
// subtitle and nav
h('.section-title.flex-row.flex-center', [
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
onClick: (event) => {
props.dispatch(actions.goHome())
},
}),
h('h2.page-subtitle', 'Add Token'),
AddTokenScreen.prototype.renderCustomForm = function () {
return !this.state.isCollapsed && (
h('div.add-token__add-custom-form', [
h('div.add-token__add-custom-field', [
h('div.add-token__add-custom-label', 'Token Address'),
h('input.add-token__add-custom-input', { type: 'text' }),
]),
h('.error', {
style: {
display: warning ? 'block' : 'none',
padding: '0 20px',
textAlign: 'center',
},
}, warning),
// conf view
h('.flex-column.flex-justify-center.flex-grow.select-none', [
h('.flex-space-around', {
style: {
padding: '20px',
},
}, [
h('div', [
h(Tooltip, {
position: 'top',
title: 'The contract of the actual token contract. Click for more info.',
}, [
h('a', {
style: { fontWeight: 'bold', paddingRight: '10px'},
href: 'https://consensyssupport.happyfox.com/staff/kb/article/24-what-is-a-token-contract-address',
target: '_blank',
}, [
h('span', 'Token Contract Address '),
h('i.fa.fa-question-circle'),
]),
]),
]),
h('section.flex-row.flex-center', [
h('input#token-address', {
name: 'address',
placeholder: 'Token Contract Address',
onChange: this.tokenAddressDidChange.bind(this),
style: {
width: 'inherit',
flex: '1 0 auto',
height: '30px',
margin: '8px',
},
}),
]),
h('div', [
h('span', {
style: { fontWeight: 'bold', paddingRight: '10px'},
}, 'Token Symbol'),
]),
h('div', { style: {display: 'flex'} }, [
h('input#token_symbol', {
placeholder: `Like "ETH"`,
value: symbol,
style: {
width: 'inherit',
flex: '1 0 auto',
height: '30px',
margin: '8px',
},
onChange: (event) => {
var element = event.target
var symbol = element.value
this.setState({ symbol })
},
}),
]),
h('div', [
h('span', {
style: { fontWeight: 'bold', paddingRight: '10px'},
}, 'Decimals of Precision'),
]),
h('div', { style: {display: 'flex'} }, [
h('input#token_decimals', {
value: decimals,
type: 'number',
min: 0,
max: 36,
style: {
width: 'inherit',
flex: '1 0 auto',
height: '30px',
margin: '8px',
},
onChange: (event) => {
var element = event.target
var decimals = element.value.trim()
this.setState({ decimals })
},
}),
]),
h('button', {
style: {
alignSelf: 'center',
},
onClick: (event) => {
const valid = this.validateInputs()
if (!valid) return
const { address, symbol, decimals } = this.state
this.props.dispatch(actions.addToken(address.trim(), symbol.trim(), decimals))
},
}, 'Add'),
]),
h('div.add-token__add-custom-field', [
h('div.add-token__add-custom-label', 'Token Symbol'),
h('input.add-token__add-custom-input', { type: 'text', disabled: true }),
]),
h('div.add-token__add-custom-field', [
h('div.add-token__add-custom-label', 'Decimals of Precision'),
h('input.add-token__add-custom-input', { type: 'text', disabled: true }),
]),
])
)
}
AddTokenScreen.prototype.renderTokenList = function () {
const { searchQuery = '' } = this.state
const results = searchQuery
? fuse.search(searchQuery) || []
: contractList
return Array(6).fill(undefined)
.map((_, i) => {
const { logo, symbol, name } = results[i] || {}
console.log({ i, logo, symbol, name })
return Boolean(logo || symbol || name) && (
h('div.add-token__token-wrapper', [
h('div.add-token__token-icon', {
style: {
backgroundImage: `url(images/contract/${logo})`,
},
}),
h('div.add-token__token-data', [
h('div.add-token__token-symbol', symbol),
h('div.add-token__token-name', name),
]),
])
)
})
}
AddTokenScreen.prototype.render = function () {
const { isCollapsed } = this.state
return (
h('div.add-token', [
h('div.add-token__wrapper', [
h('div.add-token__title-container', [
h('div.add-token__title', 'Add Token'),
h('div.add-token__description', 'Keep track of the tokens youve bought with your MetaMask account. If you bought tokens using a different account, those tokens will not appear here.'),
h('div.add-token__description', 'Search for tokens or select from our list of popular tokens.'),
]),
h('div.add-token__content-container', [
h('div.add-token__input-container', [
h('input.add-token__input', {
type: 'text',
placeholder: 'Search',
onChange: e => this.setState({ searchQuery: e.target.value }),
}),
]),
h(
'div.add-token__token-icons-container',
this.renderTokenList(),
),
]),
h('div.add-token__footers', [
h('div.add-token__add-custom', {
onClick: () => this.setState({ isCollapsed: !isCollapsed }),
}, 'Add custom token'),
this.renderCustomForm(),
]),
]),
h('div.add-token__buttons', [
h('button.btn-secondary', 'Next'),
h('button.btn-tertiary', 'Cancel'),
]),
])
)
}
// AddTokenScreen.prototype.render = function () {
// const state = this.state
// const props = this.props
// const { warning, symbol, decimals } = state
// return (
// h('.flex-column.flex-grow', [
// // subtitle and nav
// h('.section-title.flex-row.flex-center', [
// h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
// onClick: (event) => {
// props.dispatch(actions.goHome())
// },
// }),
// h('h2.page-subtitle', 'Add Token'),
// ]),
// h('.error', {
// style: {
// display: warning ? 'block' : 'none',
// padding: '0 20px',
// textAlign: 'center',
// },
// }, warning),
// // conf view
// h('.flex-column.flex-justify-center.flex-grow.select-none', [
// h('.flex-space-around', {
// style: {
// padding: '20px',
// },
// }, [
// h('div', [
// h(Tooltip, {
// position: 'top',
// title: 'The contract of the actual token contract. Click for more info.',
// }, [
// h('a', {
// style: { fontWeight: 'bold', paddingRight: '10px'},
// href: 'https://consensyssupport.happyfox.com/staff/kb/article/24-what-is-a-token-contract-address',
// target: '_blank',
// }, [
// h('span', 'Token Contract Address '),
// h('i.fa.fa-question-circle'),
// ]),
// ]),
// ]),
// h('section.flex-row.flex-center', [
// h('input#token-address', {
// name: 'address',
// placeholder: 'Token Contract Address',
// onChange: this.tokenAddressDidChange.bind(this),
// style: {
// width: 'inherit',
// flex: '1 0 auto',
// height: '30px',
// margin: '8px',
// },
// }),
// ]),
// h('div', [
// h('span', {
// style: { fontWeight: 'bold', paddingRight: '10px'},
// }, 'Token Symbol'),
// ]),
// h('div', { style: {display: 'flex'} }, [
// h('input#token_symbol', {
// placeholder: `Like "ETH"`,
// value: symbol,
// style: {
// width: 'inherit',
// flex: '1 0 auto',
// height: '30px',
// margin: '8px',
// },
// onChange: (event) => {
// var element = event.target
// var symbol = element.value
// this.setState({ symbol })
// },
// }),
// ]),
// h('div', [
// h('span', {
// style: { fontWeight: 'bold', paddingRight: '10px'},
// }, 'Decimals of Precision'),
// ]),
// h('div', { style: {display: 'flex'} }, [
// h('input#token_decimals', {
// value: decimals,
// type: 'number',
// min: 0,
// max: 36,
// style: {
// width: 'inherit',
// flex: '1 0 auto',
// height: '30px',
// margin: '8px',
// },
// onChange: (event) => {
// var element = event.target
// var decimals = element.value.trim()
// this.setState({ decimals })
// },
// }),
// ]),
// h('button', {
// style: {
// alignSelf: 'center',
// },
// onClick: (event) => {
// const valid = this.validateInputs()
// if (!valid) return
// const { address, symbol, decimals } = this.state
// this.props.dispatch(actions.addToken(address.trim(), symbol.trim(), decimals))
// },
// }, 'Add'),
// ]),
// ]),
// ])
// )
// }
AddTokenScreen.prototype.componentWillMount = function () {
if (typeof global.ethereumProvider === 'undefined') return

View File

@ -0,0 +1,173 @@
.add-token {
width: 498px;
display: flex;
flex-flow: column nowrap;
align-items: center;
position: relative;
top: -36px;
z-index: 12;
font-family: 'DIN Next Light';
@media screen and (max-width: $break-small) {
top: 0;
width: 100%;
&__wrapper {
box-shadow: none !important;
}
&__footers {
border-bottom: 1px solid $gallery;
}
}
&__wrapper {
background-color: $white;
box-shadow: 0 2px 4px 0 rgba($black, .08);
display: flex;
flex-flow: column nowrap;
align-items: center;
flex: 0 0 auto;
}
&__title-container {
display: flex;
flex-flow: column nowrap;
align-items: center;
padding: 30px 60px 12px;
border-bottom: 1px solid $gallery;
flex: 0 0 auto;
}
&__title {
color: $scorpion;
font-size: 20px;
line-height: 26px;
text-align: center;
font-weight: 600;
margin-bottom: 12px;
}
&__description {
text-align: center;
}
&__description + &__description {
margin-top: 24px;
}
&__content-container {
width: 100%;
border-bottom: 1px solid $gallery;
}
&__input-container {
padding: 11px 0;
width: 263px;
margin: 0 auto;
}
&__input {
width: 100%;
border: 2px solid $gallery;
border-radius: 4px;
padding: 5px 15px;
font-size: 14px;
line-height: 19px;
&::placeholder {
color: $silver;
}
}
&__footers {
width: 100%;
}
&__add-custom {
color: $scorpion;
font-size: 18px;
line-height: 24px;
text-align: center;
padding: 11px 0 19px;
font-weight: 600;
cursor: pointer;
}
&__add-custom-form {
display: flex;
flex-flow: column nowrap;
margin: 8px 0 51px;
}
&__add-custom-field {
width: 290px;
margin: 0 auto;
}
&__add-custom-label {
font-size: 16px;
line-height: 21px;
margin-bottom: 8px;
}
&__add-custom-input {
width: 100%;
border: 1px solid $silver;
padding: 5px 15px;
font-size: 14px;
line-height: 19px;
&::placeholder {
color: $silver;
}
}
&__add-custom-field + &__add-custom-field {
margin-top: 21px;
}
&__buttons {
display: flex;
flex-flow: column nowrap;
margin: 30px 0 51px;
flex: 0 0 auto;
}
&__token-icons-container {
display: flex;
flex-flow: row wrap;
}
&__token-wrapper {
display: flex;
flex-flow: row nowrap;
flex: 0 0 50%;
align-items: center;
padding: 24px 0 24px 24px;
}
&__token-name {
font-size: 14px;
line-height: 19px;
}
&__token-symbol {
font-size: 22px;
line-height: 29px;
font-weight: 600;
}
&__token-icon {
width: 60px;
height: 60px;
background-repeat: no-repeat;
background-size: contain;
background-position: center;
border-radius: 50%;
background-color: $white;
box-shadow: 0 2px 4px 0 rgba($black, .24);
margin-right: 12px;
flex: 0 0 auto;
}
}

View File

@ -27,3 +27,5 @@
@import './sections.scss';
@import './token-list.scss';
@import './add-token.scss';

View File

@ -1950,6 +1950,12 @@ cli-width@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
cli@0.4.3:
version "0.4.3"
resolved "https://registry.yarnpkg.com/cli/-/cli-0.4.3.tgz#e6819c8d5faa957f64f98f66a8506268c1d1f17d"
dependencies:
glob ">= 3.1.4"
client-sw-ready-event@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/client-sw-ready-event/-/client-sw-ready-event-3.3.0.tgz#988d1045562b0c228e33d9247a6dd3ed7b276fe3"
@ -2063,7 +2069,7 @@ colors@1.0.3, colors@1.0.x:
version "1.0.3"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
colors@^1.1.0, colors@^1.1.2:
colors@>=0.6.x, colors@^1.1.0, colors@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
@ -3370,7 +3376,7 @@ eth-block-tracker@^2.0.1, eth-block-tracker@^2.1.2:
pify "^2.3.0"
tape "^4.6.3"
eth-contract-metadata@^1.1.4:
eth-contract-metadata@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/eth-contract-metadata/-/eth-contract-metadata-1.1.5.tgz#301f51b0460b8dd044997dc05870751fb7f4cfcb"
@ -4211,6 +4217,20 @@ functional-red-black-tree@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
fuse.js@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-3.1.0.tgz#9062146c471552189b0f678b4f5a155731ae3b3c"
fuse@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/fuse/-/fuse-0.4.0.tgz#2c38eaf888abb0a9ba7960cfe3339d1f3f53f6e6"
dependencies:
colors ">=0.6.x"
jshint "0.9.x"
optimist ">=0.3.5"
uglify-js ">=2.2.x"
underscore ">=1.4.x"
gather-stream@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/gather-stream/-/gather-stream-1.0.0.tgz#b33994af457a8115700d410f317733cbe7a0904b"
@ -4346,17 +4366,7 @@ glob@7.1.1:
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^5.0.15, glob@^5.0.3, glob@~5.0.0:
version "5.0.15"
resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1"
dependencies:
inflight "^1.0.4"
inherits "2"
minimatch "2 || 3"
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.0.0, glob@^7.0.3, glob@^7.0.4, glob@^7.0.5, glob@^7.0.6, glob@^7.1.0, glob@^7.1.1, glob@^7.1.2, glob@~7.1.1, glob@~7.1.2:
"glob@>= 3.1.4", glob@^7.0.0, glob@^7.0.3, glob@^7.0.4, glob@^7.0.5, glob@^7.0.6, glob@^7.1.0, glob@^7.1.1, glob@^7.1.2, glob@~7.1.1, glob@~7.1.2:
version "7.1.2"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
dependencies:
@ -4367,6 +4377,16 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.0.4, glob@^7.0.5, glob@^7.0.6, glob@^7.1.0, gl
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^5.0.15, glob@^5.0.3, glob@~5.0.0:
version "5.0.15"
resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1"
dependencies:
inflight "^1.0.4"
inherits "2"
minimatch "2 || 3"
once "^1.3.0"
path-is-absolute "^1.0.0"
global-modules@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-0.2.3.tgz#ea5a3bed42c6d6ce995a4f8a1269b5dae223828d"
@ -5521,6 +5541,13 @@ jshint-stylish@~2.2.1:
string-length "^1.0.0"
text-table "^0.2.0"
jshint@0.9.x:
version "0.9.1"
resolved "https://registry.yarnpkg.com/jshint/-/jshint-0.9.1.tgz#ff32ec7f09f84001f7498eeafd63c9e4fbb2dc0e"
dependencies:
cli "0.4.3"
minimatch "0.0.x"
jsmin@1.x:
version "1.0.1"
resolved "https://registry.yarnpkg.com/jsmin/-/jsmin-1.0.1.tgz#e7bd0dcd6496c3bf4863235bf461a3d98aa3b98c"
@ -6236,6 +6263,10 @@ lru-cache@^4.0.1:
pseudomap "^1.0.2"
yallist "^2.1.2"
lru-cache@~1.0.2:
version "1.0.6"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-1.0.6.tgz#aa50f97047422ac72543bda177a9c9d018d98452"
lru-queue@0.1:
version "0.1.0"
resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3"
@ -6487,6 +6518,12 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
minimatch@0.0.x:
version "0.0.5"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.0.5.tgz#96bb490bbd3ba6836bbfac111adf75301b1584de"
dependencies:
lru-cache "~1.0.2"
"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
@ -7074,7 +7111,7 @@ opener@^1.3.0:
version "1.4.3"
resolved "https://registry.yarnpkg.com/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8"
optimist@^0.6.1:
optimist@>=0.3.5, optimist@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
dependencies:
@ -9697,6 +9734,13 @@ uglify-js@1.x:
version "1.3.5"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-1.3.5.tgz#4b5bfff9186effbaa888e4c9e94bd9fc4c94929d"
uglify-js@>=2.2.x:
version "3.1.1"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.1.1.tgz#e7144307281a1bc38a9a20715090b546c9f44791"
dependencies:
commander "~2.11.0"
source-map "~0.5.1"
uglify-js@^2.6, uglify-js@^2.8.27:
version "2.8.29"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd"
@ -9736,7 +9780,7 @@ unc-path-regex@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa"
underscore@>=1.8.3, underscore@^1.6.0:
underscore@>=1.4.x, underscore@>=1.8.3, underscore@^1.6.0:
version "1.8.3"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"