1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

EIP-1559 - Show gas estimate updating animation in transaction detail (#11566)

This commit is contained in:
David Walsh 2021-07-26 10:24:44 -05:00 committed by GitHub
parent 6986e76adc
commit a2be02dfeb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 138 additions and 42 deletions

View File

@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { useGasFeeInputs } from '../../../hooks/useGasFeeInputs'; import { useGasFeeInputs } from '../../../hooks/useGasFeeInputs';
import { useShouldAnimateGasEstimations } from '../../../hooks/useShouldAnimateGasEstimations';
import { import {
GAS_ESTIMATE_TYPES, GAS_ESTIMATE_TYPES,
@ -27,6 +28,7 @@ import {
hideSidebar, hideSidebar,
updateTransaction, updateTransaction,
} from '../../../store/actions'; } from '../../../store/actions';
import LoadingHeartBeat from '../../ui/loading-heartbeat';
export default function EditGasPopover({ export default function EditGasPopover({
popoverTitle = '', popoverTitle = '',
@ -41,6 +43,8 @@ export default function EditGasPopover({
const dispatch = useDispatch(); const dispatch = useDispatch();
const showSidebar = useSelector((state) => state.appState.sidebar.isOpen); const showSidebar = useSelector((state) => state.appState.sidebar.isOpen);
const shouldAnimate = useShouldAnimateGasEstimations();
const showEducationButton = const showEducationButton =
mode === EDIT_GAS_MODES.MODIFY_IN_PLACE && process.env.SHOW_EIP_1559_UI; mode === EDIT_GAS_MODES.MODIFY_IN_PLACE && process.env.SHOW_EIP_1559_UI;
const [showEducationContent, setShowEducationContent] = useState(false); const [showEducationContent, setShowEducationContent] = useState(false);
@ -181,45 +185,48 @@ export default function EditGasPopover({
) )
} }
> >
<div style={{ padding: '0 20px 20px 20px' }}> <div style={{ padding: '0 20px 20px 20px', position: 'relative' }}>
{showEducationContent ? ( {showEducationContent ? (
<EditGasDisplayEducation /> <EditGasDisplayEducation />
) : ( ) : (
<EditGasDisplay <>
showEducationButton={showEducationButton} <LoadingHeartBeat active={shouldAnimate} />
warning={warning} <EditGasDisplay
showAdvancedForm={showAdvancedForm} showEducationButton={showEducationButton}
setShowAdvancedForm={setShowAdvancedForm} warning={warning}
dappSuggestedGasFeeAcknowledged={dappSuggestedGasFeeAcknowledged} showAdvancedForm={showAdvancedForm}
setDappSuggestedGasFeeAcknowledged={ setShowAdvancedForm={setShowAdvancedForm}
setDappSuggestedGasFeeAcknowledged dappSuggestedGasFeeAcknowledged={dappSuggestedGasFeeAcknowledged}
} setDappSuggestedGasFeeAcknowledged={
maxPriorityFeePerGas={maxPriorityFeePerGas} setDappSuggestedGasFeeAcknowledged
setMaxPriorityFeePerGas={setMaxPriorityFeePerGas} }
maxPriorityFeePerGasFiat={maxPriorityFeePerGasFiat} maxPriorityFeePerGas={maxPriorityFeePerGas}
maxFeePerGas={maxFeePerGas} setMaxPriorityFeePerGas={setMaxPriorityFeePerGas}
setMaxFeePerGas={setMaxFeePerGas} maxPriorityFeePerGasFiat={maxPriorityFeePerGasFiat}
maxFeePerGasFiat={maxFeePerGasFiat} maxFeePerGas={maxFeePerGas}
estimatedMaximumNative={estimatedMaximumNative} setMaxFeePerGas={setMaxFeePerGas}
isGasEstimatesLoading={isGasEstimatesLoading} maxFeePerGasFiat={maxFeePerGasFiat}
gasFeeEstimates={gasFeeEstimates} estimatedMaximumNative={estimatedMaximumNative}
gasEstimateType={gasEstimateType} isGasEstimatesLoading={isGasEstimatesLoading}
gasPrice={gasPrice} gasFeeEstimates={gasFeeEstimates}
setGasPrice={setGasPrice} gasEstimateType={gasEstimateType}
gasLimit={gasLimit} gasPrice={gasPrice}
setGasLimit={setGasLimit} setGasPrice={setGasPrice}
estimateToUse={estimateToUse} gasLimit={gasLimit}
setEstimateToUse={setEstimateToUse} setGasLimit={setGasLimit}
estimatedMinimumFiat={estimatedMinimumFiat} estimateToUse={estimateToUse}
estimatedMaximumFiat={estimatedMaximumFiat} setEstimateToUse={setEstimateToUse}
hasGasErrors={hasGasErrors} estimatedMinimumFiat={estimatedMinimumFiat}
onEducationClick={() => setShowEducationContent(true)} estimatedMaximumFiat={estimatedMaximumFiat}
mode={mode} onEducationClick={() => setShowEducationContent(true)}
transaction={transaction} mode={mode}
gasErrors={gasErrors} transaction={transaction}
onManualChange={onManualChange} hasGasErrors={hasGasErrors}
{...editGasDisplayProps} gasErrors={gasErrors}
/> onManualChange={onManualChange}
{...editGasDisplayProps}
/>
</>
)} )}
</div> </div>
</Popover> </Popover>

View File

@ -1,4 +1,6 @@
.transaction-detail { .transaction-detail {
position: relative;
.transaction-detail-edit { .transaction-detail-edit {
text-align: end; text-align: end;
padding-top: 20px; padding-top: 20px;

View File

@ -1,20 +1,19 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames';
import { I18nContext } from '../../../contexts/i18n'; import { I18nContext } from '../../../contexts/i18n';
import { useShouldAnimateGasEstimations } from '../../../hooks/useShouldAnimateGasEstimations';
import TransactionDetailItem from '../transaction-detail-item/transaction-detail-item.component'; import TransactionDetailItem from '../transaction-detail-item/transaction-detail-item.component';
import LoadingHeartBeat from '../../ui/loading-heartbeat';
export default function TransactionDetail({ rows = [], onEdit }) { export default function TransactionDetail({ rows = [], onEdit }) {
const t = useContext(I18nContext); const t = useContext(I18nContext);
const shouldAnimate = useShouldAnimateGasEstimations();
return ( return (
<div <div className="transaction-detail">
className={classNames('transaction-detail', { <LoadingHeartBeat active={shouldAnimate} />
'transaction-detail--editable': Boolean(onEdit),
})}
>
{onEdit && ( {onEdit && (
<div className="transaction-detail-edit"> <div className="transaction-detail-edit">
<button onClick={onEdit}>{t('edit')}</button> <button onClick={onEdit}>{t('edit')}</button>

View File

@ -0,0 +1,36 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React, { useEffect, useRef } from 'react';
export default function LoadingHeartBeat({ active }) {
const heartNode = useRef(null);
const LOADING_CLASS = 'loading-heartbeat--active';
// When the loading animation completes, remove the className to disappear again
useEffect(() => {
const eventName = 'animationend';
const node = heartNode?.current;
const eventHandler = () => {
node?.classList.remove(LOADING_CLASS);
};
node?.addEventListener(eventName, eventHandler);
return () => {
node?.removeEventListener(eventName, eventHandler);
};
}, [heartNode]);
return (
<div
className={classNames('loading-heartbeat', {
[LOADING_CLASS]: active,
})}
ref={heartNode}
></div>
);
}
LoadingHeartBeat.propTypes = {
active: PropTypes.bool,
};

View File

@ -0,0 +1,23 @@
.loading-heartbeat {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
opacity: 0;
background: #fff;
display: none;
&--active {
display: block;
animation: heartbeat 2s ease-in-out;
}
}
@keyframes heartbeat {
0% { opacity: 0; }
25% { opacity: 1; }
50% { opacity: 0.5; }
75% { opacity: 1; }
100% { opacity: 0; }
}

View File

@ -31,6 +31,7 @@
@import 'identicon/index'; @import 'identicon/index';
@import 'info-tooltip/index'; @import 'info-tooltip/index';
@import 'list-item/index'; @import 'list-item/index';
@import 'loading-heartbeat/index';
@import 'loading-indicator/loading-indicator'; @import 'loading-indicator/loading-indicator';
@import 'loading-screen/index'; @import 'loading-screen/index';
@import 'menu/menu'; @import 'menu/menu';

View File

@ -0,0 +1,28 @@
import { useRef } from 'react';
import { isEqual } from 'lodash';
import { useGasFeeEstimates } from './useGasFeeEstimates';
export function useShouldAnimateGasEstimations() {
const { isGasEstimatesLoading, gasFeeEstimates } = useGasFeeEstimates();
// Do the animation only when gas prices have changed...
const lastGasEstimates = useRef(gasFeeEstimates);
const gasEstimatesChanged = !isEqual(
lastGasEstimates.current,
gasFeeEstimates,
);
// ... and only if gas didn't just load
// Removing this line will cause the initial loading screen to stay empty
const gasJustLoaded = isEqual(lastGasEstimates.current, {});
if (gasEstimatesChanged) {
lastGasEstimates.current = gasFeeEstimates;
}
const showLoadingAnimation =
isGasEstimatesLoading || (gasEstimatesChanged && !gasJustLoaded);
return showLoadingAnimation;
}