1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-10-22 19:26:13 +02:00

Adds the content of the advanced tab - w/o chart or dynamic content - to gas customize modal.

This commit is contained in:
Dan Miller 2018-08-02 13:32:22 -02:30
parent 5e7409482b
commit 342dc95410
21 changed files with 538 additions and 63 deletions

View File

@ -701,6 +701,9 @@
"missingYourTokens": {
"message": "Don't see your tokens?"
},
"minutesShorthand": {
"message": "Min"
},
"myAccounts": {
"message": "My Accounts"
},
@ -1036,6 +1039,9 @@
"secretPhrase": {
"message": "Enter your secret twelve word phrase here to restore your vault."
},
"secondsShorthand": {
"message": "Sec"
},
"seedPhraseReq": {
"message": "Seed phrases are 12 words long"
},

View File

@ -0,0 +1,105 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import {
MIN_GAS_PRICE_DEC,
MIN_GAS_LIMIT_DEC,
} from '../../../send/send.constants'
import GasSlider from '../../gas-slider'
import TimeRemaining from './time-remaining'
export default class AdvancedTabContent extends Component {
static contextTypes = {
t: PropTypes.func,
}
static propTypes = {
updateCustomGasPrice: PropTypes.func,
updateCustomGasLimit: PropTypes.func,
customGasPrice: PropTypes.number,
customGasLimit: PropTypes.number,
millisecondsRemaining: PropTypes.number,
}
gasInput (value, onChange, min, precision, showGWEI) {
return (
<div className="advanced-tab__gas-edit-row__input-wrapper">
<input
className="advanced-tab__gas-edit-row__input"
type="number"
value={value}
onChange={event => onChange(Number(event.target.value))}
/>
{showGWEI
? <span className="advanced-tab__gas-edit-row__gwei-symbol">GWEI</span>
: null}
</div>
)
}
infoButton (onClick) {
return <i className="fa info-circle" onClick={onClick} />
}
render () {
const {
updateCustomGasPrice,
updateCustomGasLimit,
millisecondsRemaining,
customGasPrice,
customGasLimit,
} = this.props
return (
<div className="advanced-tab">
<div className="advanced-tab__transaction-data-summary">
<div className="advanced-tab__transaction-data-summary__titles">
<span>New Transaction Fee</span>
<span>~Transaction Time</span>
</div>
<div className="advanced-tab__transaction-data-summary__container">
<div className="advanced-tab__transaction-data-summary__fee">
$0.30
</div>
<TimeRemaining
milliseconds={millisecondsRemaining}
/>
</div>
</div>
<div className="advanced-tab__fee-chart-title">
Live Transaction Fee Predictions
</div>
<div className="advanced-tab__fee-chart" />
<div className="advanced-tab__slider-container">
<GasSlider
onChange={value => {
updateCustomGasPrice(Number(value))
}}
lowLabel={'Cheaper'}
highLabel={'Faster'}
value={customGasPrice}
step={0.1}
max={200}
min={0}
coloredStart={{}}
/>
</div>
<div className="advanced-tab__gas-edit-rows">
<div className="advanced-tab__gas-edit-row">
<div className="advanced-tab__gas-edit-row__label">
Gas Price
{ this.infoButton(() => {}) }
</div>
{ this.gasInput(customGasPrice, updateCustomGasPrice, MIN_GAS_PRICE_DEC, 9, true) }
</div>
<div className="advanced-tab__gas-edit-row">
<div className="advanced-tab__gas-edit-row__label">
Gas Limit
{ this.infoButton(() => {}) }
</div>
{ this.gasInput(customGasLimit, updateCustomGasLimit, MIN_GAS_LIMIT_DEC, 0) }
</div>
</div>
</div>
)
}
}

View File

@ -0,0 +1 @@
export { default } from './advanced-tab-content.component'

View File

