mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Complete integration of gas chart with redux.
This commit is contained in:
parent
a2bbf504b8
commit
cd32c58fb4
@ -97,7 +97,7 @@ export default class AdvancedTabContent extends Component {
|
||||
updateCustomGasLimit
|
||||
) }
|
||||
<div className="advanced-tab__fee-chart__title">Live Gas Price Predictions</div>
|
||||
<GasPriceChart {...gasChartProps} />
|
||||
<GasPriceChart {...gasChartProps} updateCustomGasPrice={updateCustomGasPrice} />
|
||||
<div className="advanced-tab__fee-chart__speed-buttons">
|
||||
<span>Slower</span>
|
||||
<span>Faster</span>
|
||||
|
@ -47,6 +47,7 @@ export default class GasModalPageContainer extends Component {
|
||||
customGasLimit,
|
||||
newTotalFiat,
|
||||
gasChartProps,
|
||||
currentTimeEstimate,
|
||||
}) {
|
||||
const { transactionFee } = this.props
|
||||
return (
|
||||
@ -55,7 +56,7 @@ export default class GasModalPageContainer extends Component {
|
||||
updateCustomGasLimit={convertThenUpdateCustomGasLimit}
|
||||
customGasPrice={customGasPrice}
|
||||
customGasLimit={customGasLimit}
|
||||
timeRemaining="1 min 31 sec"
|
||||
timeRemaining={currentTimeEstimate}
|
||||
transactionFee={transactionFee}
|
||||
totalFee={newTotalFiat}
|
||||
gasChartProps={gasChartProps}
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
setCustomGasPrice,
|
||||
setCustomGasLimit,
|
||||
resetCustomData,
|
||||
setCustomTimeEstimate,
|
||||
} from '../../../ducks/gas.duck'
|
||||
import {
|
||||
hideGasButtonGroup,
|
||||
@ -29,6 +30,7 @@ import {
|
||||
getBasicGasEstimateLoadingStatus,
|
||||
getAveragePriceEstimateInHexWEI,
|
||||
getDefaultActiveButtonIndex,
|
||||
formatTimeEstimate,
|
||||
} from '../../../selectors/custom-gas'
|
||||
import {
|
||||
formatCurrency,
|
||||
@ -65,20 +67,24 @@ const mapStateToProps = state => {
|
||||
|
||||
const hideBasic = state.appState.modal.modalState.props.hideBasic
|
||||
|
||||
const customGasPrice = calcCustomGasPrice(customModalGasPriceInHex)
|
||||
|
||||
return {
|
||||
hideBasic,
|
||||
isConfirm: isConfirm(state),
|
||||
customModalGasPriceInHex,
|
||||
customModalGasLimitInHex,
|
||||
customGasPrice: calcCustomGasPrice(customModalGasPriceInHex),
|
||||
customGasPrice,
|
||||
customGasLimit: calcCustomGasLimit(customModalGasLimitInHex),
|
||||
newTotalFiat,
|
||||
currentTimeEstimate: getRenderableTimeEstimate(customGasPrice, state.gas.priceAndTimeEstimates),
|
||||
gasPriceButtonGroupProps: {
|
||||
buttonDataLoading,
|
||||
defaultActiveButtonIndex: getDefaultActiveButtonIndex(gasButtonInfo, customModalGasPriceInHex),
|
||||
gasButtonInfo,
|
||||
},
|
||||
gasChartProps: {
|
||||
currentPrice: customGasPrice,
|
||||
priceAndTimeEstimates: state.gas.priceAndTimeEstimates,
|
||||
},
|
||||
infoRowProps: {
|
||||
@ -111,6 +117,7 @@ const mapDispatchToProps = dispatch => {
|
||||
return dispatch(updateGasAndCalculate({ gasLimit, gasPrice }))
|
||||
},
|
||||
hideGasButtonGroup: () => dispatch(hideGasButtonGroup()),
|
||||
setCustomTimeEstimate: (timeEstimateInSeconds) => dispatch(setCustomTimeEstimate(timeEstimateInSeconds)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,3 +188,25 @@ function addHexWEIsToRenderableFiat (aHexWEI, bHexWEI, convertedCurrency, conver
|
||||
partialRight(formatCurrency, [convertedCurrency]),
|
||||
)(aHexWEI, bHexWEI)
|
||||
}
|
||||
|
||||
function getRenderableTimeEstimate (currentGasPrice, priceAndTimeEstimates) {
|
||||
const gasPrices = priceAndTimeEstimates.map(({ gasprice }) => gasprice)
|
||||
const estimatedTimes = priceAndTimeEstimates.map(({ expectedTime }) => expectedTime)
|
||||
|
||||
const closestLowerValueIndex = gasPrices.findIndex((e, i, a) => {
|
||||
return e <= currentGasPrice && a[i + 1] >= currentGasPrice
|
||||
})
|
||||
const closestHigherValueIndex = gasPrices.findIndex((e, i, a) => {
|
||||
return e > currentGasPrice
|
||||
})
|
||||
|
||||
const closestLowerValue = gasPrices[closestLowerValueIndex]
|
||||
const closestHigherValue = gasPrices[closestHigherValueIndex]
|
||||
const estimatedClosestLowerTimeEstimate = estimatedTimes[closestLowerValueIndex]
|
||||
const estimatedClosestHigherTimeEstimate = estimatedTimes[closestHigherValueIndex]
|
||||
|
||||
const slope = (estimatedClosestHigherTimeEstimate - estimatedClosestLowerTimeEstimate) / (closestHigherValue - closestLowerValue)
|
||||
const newTimeEstimate = -1 * (slope * (closestHigherValue - currentGasPrice) - estimatedClosestHigherTimeEstimate)
|
||||
|
||||
return formatTimeEstimate(newTimeEstimate)
|
||||
}
|
||||
|
@ -65,6 +65,7 @@ describe('GasModalPageContainer Component', function () {
|
||||
customGasLimit={54321}
|
||||
gasPriceButtonGroupProps={mockGasPriceButtonGroupProps}
|
||||
infoRowProps={mockInfoRowProps}
|
||||
currentTimeEstimate={'1 min 31 sec'}
|
||||
customGasPriceInHex={'mockCustomGasPriceInHex'}
|
||||
customGasLimitInHex={'mockCustomGasLimitInHex'}
|
||||
/>, { context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } })
|
||||
@ -199,6 +200,7 @@ describe('GasModalPageContainer Component', function () {
|
||||
customGasPrice: 123,
|
||||
customGasLimit: 456,
|
||||
newTotalFiat: '$0.30',
|
||||
currentTimeEstimate: '1 min 31 sec',
|
||||
})
|
||||
const advancedTabContentProps = renderAdvancedTabContentResult.props
|
||||
assert.equal(advancedTabContentProps.updateCustomGasPrice(), 'mockConvertThenUpdateCustomGasPrice')
|
||||
|
@ -75,7 +75,12 @@ describe('gas-modal-page-container container', () => {
|
||||
limit: 'aaaaaaaa',
|
||||
price: 'ffffffff',
|
||||
},
|
||||
priceAndTimeEstimates: 'mockPriceAndTimeEstimates',
|
||||
priceAndTimeEstimates: [
|
||||
{ gasprice: 3, expectedTime: '31' },
|
||||
{ gasprice: 4, expectedTime: '62' },
|
||||
{ gasprice: 5, expectedTime: '93' },
|
||||
{ gasprice: 6, expectedTime: '124' },
|
||||
],
|
||||
},
|
||||
confirmTransaction: {
|
||||
txData: {
|
||||
@ -93,11 +98,18 @@ describe('gas-modal-page-container container', () => {
|
||||
isConfirm: true,
|
||||
customGasPrice: 4.294967295,
|
||||
customGasLimit: 2863311530,
|
||||
currentTimeEstimate: '~1 min 11 sec',
|
||||
newTotalFiat: '637.41',
|
||||
customModalGasLimitInHex: 'aaaaaaaa',
|
||||
customModalGasPriceInHex: 'ffffffff',
|
||||
gasChartProps: {
|
||||
priceAndTimeEstimates: 'mockPriceAndTimeEstimates',
|
||||
'currentPrice': 4.294967295,
|
||||
priceAndTimeEstimates: [
|
||||
{ gasprice: 3, expectedTime: '31' },
|
||||
{ gasprice: 4, expectedTime: '62' },
|
||||
{ gasprice: 5, expectedTime: '93' },
|
||||
{ gasprice: 6, expectedTime: '124' },
|
||||
],
|
||||
},
|
||||
gasPriceButtonGroupProps: {
|
||||
buttonDataLoading: 'mockBasicGasEstimateLoadingStatus:4',
|
||||
|
@ -35,6 +35,43 @@ function appendOrUpdateCircle ({ circle, data, itemIndex, cx, cy, cssId, appendO
|
||||
}
|
||||
}
|
||||
|
||||
function setSelectedCircle ({ chart, gasPrices, currentPrice, chartXStart, chartWidth }) {
|
||||
const numberOfValues = chart.internal.data.xs.data1.length
|
||||
const closestLowerValueIndex = gasPrices.findIndex((e, i, a) => {
|
||||
return e <= currentPrice && a[i + 1] >= currentPrice
|
||||
})
|
||||
const closestHigherValueIndex = gasPrices.findIndex((e, i, a) => {
|
||||
return e > currentPrice
|
||||
})
|
||||
const closestHigherValue = gasPrices[closestHigherValueIndex]
|
||||
const closestLowerValue = gasPrices[closestLowerValueIndex]
|
||||
|
||||
if (closestHigherValue && closestLowerValue) {
|
||||
const closestLowerCircle = d3.select(`.c3-circle-${closestLowerValueIndex}`)
|
||||
const closestHigherCircle = d3.select(`.c3-circle-${closestHigherValueIndex}`)
|
||||
const { x: lowerX, y: lowerY } = closestLowerCircle.node().getBoundingClientRect()
|
||||
const { x: higherX, y: higherY } = closestHigherCircle.node().getBoundingClientRect()
|
||||
const currentX = lowerX + (higherX - lowerX) * (currentPrice - closestLowerValue) / (closestHigherValue - closestLowerValue)
|
||||
const slope = (higherY - lowerY) / (higherX - lowerX)
|
||||
const newTimeEstimate = -1 * (slope * (higherX - currentX) - higherY)
|
||||
chart.internal.selectPointB({
|
||||
x: currentX,
|
||||
value: newTimeEstimate,
|
||||
id: 'data1',
|
||||
index: numberOfValues,
|
||||
name: 'data1',
|
||||
}, numberOfValues)
|
||||
} else {
|
||||
const setCircle = d3.select('#set-circle')
|
||||
if (!setCircle.empty()) {
|
||||
setCircle.remove()
|
||||
}
|
||||
d3.select('.c3-tooltip-container').style('display', 'none !important')
|
||||
chart.internal.hideXGridFocus()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
export default class GasPriceChart extends Component {
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
@ -42,12 +79,15 @@ export default class GasPriceChart extends Component {
|
||||
|
||||
static propTypes = {
|
||||
priceAndTimeEstimates: PropTypes.array,
|
||||
currentPrice: PropTypes.number,
|
||||
updateCustomGasPrice: PropTypes.func,
|
||||
}
|
||||
|
||||
renderChart (priceAndTimeEstimates) {
|
||||
renderChart (currentPrice, priceAndTimeEstimates, updateCustomGasPrice) {
|
||||
const gasPrices = priceAndTimeEstimates.map(({ gasprice }) => gasprice)
|
||||
const gasPricesMax = gasPrices[gasPrices.length - 1] + 1
|
||||
const estimatedTimes = priceAndTimeEstimates.map(({ expectedTime }) => expectedTime)
|
||||
|
||||
const estimatedTimesMax = estimatedTimes[0]
|
||||
const chart = c3.generate({
|
||||
size: {
|
||||
@ -187,6 +227,28 @@ export default class GasPriceChart extends Component {
|
||||
})
|
||||
}
|
||||
|
||||
chart.internal.selectPointB = function (data, itemIndex = (data.index || 0)) {
|
||||
const { x: chartXStart, y: chartYStart } = d3.select('.c3-areas-data1')
|
||||
.node()
|
||||
.getBoundingClientRect()
|
||||
|
||||
d3.select('#set-circle').remove()
|
||||
|
||||
const circle = this.main
|
||||
.select('.' + 'c3-selected-circles' + this.getTargetSelectorSuffix(data.id))
|
||||
.selectAll('.' + 'c3-selected-circle' + '-' + itemIndex)
|
||||
|
||||
appendOrUpdateCircle.bind(this)({
|
||||
circle,
|
||||
data,
|
||||
itemIndex,
|
||||
cx: () => data.x - chartXStart + 11,
|
||||
cy: () => data.value - chartYStart + 10,
|
||||
cssId: 'set-circle',
|
||||
appendOnly: true,
|
||||
})
|
||||
}
|
||||
|
||||
chart.internal.overlayPoint = function (data, itemIndex) {
|
||||
const circle = this.main
|
||||
.select('.' + 'c3-selected-circles' + this.getTargetSelectorSuffix(data.id))
|
||||
@ -238,13 +300,13 @@ export default class GasPriceChart extends Component {
|
||||
|
||||
setTimeout(function () {
|
||||
setTickPosition('y', 0, -5, 8)
|
||||
setTickPosition('y', 1, -3)
|
||||
setTickPosition('x', 0, 3, 20)
|
||||
setTickPosition('x', 1, 3, -10)
|
||||
setTickPosition('y', 1, -3, -5)
|
||||
setTickPosition('x', 0, 3, 15)
|
||||
setTickPosition('x', 1, 3, -8)
|
||||
|
||||
// TODO: Confirm the below constants work with all data sets and screen sizes
|
||||
d3.select('.c3-axis-x-label').attr('transform', 'translate(0,-15)')
|
||||
d3.select('.c3-axis-y-label').attr('transform', 'translate(32, 2) rotate(-90)')
|
||||
d3.select('.c3-axis-y-label').attr('transform', 'translate(52, 2) rotate(-90)')
|
||||
d3.select('.c3-xgrid-focus line').attr('y2', 98)
|
||||
|
||||
d3.select('.c3-chart').on('mouseout', () => {
|
||||
@ -262,6 +324,7 @@ export default class GasPriceChart extends Component {
|
||||
const overlayedCircle = d3.select('#overlayed-circle')
|
||||
const numberOfValues = chart.internal.data.xs.data1.length
|
||||
const { x: circleX, y: circleY } = overlayedCircle.node().getBoundingClientRect()
|
||||
const { x: xData } = overlayedCircle.datum()
|
||||
chart.internal.selectPoint({
|
||||
x: circleX - chartXStart,
|
||||
value: circleY - 1.5,
|
||||
@ -269,13 +332,15 @@ export default class GasPriceChart extends Component {
|
||||
index: numberOfValues,
|
||||
name: 'data1',
|
||||
}, numberOfValues)
|
||||
updateCustomGasPrice(xData)
|
||||
})
|
||||
|
||||
setSelectedCircle({ chart, gasPrices, currentPrice, chartXStart, chartWidth })
|
||||
|
||||
d3.select('.c3-chart').on('mousemove', function () {
|
||||
const chartMouseXPos = d3.event.clientX - chartXStart
|
||||
const posPercentile = chartMouseXPos / chartWidth
|
||||
|
||||
|
||||
const currentPosValue = (gasPrices[gasPrices.length - 1] - gasPrices[0]) * posPercentile + gasPrices[0]
|
||||
const closestLowerValueIndex = gasPrices.findIndex((e, i, a) => {
|
||||
return e <= currentPosValue && a[i + 1] >= currentPosValue
|
||||
@ -326,11 +391,26 @@ export default class GasPriceChart extends Component {
|
||||
})
|
||||
}, 0)
|
||||
|
||||
this.chart = chart
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
if (prevProps.currentPrice !== this.props.currentPrice) {
|
||||
const chartRect = d3.select('.c3-areas-data1')
|
||||
const { x: chartXStart, width: chartWidth } = chartRect.node().getBoundingClientRect()
|
||||
setSelectedCircle({
|
||||
chart: this.chart,
|
||||
currentPrice: this.props.currentPrice,
|
||||
gasPrices: this.props.priceAndTimeEstimates.map(({ gasprice }) => gasprice),
|
||||
chartXStart,
|
||||
chartWidth,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.renderChart(this.props.priceAndTimeEstimates)
|
||||
const { currentPrice, priceAndTimeEstimates, updateCustomGasPrice } = this.props
|
||||
this.renderChart(currentPrice, priceAndTimeEstimates, updateCustomGasPrice)
|
||||
}
|
||||
|
||||
render () {
|
||||
|
@ -10,6 +10,9 @@ const mockSelectReturn = {
|
||||
node: () => ({
|
||||
getBoundingClientRect: () => ({ x: 123, y: 321, width: 400 }),
|
||||
}),
|
||||
empty: sinon.spy(),
|
||||
remove: sinon.spy(),
|
||||
style: sinon.spy(),
|
||||
select: d3.select,
|
||||
attr: sinon.spy(),
|
||||
on: sinon.spy(),
|
||||
@ -17,11 +20,17 @@ const mockSelectReturn = {
|
||||
|
||||
const GasPriceChart = proxyquire('../gas-price-chart.component.js', {
|
||||
'c3': {
|
||||
generate: function () {
|
||||
generate: function ({ data: { columns } }) {
|
||||
return {
|
||||
internal: {
|
||||
showTooltip: () => {},
|
||||
showXGridFocus: () => {},
|
||||
hideXGridFocus: () => {},
|
||||
data: {
|
||||
xs: {
|
||||
[columns[1][0]]: columns[1].slice(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
@ -212,7 +212,7 @@ export function fetchGasEstimates (blockTime) {
|
||||
const estimatedPricesAndTimes = r.map(({ expectedTime, expectedWait, gasprice }) => ({ expectedTime, expectedWait, gasprice }))
|
||||
const estimatedTimeWithUniquePrices = uniqBy(({ expectedTime }) => expectedTime, estimatedPricesAndTimes)
|
||||
const timeMappedToSeconds = estimatedTimeWithUniquePrices.map(({ expectedWait, gasprice }) => {
|
||||
const expectedTime = (new BigNumber(expectedWait)).times(Number(blockTime), 10).div(60, 10).toString(10)
|
||||
const expectedTime = (new BigNumber(expectedWait)).times(Number(blockTime), 10).toString(10)
|
||||
return {
|
||||
expectedTime,
|
||||
expectedWait,
|
||||
|
@ -31,6 +31,7 @@ const selectors = {
|
||||
getAveragePriceEstimateInHexWEI,
|
||||
getDefaultActiveButtonIndex,
|
||||
priceEstimateToWei,
|
||||
formatTimeEstimate,
|
||||
}
|
||||
|
||||
module.exports = selectors
|
||||
|
Loading…
Reference in New Issue
Block a user