-
Notifications
You must be signed in to change notification settings - Fork 81
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: update homepage to typescript and hooks (#921)
## High Level Overview of Change Update homepage to typescript and hooks and break out aspects of the homepage's scrolling ledgers into smaller components. This also adds a new hook `useTooltip` that cleans up tooltip logic when many places are updating tooltips. A small change to move the transaction iteration into its own component reduced render times of the homepage due by between 30ms to 60ms each re-render. ### Type of Change - [x] Refactor (non-breaking change that only restructures code) ### TypeScript/Hooks Update - [x] Updated files to React Hooks - [x] Updated files to TypeScript ## Future Work Optimize the <Streams> component into a hook that causes way fewer re-renders.
- Loading branch information
Showing
23 changed files
with
698 additions
and
516 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { useTranslation } from 'react-i18next' | ||
import SuccessIcon from '../shared/images/success.svg' | ||
import { LedgerEntryValidator } from './LedgerEntryValidator' | ||
import { LedgerEntryHashTrustedCount } from './LedgerEntryHashTrustedCount' | ||
import { ValidatorResponse } from './types' | ||
|
||
export const LedgerEntryHash = ({ | ||
hash, | ||
unlCount, | ||
validators, | ||
}: { | ||
hash: any | ||
unlCount?: number | ||
validators: { [pubkey: string]: ValidatorResponse } | ||
}) => { | ||
const { t } = useTranslation() | ||
const shortHash = hash.hash.substr(0, 6) | ||
const barStyle = { background: `#${shortHash}` } | ||
const validated = hash.validated && <SuccessIcon className="validated" /> | ||
return ( | ||
<div | ||
className={`hash ${hash.unselected ? 'unselected' : ''}`} | ||
key={hash.hash} | ||
> | ||
<div className="bar" style={barStyle} /> | ||
<div className="ledger-hash"> | ||
<div className="hash-concat">{hash.hash.substr(0, 6)}</div> | ||
{validated} | ||
</div> | ||
<div className="subtitle"> | ||
<div className="validation-total"> | ||
<div>{t('total')}:</div> | ||
<b>{hash.validations.length}</b> | ||
</div> | ||
<LedgerEntryHashTrustedCount | ||
hash={hash} | ||
unlCount={unlCount} | ||
validators={validators} | ||
/> | ||
</div> | ||
<div className="validations"> | ||
{hash.validations.map((validation, i) => ( | ||
<LedgerEntryValidator | ||
validators={validators} | ||
validator={validation} | ||
index={i} | ||
key={validation.cookie} | ||
/> | ||
))} | ||
</div> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { useTranslation } from 'react-i18next' | ||
import { useTooltip } from '../shared/components/Tooltip' | ||
import { Hash, ValidatorResponse } from './types' | ||
|
||
export const LedgerEntryHashTrustedCount = ({ | ||
hash, | ||
unlCount, | ||
validators, | ||
}: { | ||
hash: Hash | ||
unlCount?: number | ||
validators: { [pubkey: string]: ValidatorResponse } | ||
}) => { | ||
const { t } = useTranslation() | ||
const { hideTooltip, showTooltip } = useTooltip() | ||
const className = hash.trusted_count < (unlCount || 0) ? 'missed' : '' | ||
|
||
const getMissingValidators = () => { | ||
const unl = {} | ||
|
||
Object.keys(validators).forEach((pubkey) => { | ||
if (validators[pubkey].unl) { | ||
unl[pubkey] = false | ||
} | ||
}) | ||
|
||
hash.validations.forEach((v) => { | ||
if (unl[v.pubkey] !== undefined) { | ||
delete unl[v.pubkey] | ||
} | ||
}) | ||
|
||
return Object.keys(unl).map((pubkey) => validators[pubkey]) | ||
} | ||
|
||
const missing = | ||
hash.trusted_count && className === 'missed' ? getMissingValidators() : null | ||
|
||
return hash.trusted_count ? ( | ||
<span | ||
tabIndex={0} | ||
role="button" | ||
className={className} | ||
onMouseMove={(e) => | ||
missing && missing.length && showTooltip('missing', e, { missing }) | ||
} | ||
onFocus={() => {}} | ||
onKeyUp={() => {}} | ||
onMouseLeave={() => hideTooltip()} | ||
> | ||
<div>{t('unl')}:</div> | ||
<b> | ||
{hash.trusted_count}/{unlCount} | ||
</b> | ||
</span> | ||
) : null | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import classNames from 'classnames' | ||
import { getAction, getCategory } from '../shared/components/Transaction' | ||
import { TRANSACTION_ROUTE } from '../App/routes' | ||
import { TransactionActionIcon } from '../shared/components/TransactionActionIcon/TransactionActionIcon' | ||
import { RouteLink } from '../shared/routing' | ||
import { useTooltip } from '../shared/components/Tooltip' | ||
import { TransactionSummary } from '../shared/types' | ||
|
||
export const LedgerEntryTransaction = ({ | ||
transaction, | ||
}: { | ||
transaction: TransactionSummary | ||
}) => { | ||
const { hideTooltip, showTooltip } = useTooltip() | ||
|
||
return ( | ||
<RouteLink | ||
key={transaction.hash} | ||
className={classNames( | ||
`txn transaction-type transaction-dot bg`, | ||
`tx-category-${getCategory(transaction.type)}`, | ||
`transaction-action-${getAction(transaction.type)}`, | ||
`${transaction.result}`, | ||
)} | ||
onMouseOver={(e) => showTooltip('tx', e, transaction)} | ||
onFocus={() => {}} | ||
onMouseLeave={() => hideTooltip()} | ||
to={TRANSACTION_ROUTE} | ||
params={{ identifier: transaction.hash }} | ||
> | ||
<TransactionActionIcon type={transaction.type} /> | ||
<span>{transaction.hash}</span> | ||
</RouteLink> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { memo } from 'react' | ||
import { Loader } from '../shared/components/Loader' | ||
import { LedgerEntryTransaction } from './LedgerEntryTransaction' | ||
import { TransactionSummary } from '../shared/types' | ||
|
||
/** | ||
* A separate component to handle iterating over the transactions for a ledger on the homepage. | ||
* It is a separate component so that it can benefit from React's memoization the array only changes once | ||
* when the ledger closes and the call returns will all its transactions | ||
* @param transactions | ||
* @constructor | ||
*/ | ||
export const LedgerEntryTransactions = memo( | ||
({ transactions }: { transactions: TransactionSummary[] }) => ( | ||
<> | ||
{transactions == null && <Loader />} | ||
<div className="transactions"> | ||
{transactions?.map((tx) => ( | ||
<LedgerEntryTransaction transaction={tx} key={tx.hash} /> | ||
))} | ||
</div> | ||
</> | ||
), | ||
(prevProps, nextProps) => | ||
prevProps.transactions && | ||
nextProps.transactions && | ||
prevProps.transactions.length === nextProps.transactions.length, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { useSelectedValidator } from './useSelectedValidator' | ||
import { useTooltip } from '../shared/components/Tooltip' | ||
import { ValidatorResponse } from './types' | ||
|
||
export const LedgerEntryValidator = ({ | ||
validator, | ||
validators, | ||
index, | ||
}: { | ||
validator: any | ||
validators: { [pubkey: string]: ValidatorResponse } | ||
index: number | ||
}) => { | ||
const { showTooltip, hideTooltip } = useTooltip() | ||
const { selectedValidator, setSelectedValidator } = useSelectedValidator() | ||
|
||
const trusted = validator.unl ? 'trusted' : '' | ||
const unselected = selectedValidator ? 'unselected' : '' | ||
const selected = selectedValidator === validator.pubkey ? 'selected' : '' | ||
const className = `validation ${trusted} ${unselected} ${selected} ${validator.pubkey}` | ||
const partial = validator.partial ? <div className="partial" /> : null | ||
|
||
return ( | ||
<div | ||
key={`${validator.pubkey}_${validator.cookie}`} | ||
role="button" | ||
tabIndex={index} | ||
className={className} | ||
onMouseOver={(e) => | ||
showTooltip('validator', e, { | ||
...validator, | ||
v: validators[validator.pubkey], | ||
}) | ||
} | ||
onFocus={() => {}} | ||
onKeyUp={() => {}} | ||
onMouseLeave={() => hideTooltip()} | ||
onClick={() => | ||
setSelectedValidator( | ||
selectedValidator === validator.pubkey ? undefined : validator.pubkey, | ||
) | ||
} | ||
> | ||
{partial} | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import { useTranslation } from 'react-i18next' | ||
import { Ledger, ValidatorResponse } from './types' | ||
import { RouteLink } from '../shared/routing' | ||
import { LEDGER_ROUTE } from '../App/routes' | ||
import { Amount } from '../shared/components/Amount' | ||
import { LedgerEntryHash } from './LedgerEntryHash' | ||
import { LedgerEntryTransactions } from './LedgerEntryTransactions' | ||
import { | ||
Tooltip, | ||
TooltipProvider, | ||
useTooltip, | ||
} from '../shared/components/Tooltip' | ||
|
||
const SIGMA = '\u03A3' | ||
|
||
const LedgerIndex = ({ ledgerIndex }: { ledgerIndex: number }) => { | ||
const { t } = useTranslation() | ||
const flagLedger = ledgerIndex % 256 === 0 | ||
return ( | ||
<div | ||
className={`ledger-index ${flagLedger ? 'flag-ledger' : ''}`} | ||
title={flagLedger ? t('flag_ledger') : ''} | ||
> | ||
<RouteLink to={LEDGER_ROUTE} params={{ identifier: ledgerIndex }}> | ||
{ledgerIndex} | ||
</RouteLink> | ||
</div> | ||
) | ||
} | ||
|
||
export const LedgerListEntryInner = ({ | ||
ledger, | ||
unlCount, | ||
validators, | ||
}: { | ||
ledger: Ledger | ||
unlCount?: number | ||
validators: { [pubkey: string]: ValidatorResponse } | ||
}) => { | ||
const { tooltip } = useTooltip() | ||
const { t } = useTranslation() | ||
const time = ledger.close_time | ||
? new Date(ledger.close_time).toLocaleTimeString() | ||
: null | ||
|
||
return ( | ||
<div className="ledger" key={ledger.ledger_index}> | ||
<div className="ledger-head"> | ||
<LedgerIndex ledgerIndex={ledger.ledger_index} /> | ||
<div className="close-time">{time}</div> | ||
{/* Render Transaction Count (can be 0) */} | ||
{ledger.txn_count !== undefined && ( | ||
<div className="txn-count"> | ||
{t('txn_count')}:<b>{ledger.txn_count.toLocaleString()}</b> | ||
</div> | ||
)} | ||
{/* Render Total Fees (can be 0) */} | ||
{ledger.total_fees !== undefined && ( | ||
<div className="fees"> | ||
{SIGMA} {t('fees')}: | ||
<b> | ||
<Amount value={{ currency: 'XRP', amount: ledger.total_fees }} /> | ||
</b> | ||
</div> | ||
)} | ||
<LedgerEntryTransactions transactions={ledger.transactions} /> | ||
</div> | ||
<div className="hashes"> | ||
{ledger.hashes.map((hash) => ( | ||
<LedgerEntryHash | ||
hash={hash} | ||
key={hash.hash} | ||
unlCount={unlCount} | ||
validators={validators} | ||
/> | ||
))} | ||
</div> | ||
<Tooltip tooltip={tooltip} /> | ||
</div> | ||
) | ||
} | ||
|
||
export const LedgerListEntry = ({ | ||
ledger, | ||
unlCount, | ||
validators, | ||
}: { | ||
ledger: Ledger | ||
unlCount?: number | ||
validators: { [pubkey: string]: ValidatorResponse } | ||
}) => ( | ||
<TooltipProvider> | ||
<LedgerListEntryInner | ||
ledger={ledger} | ||
validators={validators} | ||
unlCount={unlCount} | ||
/> | ||
</TooltipProvider> | ||
) |
Oops, something went wrong.