@ -0,0 +1,109 @@
@import './time-remaining/index';
.advanced-tab {
display: flex;
flex-flow: column;
border-bottom: 1px solid $alto;
&__transaction-data-summary,
&__fee-chart-title,
&__gas-edit-row {
padding-left: 24px;
padding-right: 24px;
}
&__transaction-data-summary {
display: flex;
flex-flow: column;
color: $mid-gray;
margin-top: 12px;
&__titles,
&__container {
display: flex;
flex-flow: row;
justify-content: space-between;
font-size: 12px;
}
&__container {
font-size: 26px;
margin-top: 6px;
}
}
&__fee-chart-title {
font-size: 14px;
color: $scorpion;
margin-top: 22px;
}
&__fee-chart {
padding-left: 10px;
margin-top: 24px;
height: 134px;
}
&__slider-container {
padding-left: 27px;
padding-right: 27px;
}
&__gas-edit-rows {
margin-top: 44px;
height: 87px;
display: flex;
flex-flow: column;
justify-content: space-between;
}
&__gas-edit-row {
display: flex;
flex-flow: row;
justify-content: space-between;
&__label {
color: $mid-gray;
font-size: 16px;
.info-circle {
color: $silver;
margin-left: 10px;
}
}
&__input-wrapper {
position: relative;
}
&__input {
border: 1px solid $dusty-gray;
border-radius: 4px;
color: $mid-gray;
font-size: 16px;
height: 37px;
width: 163px;
padding: 8px 10px 10px 10px;
}
input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
-moz-appearance: none;
display: none;
}
input[type="number"]:hover::-webkit-inner-spin-button {
-webkit-appearance: none;
-moz-appearance: none;
display: none;
}
&__gwei-symbol {
position: absolute;
top: 8px;
right: 10px;
color: $dusty-gray;
}
}
}

View File

@ -0,0 +1 @@
export { default } from './time-remaining.component'

View File

@ -0,0 +1,13 @@
.time-remaining {
.minutes-num, .seconds-num {
font-size: 26px;
}
.seconds-num {
margin-left: 7px;
}
.minutes-label, .seconds-label {
font-size: 14px;
}
}

View File

@ -0,0 +1,33 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { getTimeBreakdown } from './time-remaining.utils'
export default class TimeRemaining extends Component {
static contextTypes = {
t: PropTypes.func,
}
static propTypes = {
milliseconds: PropTypes.number,
}
render () {
const {
milliseconds,
} = this.props
const {
minutes,
seconds,
} = getTimeBreakdown(milliseconds)
return (
<div className="time-remaining">
<span className="minutes-num">{minutes}</span>
<span className="minutes-label">{this.context.t('minutesShorthand')}</span>
<span className="seconds-num">{seconds}</span>
<span className="seconds-label">{this.context.t('secondsShorthand')}</span>
</div>
)
}
}

View File

@ -0,0 +1,11 @@
function getTimeBreakdown (milliseconds) {
return {
hours: Math.floor(milliseconds / 3600000),
minutes: Math.floor((milliseconds % 3600000) / 60000),
seconds: Math.floor((milliseconds % 60000) / 1000),
}
}
module.exports = {
getTimeBreakdown,
}

View File

