From 09ddcfc7356f2bc115289529e7b9d1c4847391f9 Mon Sep 17 00:00:00 2001 From: ryanml Date: Wed, 12 Jun 2019 21:49:40 -0700 Subject: [PATCH] Fixes brave/brave-browser#4773 - adds BAT token by default to accounts --- brave/app/scripts/controllers/preferences.js | 16 +++ brave/app/scripts/metamask-controller.js | 14 +++ brave/gulp.js | 83 +++++++++++++ .../app/dropdowns/components/menu.js | 30 +++++ .../app/dropdowns/token-menu-dropdown.js | 74 ++++++++++++ brave/ui/app/ducks/metamask/metamask.js | 12 ++ brave/ui/app/pages/home/home.component.js | 24 ++++ brave/ui/app/pages/home/home.container.js | 32 +++++ brave/ui/app/pages/home/index.js | 1 + brave/ui/app/pages/routes/index.js | 112 ++++++++++++++++++ brave/ui/app/store/actions.js | 40 +++++++ brave/ui/app/store/bat-token.js | 10 ++ gulpfile.js | 28 +++-- 13 files changed, 468 insertions(+), 8 deletions(-) create mode 100644 brave/app/scripts/controllers/preferences.js create mode 100644 brave/app/scripts/metamask-controller.js create mode 100644 brave/gulp.js create mode 100644 brave/ui/app/components/app/dropdowns/components/menu.js create mode 100644 brave/ui/app/components/app/dropdowns/token-menu-dropdown.js create mode 100644 brave/ui/app/ducks/metamask/metamask.js create mode 100644 brave/ui/app/pages/home/home.component.js create mode 100644 brave/ui/app/pages/home/home.container.js create mode 100644 brave/ui/app/pages/home/index.js create mode 100644 brave/ui/app/pages/routes/index.js create mode 100644 brave/ui/app/store/actions.js create mode 100644 brave/ui/app/store/bat-token.js diff --git a/brave/app/scripts/controllers/preferences.js b/brave/app/scripts/controllers/preferences.js new file mode 100644 index 0000000000..6cb18b3d22 --- /dev/null +++ b/brave/app/scripts/controllers/preferences.js @@ -0,0 +1,16 @@ +const PreferencesController = require('../../../../app/scripts/controllers/preferences') + +module.exports = class BravePreferencesController extends PreferencesController { + constructor (opts = {}) { + if (opts.initState === undefined) { + opts['initState'] = {} + } + opts.initState.batTokenAdded = false + super(opts) + } + + setBatTokenAdded () { + this.store.updateState({ batTokenAdded: true }) + return Promise.resolve(true) + } +} diff --git a/brave/app/scripts/metamask-controller.js b/brave/app/scripts/metamask-controller.js new file mode 100644 index 0000000000..8676d48f0d --- /dev/null +++ b/brave/app/scripts/metamask-controller.js @@ -0,0 +1,14 @@ +const MetamaskController = require('../../../app/scripts/metamask-controller') +const nodeify = require('../../../app/scripts/lib/nodeify') + +module.exports = class BraveController extends MetamaskController { + constructor (opts) { + super(opts) + } + + getApi () { + const api = super.getApi() + api.setBatTokenAdded = nodeify(this.preferencesController.setBatTokenAdded, this.preferencesController) + return api + } +} diff --git a/brave/gulp.js b/brave/gulp.js new file mode 100644 index 0000000000..c06a628195 --- /dev/null +++ b/brave/gulp.js @@ -0,0 +1,83 @@ +const gulp = require('gulp') +const replace = require('gulp-replace') + +/* + * Brave + */ + +const overrideDirs = [ + 'ui/app/**/*', + 'app/scripts/**/*' +] + +const bravePrefix = '~/brave/' + +/* + * ToDo(ryanml) - Write a method to convert simple paths to a Regex obj + * then a simple mapping can be created for these replacements. + * Ex (psuedo-ish): + * const replacements = [ + * { + * target: 'actions', + * replace: 'ui/app/store/actions' + * } + * ] + * + * replacements.map((item) => { + * .pipe( + * replace(convertToRegex(item.target), fmtPath(item.replace)) + * ) + * }) + */ +module.exports = function () { + return gulp.src(overrideDirs) + .pipe( + replace( + /\'(.*)\/home\'/gm, + `'${bravePrefix}ui/app/pages/home'` + ) + ) + .pipe( + replace( + /\'\.\/routes\'/gm, + `'${bravePrefix}ui/app/pages/routes'` + ) + ) + .pipe( + replace( + /\'(.*)\/actions\'/gm, + `'${bravePrefix}ui/app/store/actions'` + ) + ) + .pipe( + replace( + /\'(.*)\/preferences\'/gm, + `'${bravePrefix}app/scripts/controllers/preferences'` + ) + ) + .pipe( + replace( + /\'(.*)\/metamask-controller\'/gm, + `'${bravePrefix}app/scripts/metamask-controller'` + ) + ) + .pipe( + replace( + /\'(.*)\/metamask\/metamask\'/gm, + `'${bravePrefix}ui/app/ducks/metamask/metamask'` + ) + ) + .pipe( + replace( + /\'(.*)\/components\/menu\'/gm, + `'${bravePrefix}ui/app/components/app/dropdowns/components/menu'` + ) + ) + .pipe( + replace( + /\'(.*)\/token-menu-dropdown\.js\'/gm, + `'${bravePrefix}ui/app/components/app/dropdowns/token-menu-dropdown'` + ) + ) + .pipe(gulp.dest(file => file.base)) +} diff --git a/brave/ui/app/components/app/dropdowns/components/menu.js b/brave/ui/app/components/app/dropdowns/components/menu.js new file mode 100644 index 0000000000..36316c83f3 --- /dev/null +++ b/brave/ui/app/components/app/dropdowns/components/menu.js @@ -0,0 +1,30 @@ +const h = require('react-hyperscript') + +import { Menu, Item, Divider, CloseArea } from '../../../../../../../ui/app/components/app/dropdowns/components/menu' + +Item.prototype.render = function () { + const { + icon, + children, + text, + className = '', + onClick, + isShowing + } = this.props + + if (isShowing === false) { + return h('noscript') + } + + const itemClassName = `menu__item ${className} ${onClick ? 'menu__item--clickable' : ''}` + const iconComponent = icon ? h('div.menu__item__icon', [icon]) : null + const textComponent = text ? h('div.menu__item__text', text) : null + + return children + ? h('div', { className: itemClassName, onClick }, children) + : h('div.menu__item', { className: itemClassName, onClick }, [ iconComponent, textComponent ] + .filter(d => Boolean(d)) + ) +} + +module.exports = { Menu, Item, Divider, CloseArea } \ No newline at end of file diff --git a/brave/ui/app/components/app/dropdowns/token-menu-dropdown.js b/brave/ui/app/components/app/dropdowns/token-menu-dropdown.js new file mode 100644 index 0000000000..2e60d497b8 --- /dev/null +++ b/brave/ui/app/components/app/dropdowns/token-menu-dropdown.js @@ -0,0 +1,74 @@ +const Component = require('react').Component +const PropTypes = require('prop-types') +const h = require('react-hyperscript') +const inherits = require('util').inherits +const connect = require('react-redux').connect +const genAccountLink = require('etherscan-link').createAccountLink +const { Menu, Item, CloseArea } = require('./components/menu') + +import actions from '../../../store/actions' + +function mapStateToProps(state) { + return { + network: state.metamask.network, + } +} + +function mapDispatchToProps(dispatch) { + return { + showHideTokenConfirmationModal: (token) => { + dispatch(actions.showModal({ + name: 'HIDE_TOKEN_CONFIRMATION', + token + })) + }, + } +} + +BraveTokenMenuDropdown.contextTypes = { + t: PropTypes.func, +} + +inherits(BraveTokenMenuDropdown, Component) +function BraveTokenMenuDropdown () { + Component.call(this) + + this.onClose = this.onClose.bind(this) +} + +BraveTokenMenuDropdown.prototype.onClose = function (e) { + e.stopPropagation() + this.props.onClose() +} + +BraveTokenMenuDropdown.prototype.render = function() { + const { showHideTokenConfirmationModal } = this.props + + return h(Menu, { className: 'token-menu-dropdown', isShowing: true }, [ + h(CloseArea, { + onClick: this.onClose, + }), + h(Item, { + onClick: (e) => { + e.stopPropagation() + showHideTokenConfirmationModal(this.props.token) + this.props.onClose() + }, + isShowing: (this.props.token.symbol !== 'BAT'), + text: 'Hide Tokens', + }), + h(Item, { + onClick: (e) => { + e.stopPropagation() + const url = genAccountLink(this.props.token.address, this.props.network) + global.platform.openWindow({ + url + }) + this.props.onClose() + }, + text: 'View on Etherscan', + }), + ]) +} + +module.exports = connect(mapStateToProps, mapDispatchToProps)(BraveTokenMenuDropdown) \ No newline at end of file diff --git a/brave/ui/app/ducks/metamask/metamask.js b/brave/ui/app/ducks/metamask/metamask.js new file mode 100644 index 0000000000..be6966d2b8 --- /dev/null +++ b/brave/ui/app/ducks/metamask/metamask.js @@ -0,0 +1,12 @@ +const reduceMetamask = require('../../../../../ui/app/ducks/metamask/metamask') + +module.exports = function (state, action) { + const newState = reduceMetamask(state, action) + newState.batTokenAdded = newState.batTokenAdded || false + + if (action.type === 'SET_BAT_TOKEN_ADDED') { + newState.batTokenAdded = action.value + } + + return newState +} diff --git a/brave/ui/app/pages/home/home.component.js b/brave/ui/app/pages/home/home.component.js new file mode 100644 index 0000000000..7dc55cc6c2 --- /dev/null +++ b/brave/ui/app/pages/home/home.component.js @@ -0,0 +1,24 @@ +import Home from '../../../../../ui/app/pages/home/home.component' +import PropTypes from 'prop-types' +import actions from '../../store/actions' +import batToken from '../../store/bat-token' + +const BraveHome = class BraveHome extends Home { + constructor (props) { + super(props) + } + + componentDidMount () { + super.componentDidMount() + + const { batTokenAdded } = this.props + + if (!batTokenAdded) { + this.props.dispatch(actions.addTokens(batToken)) + } + } +} + +BraveHome.propTypes.batTokenAdded = PropTypes.bool + +module.exports = BraveHome \ No newline at end of file diff --git a/brave/ui/app/pages/home/home.container.js b/brave/ui/app/pages/home/home.container.js new file mode 100644 index 0000000000..22872fa238 --- /dev/null +++ b/brave/ui/app/pages/home/home.container.js @@ -0,0 +1,32 @@ +import Home from './home.component' +import { compose } from 'recompose' +import { connect } from 'react-redux' +import { withRouter } from 'react-router-dom' +import { unconfirmedTransactionsCountSelector } from '../../../../../ui/app/selectors/confirm-transaction' + +const mapStateToProps = state => { + const { metamask, appState } = state + const { + lostAccounts, + seedWords, + suggestedTokens, + providerRequests, + batTokenAdded + } = metamask + const { forgottenPassword } = appState + + return { + lostAccounts, + forgottenPassword, + seedWords, + suggestedTokens, + unconfirmedTransactionsCount: unconfirmedTransactionsCountSelector(state), + providerRequests, + batTokenAdded + } +} + +export default compose( + withRouter, + connect(mapStateToProps) +)(Home) diff --git a/brave/ui/app/pages/home/index.js b/brave/ui/app/pages/home/index.js new file mode 100644 index 0000000000..5fca7d33a2 --- /dev/null +++ b/brave/ui/app/pages/home/index.js @@ -0,0 +1 @@ +export { default } from './home.container' \ No newline at end of file diff --git a/brave/ui/app/pages/routes/index.js b/brave/ui/app/pages/routes/index.js new file mode 100644 index 0000000000..3ec4d44c71 --- /dev/null +++ b/brave/ui/app/pages/routes/index.js @@ -0,0 +1,112 @@ +const Routes = require('../../../../../ui/app/pages/routes') + +import PropTypes from 'prop-types' +import { connect } from 'react-redux' +import { compose } from 'recompose' +import { withRouter } from 'react-router-dom' + +import { + getMetaMaskAccounts, + getNetworkIdentifier, + preferencesSelector +} from '../../../../../ui/app/selectors/selectors' +import { + submittedPendingTransactionsSelector +} from '../../../../../ui/app/selectors/transactions' + +import actions from '../../store/actions' + +function mapStateToProps(state) { + const { appState, metamask } = state + const { + networkDropdownOpen, + sidebar, + alertOpen, + alertMessage, + isLoading, + loadingMessage, + } = appState + + const accounts = getMetaMaskAccounts(state) + const { autoLogoutTimeLimit = 0 } = preferencesSelector(state) + + const { + identities, + address, + keyrings, + isInitialized, + seedWords, + unapprovedTxs, + lostAccounts, + unapprovedMsgCount, + unapprovedPersonalMsgCount, + unapprovedTypedMessagesCount, + providerRequests, + batTokenAdded + } = metamask + const selected = address || Object.keys(accounts)[0] + + return { + // state from plugin + networkDropdownOpen, + sidebar, + alertOpen, + alertMessage, + isLoading, + loadingMessage, + isInitialized, + isUnlocked: state.metamask.isUnlocked, + selectedAddress: state.metamask.selectedAddress, + currentView: state.appState.currentView, + activeAddress: state.appState.activeAddress, + transForward: state.appState.transForward, + isOnboarding: Boolean(seedWords || !isInitialized), + isPopup: state.metamask.isPopup, + seedWords: state.metamask.seedWords, + submittedPendingTransactions: submittedPendingTransactionsSelector(state), + unapprovedTxs, + unapprovedMsgs: state.metamask.unapprovedMsgs, + unapprovedMsgCount, + unapprovedPersonalMsgCount, + unapprovedTypedMessagesCount, + menuOpen: state.appState.menuOpen, + network: state.metamask.network, + provider: state.metamask.provider, + forgottenPassword: state.appState.forgottenPassword, + lostAccounts, + frequentRpcListDetail: state.metamask.frequentRpcListDetail || [], + currentCurrency: state.metamask.currentCurrency, + isMouseUser: state.appState.isMouseUser, + isRevealingSeedWords: state.metamask.isRevealingSeedWords, + Qr: state.appState.Qr, + welcomeScreenSeen: state.metamask.welcomeScreenSeen, + providerId: getNetworkIdentifier(state), + autoLogoutTimeLimit, + + identities, + selected, + keyrings, + providerRequests, + batTokenAdded + } +} + +function mapDispatchToProps(dispatch) { + return { + dispatch, + hideSidebar: () => dispatch(actions.hideSidebar()), + showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()), + hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()), + setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')), + toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()), + setMouseUserState: (isMouseUser) => dispatch(actions.setMouseUserState(isMouseUser)), + setLastActiveTime: () => dispatch(actions.setLastActiveTime()), + } +} + +Routes.propTypes['batTokenAdded'] = PropTypes.bool + +module.exports = compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(Routes) diff --git a/brave/ui/app/store/actions.js b/brave/ui/app/store/actions.js new file mode 100644 index 0000000000..7c507a5475 --- /dev/null +++ b/brave/ui/app/store/actions.js @@ -0,0 +1,40 @@ +const MetaMaskActions = require('../../../../ui/app/store/actions') + +MetaMaskActions.addToken = addToken +MetaMaskActions.setBatTokenAdded = setBatTokenAdded +MetaMaskActions.SET_BAT_TOKEN_ADDED = 'SET_BAT_TOKEN_ADDED' + +function setBatTokenAdded () { + return (dispatch) => { + background.setBatTokenAdded((err) => { + if (err) { + return dispatch(actions.displayWarning(err.message)) + } + }) + dispatch({ + type: actions.SET_BAT_TOKEN_ADDED, + value: true + }) + } +} + +function addToken (address, symbol, decimals, image) { + return (dispatch) => { + dispatch(actions.showLoadingIndication()) + return new Promise((resolve, reject) => { + background.addToken(address, symbol, decimals, image, (err, tokens) => { + dispatch(actions.hideLoadingIndication()) + if (err) { + dispatch(actions.displayWarning(err.message)) + reject(err) + } else if (symbol === 'BAT') { + dispatch(actions.setBatTokenAdded()) + } + dispatch(actions.updateTokens(tokens)) + resolve(tokens) + }) + }) + } +} + +module.exports = MetaMaskActions diff --git a/brave/ui/app/store/bat-token.js b/brave/ui/app/store/bat-token.js new file mode 100644 index 0000000000..57de132708 --- /dev/null +++ b/brave/ui/app/store/bat-token.js @@ -0,0 +1,10 @@ +export default { + '0x0D8775F648430679A709E98d2b0Cb6250d2887EF': { + 'name': 'Basic Attention Token', + 'logo': 'BAT_icon.svg', + 'erc20': true, + 'symbol': 'BAT', + 'decimals': 18, + 'address' :'0x0D8775F648430679A709E98d2b0Cb6250d2887EF' + } + } \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index d5d74333af..78383c18d3 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -33,14 +33,13 @@ const materialUIDependencies = ['@material-ui/core'] const reactDepenendencies = dependencies.filter(dep => dep.match(/react/)) const d3Dependencies = ['c3', 'd3'] -const externalDependenciesMap = { - background: [ - '3box', - ], - ui: [ - ...materialUIDependencies, ...reactDepenendencies, ...d3Dependencies, - ], -} +const braveGulp = require('./brave/gulp') + +const uiDependenciesToBundle = [ + ...materialUIDependencies, + ...reactDepenendencies, + ...d3Dependencies, +] function gulpParallel (...args) { return function spawnGulpChildProcess (cb) { @@ -449,6 +448,19 @@ gulp.task('zip', gulp.parallel('zip:chrome', 'zip:firefox', 'zip:opera')) // high level tasks +gulp.task('dev', + gulp.series( + braveGulp, + 'clean', + 'dev:scss', + gulp.parallel( + 'dev:extension:js', + 'dev:copy', + 'dev:reload' + ) + ) +) + gulp.task('dev:test', gulp.series( 'clean',