@ -2,6 +2,7 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import PageContainer from '../../page-container'
import { Tabs, Tab } from '../../tabs'
import AdvancedTabContent from './advanced-tab-content'
export default class GasModalPageContainer extends Component {
static contextTypes = {
@ -10,6 +11,10 @@ export default class GasModalPageContainer extends Component {
static propTypes = {
hideModal: PropTypes.func,
updateCustomGasPrice: PropTypes.func,
updateCustomGasLimit: PropTypes.func,
customGasPrice: PropTypes.number,
customGasLimit: PropTypes.number,
}
state = {}
@ -20,9 +25,22 @@ export default class GasModalPageContainer extends Component {
)
}
renderAdvancedTabContent () {
renderAdvancedTabContent = () => {
const {
updateCustomGasPrice,
updateCustomGasLimit,
customGasPrice,
customGasLimit,
} = this.props
return (
<div className="gas-modal-content__advanced-tab"/>
<AdvancedTabContent
updateCustomGasPrice={updateCustomGasPrice}
updateCustomGasLimit={updateCustomGasLimit}
customGasPrice={customGasPrice}
customGasLimit={customGasLimit}
millisecondsRemaining={91000}
/>
)
}
@ -68,14 +86,16 @@ export default class GasModalPageContainer extends Component {
const { hideModal } = this.props
return (
<PageContainer
title={this.context.t('customGas')}
subtitle={this.context.t('customGasSubTitle')}
tabsComponent={this.renderTabs()}
disabled={false}
onCancel={() => hideModal()}
onClose={() => hideModal()}
/>
<div className="gas-modal-page-container">
<PageContainer
title={this.context.t('customGas')}
subtitle={this.context.t('customGasSubTitle')}
tabsComponent={this.renderTabs()}
disabled={false}
onCancel={() => hideModal()}
onClose={() => hideModal()}
/>
</div>
)
}
}

View File

@ -1,11 +1,28 @@
import { connect } from 'react-redux'
import GasModalPageContainer from './gas-modal-page-container.component'
import { hideModal } from '../../../actions'
import {
setCustomGasPrice,
setCustomGasLimit,
} from '../../../ducks/custom-gas'
import {
getCustomGasPrice,
getCustomGasLimit,
} from '../../../selectors/custom-gas'
const mapStateToProps = state => {
return {
customGasPrice: getCustomGasPrice(state),
customGasLimit: getCustomGasLimit(state),
}
}
const mapDispatchToProps = dispatch => {
return {
hideModal: () => dispatch(hideModal()),
updateCustomGasPrice: (newPrice) => dispatch(setCustomGasPrice(newPrice)),
updateCustomGasLimit: (newLimit) => dispatch(setCustomGasLimit(newLimit)),
}
}
export default connect(null, mapDispatchToProps)(GasModalPageContainer)
export default connect(mapStateToProps, mapDispatchToProps)(GasModalPageContainer)

View File

@ -1,3 +1,11 @@
@import './advanced-tab-content/index';
.gas-modal-page-container {
.page-container {
width: 391px;
}
}
.gas-modal-content {
&__basic-tab {
height: 219px;

View File

@ -0,0 +1,48 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
export default class AdvancedTabContent extends Component {
static propTypes = {
onChange: PropTypes.func,
lowLabel: PropTypes.string,
highLabel: PropTypes.string,
value: PropTypes.number,
step: PropTypes.number,
max: PropTypes.number,
min: PropTypes.number,
}
render () {
const {
onChange,
lowLabel,
highLabel,
value,
step,
max,
min,
} = this.props
return (
<div className="gas-slider">
<input
className="gas-slider__input"
type="range"
step={step}
max={max}
min={min}
value={value}
id="gasSlider"
onChange={event => onChange(event.target.value)}
/>
<div className="gas-slider__bar">
<div className="gas-slider__colored"/>
</div>
<div className="gas-slider__labels">
<span>{lowLabel}</span>
<span>{highLabel}</span>
</div>
</div>
)
}
}

View File

@ -0,0 +1 @@
export { default } from './gas-slider.component'

View File

@ -0,0 +1,54 @@
.gas-slider {
position: relative;
width: 322px;
&__input {
width: 322px;
margin-left: -2px;
z-index: 2;
}
input[type=range] {
-webkit-appearance: none !important;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none !important;
height: 34px;
width: 34px;
background-color: $curious-blue;
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.08);
border-radius: 50%;
position: relative;
z-index: 10;
}
&__bar {
height: 6px;
width: 322px;
background: $alto;
display: flex;
justify-content: space-between;
position: absolute;
top: 16px;
z-index: 0;
border-radius: 4px;
}
&__colored {
height: 6px;
border-radius: 4px;
margin-left: 102px;
width: 322px;
z-index: 1;
background-color: $blizzard-blue;
}
&__labels {
display: flex;
justify-content: space-between;
font-size: 12px;
margin-top: -6px;
color: $mid-gray;
}
}

View File

@ -0,0 +1,3 @@
@import './gas-slider/index';
@import './gas-modal-page-container/index';

View File

@ -64,4 +64,10 @@
@import './unit-input/index';
@import './gas-customization/gas-modal-page-container/index'
@import './gas-customization/gas-modal-page-container/index';
@import './gas-customization/gas-modal-page-container/index';
@import './gas-customization/gas-modal-page-container/index';
@import './gas-customization/index';

View File

@ -1,51 +0,0 @@
.gas-slider {
position: relative;
width: 313px;
&__input {
width: 317px;
margin-left: -2px;
z-index: 2;
}
input[type=range] {
-webkit-appearance: none !important;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none !important;
height: 26px;
width: 26px;
border: 2px solid #B8B8B8;
background-color: #FFFFFF;
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.08);
border-radius: 50%;
position: relative;
z-index: 10;
}
&__bar {
height: 6px;
width: 313px;
background: $alto;
display: flex;
justify-content: space-between;
position: absolute;
top: 11px;
z-index: 0;
}
&__low, &__high {
height: 6px;
width: 49px;
z-index: 1;
}
&__low {
background-color: $crimson;
}
&__high {
background-color: $caribbean-green;
}
}

View File

@ -57,6 +57,7 @@ $ecstasy: #f7861c;
$linen: #fdf4f4;
$oslo-gray: #8C8E94;
$polar: #fafcfe;
$blizzard-blue: #bfdef3;
/*
Z-Indicies

View File

@ -0,0 +1,67 @@
import extend from 'xtend'
// Actions
const SET_CUSTOM_GAS_PRICE = 'metamask/custom-gas/SET_CUSTOM_GAS_PRICE'
const SET_CUSTOM_GAS_LIMIT = 'metamask/custom-gas/SET_CUSTOM_GAS_LIMIT'
const SET_CUSTOM_GAS_ERRORS = 'metamask/custom-gas/SET_CUSTOM_GAS_ERRORS'
const RESET_CUSTOM_GAS_STATE = 'metamask/custom-gas/RESET_CUSTOM_GAS_STATE'
// TODO: determine if this approach to initState is consistent with conventional ducks pattern
const initState = {
price: 0,
limit: 21000,
errors: {},
}
// Reducer
export default function reducer ({ customGas: customGasState = initState }, action = {}) {
const newState = extend({}, customGasState)
switch (action.type) {
case SET_CUSTOM_GAS_PRICE:
return extend(newState, {
price: action.value,
})
case SET_CUSTOM_GAS_LIMIT:
return extend(newState, {
limit: action.value,
})
case SET_CUSTOM_GAS_ERRORS:
return extend(newState, {
errors: {
...newState.errors,
...action.value,
},
})
case RESET_CUSTOM_GAS_STATE:
return extend({}, initState)
default:
return newState
}
}
// Action Creators
export function setCustomGasPrice (newPrice) {
return {
type: SET_CUSTOM_GAS_PRICE,
value: newPrice,
}
}
export function setCustomGasLimit (newLimit) {
return {
type: SET_CUSTOM_GAS_LIMIT,
value: newLimit,
}
}
export function setCustomGasErrors (newErrors) {
return {
type: SET_CUSTOM_GAS_ERRORS,
value: newErrors,
}
}
export function resetCustomGasState () {
return { type: RESET_CUSTOM_GAS_STATE }
}

View File

@ -10,6 +10,7 @@ const reduceApp = require('./reducers/app')
const reduceLocale = require('./reducers/locale')
const reduceSend = require('./ducks/send.duck').default
import reduceConfirmTransaction from './ducks/confirm-transaction.duck'
import reduceCustomGas from './ducks/custom-gas'
window.METAMASK_CACHED_LOG_STATE = null
@ -49,6 +50,8 @@ function rootReducer (state, action) {
state.confirmTransaction = reduceConfirmTransaction(state, action)
state.customGas = reduceCustomGas(state, action)
window.METAMASK_CACHED_LOG_STATE = state
return state
}

View File

@ -0,0 +1,19 @@
const selectors = {
getCustomGasPrice,
getCustomGasLimit,
getCustomGasErrors,
}
module.exports = selectors
function getCustomGasPrice (state) {
return state.customGas.price
}
function getCustomGasLimit (state) {
return state.customGas.limit
}
function getCustomGasErrors (state) {
return state.customGas.errors
}