From 1e3cf6094bd0dc2a7622eeeccb97b5d6daf32c63 Mon Sep 17 00:00:00 2001 From: Jaco Date: Sun, 18 Dec 2022 15:48:03 +0200 Subject: [PATCH] Adjust Call/Event display (known balance args) (#8569) * Adjust Call/Event display (known balance args) * Additional * Adjust * smooth * adjust colors --- .../page-explorer/src/BlockInfo/Extrinsic.tsx | 5 +- packages/page-explorer/src/Event.tsx | 5 +- packages/page-referenda/package.json | 3 ++ .../page-referenda/src/Referenda/Group.tsx | 53 +++++++++++++++---- .../page-referenda/src/Referenda/RefTuple.tsx | 9 ++-- .../src/Referenda/Referendum.tsx | 24 ++++++--- packages/page-referenda/src/Referenda/util.ts | 12 +++++ packages/page-referenda/src/types.ts | 4 ++ packages/page-referenda/src/util.ts | 33 ++++++------ packages/react-components/src/Call.tsx | 34 +++++++++--- .../react-components/src/CallExpander.tsx | 4 +- packages/react-components/src/Event.tsx | 25 ++++++++- .../react-components/src/InputBalance.tsx | 26 +++++---- packages/react-components/src/Table/Head.tsx | 7 ++- packages/react-components/src/Table/index.tsx | 7 ++- packages/react-params/src/Param/Call.tsx | 4 +- .../react-params/src/Param/findComponent.ts | 2 +- packages/react-params/src/index.tsx | 2 + yarn.lock | 1 + 19 files changed, 198 insertions(+), 62 deletions(-) diff --git a/packages/page-explorer/src/BlockInfo/Extrinsic.tsx b/packages/page-explorer/src/BlockInfo/Extrinsic.tsx index dbba94334b..a196e1e094 100644 --- a/packages/page-explorer/src/BlockInfo/Extrinsic.tsx +++ b/packages/page-explorer/src/BlockInfo/Extrinsic.tsx @@ -112,6 +112,8 @@ function ExtrinsicDisplay ({ blockNumber, className = '', events, index, maxBloc [index, events, maxBlockWeight] ); + const callName = `${section}.${method}`; + return ( { + const eventName = `${event.section}.${event.method}`; + return ( {event.data.length ? ( ) diff --git a/packages/page-referenda/package.json b/packages/page-referenda/package.json index 8ab8151d2e..3b36dc98b6 100644 --- a/packages/page-referenda/package.json +++ b/packages/page-referenda/package.json @@ -14,5 +14,8 @@ "version": "0.122.3-132-x", "dependencies": { "@babel/runtime": "^7.20.6" + }, + "devDependencies": { + "chart.js": "^3.9.1" } } diff --git a/packages/page-referenda/src/Referenda/Group.tsx b/packages/page-referenda/src/Referenda/Group.tsx index 975c698aa6..a4bf636e96 100644 --- a/packages/page-referenda/src/Referenda/Group.tsx +++ b/packages/page-referenda/src/Referenda/Group.tsx @@ -7,8 +7,8 @@ import type { PalletReferenda, PalletVote, ReferendaGroup, TrackDescription } fr import React, { useMemo } from 'react'; import styled from 'styled-components'; -import { Table } from '@polkadot/react-components'; -import { useApi } from '@polkadot/react-hooks'; +import { ExpandButton, Table } from '@polkadot/react-components'; +import { useApi, useToggle } from '@polkadot/react-hooks'; import { useTranslation } from '../translate'; import { getTrackInfo } from '../util'; @@ -28,25 +28,51 @@ interface Props extends ReferendaGroup { function Group ({ activeIssuance, className, isMember, members, palletReferenda, palletVote, ranks, referenda, trackId, trackName, tracks }: Props): React.ReactElement { const { t } = useTranslation(); const { api, specName } = useApi(); + const [isExpanded, toggleExpanded] = useToggle(); const trackInfo = useMemo( () => getTrackInfo(api, specName, palletReferenda, tracks, trackId?.toNumber()), [api, specName, palletReferenda, tracks, trackId] ); + const [headerButton, headerChildren] = useMemo( + () => [ + false && trackInfo && ( + + ), + isExpanded && trackInfo && ( + + Hello expanded + + ) + ], + [isExpanded, toggleExpanded, trackInfo] + ); + + const [header, key] = useMemo( + () => [ + [ + [trackName ? <>{trackName}
{trackInfo?.text}
: t('referenda'), 'start', 5], + [undefined, 'number', 3], + [headerButton] + ], + trackName + ? `track:${trackName}` + : 'untracked' + ], + [headerButton, t, trackInfo, trackName] + ); + return ( ('No active referenda')} - header={[ - [trackName ? <>{trackName}
{trackInfo?.text}
: t('referenda'), 'start', 7], - [undefined, undefined, 3] - ]} - key={ - trackName - ? `track:${trackName}` - : 'untracked' - } + header={header} + headerChildren={headerChildren} + key={key} withCollapsibleRows > {referenda && referenda.map((r) => ( @@ -73,4 +99,9 @@ export default React.memo(styled(Group)` padding-left: 1.5rem; text-overflow: ellipsis; } + + th.number h1 { + display: inline-block; + opacity: 0.75; + } `); diff --git a/packages/page-referenda/src/Referenda/RefTuple.tsx b/packages/page-referenda/src/Referenda/RefTuple.tsx index 2b15170ac8..189a9944e7 100644 --- a/packages/page-referenda/src/Referenda/RefTuple.tsx +++ b/packages/page-referenda/src/Referenda/RefTuple.tsx @@ -49,7 +49,10 @@ function Tuple ({ palletReferenda, value: { id, info, track } }: Props): React.R return ( <> - + {children} ); } @@ -100,7 +102,8 @@ export default React.memo(styled(Head)` padding: 0; } - &.expand { + &.expand, + &.number { text-align: right; } diff --git a/packages/react-components/src/Table/index.tsx b/packages/react-components/src/Table/index.tsx index a3b083ad53..9372bef3c5 100644 --- a/packages/react-components/src/Table/index.tsx +++ b/packages/react-components/src/Table/index.tsx @@ -16,6 +16,7 @@ interface TableProps { filter?: React.ReactNode; footer?: React.ReactNode; header?: [React.ReactNode?, string?, number?, (() => void)?][]; + headerChildren?: React.ReactNode; isFixed?: boolean; isInline?: boolean; legend?: React.ReactNode; @@ -34,7 +35,7 @@ function extractBodyChildren (children: React.ReactNode): [boolean, React.ReactN return [isEmpty, isEmpty ? null : kids]; } -function Table ({ children, className = '', empty, emptySpinner, filter, footer, header, isFixed, isInline, legend, noBodyTag, withCollapsibleRows = false }: TableProps): React.ReactElement { +function Table ({ children, className = '', empty, emptySpinner, filter, footer, header, headerChildren, isFixed, isInline, legend, noBodyTag, withCollapsibleRows = false }: TableProps): React.ReactElement { const [isEmpty, bodyChildren] = extractBodyChildren(children); return ( @@ -45,7 +46,9 @@ function Table ({ children, className = '', empty, emptySpinner, filter, footer, filter={filter} header={header} isEmpty={isEmpty} - /> + > + {headerChildren} + { const call = value as Extrinsic; const { method, section } = call.registry.findMetaCall(call.callIndex); + const callName = `${section}.${method}`; return ( @@ -32,9 +33,10 @@ function CallDisplay (props: Props): React.ReactElement { label={label} withLabel={withLabel} > - {section}.{method} + {callName} ('call hash / {{section}}.{{method}}', { replace: { method, section } })} noIndent value={call} diff --git a/packages/react-params/src/Param/findComponent.ts b/packages/react-params/src/Param/findComponent.ts index c64c98b825..b4af99ef2c 100644 --- a/packages/react-params/src/Param/findComponent.ts +++ b/packages/react-params/src/Param/findComponent.ts @@ -148,7 +148,7 @@ export default function findComponent (registry: Registry, def: TypeDef, overrid : null; const type = fromDef(def); - let Component = findOne(def.lookupName) || findOne(type); + let Component = findOne(def.lookupName) || findOne(def.type) || findOne(type); if (!Component) { try { diff --git a/packages/react-params/src/index.tsx b/packages/react-params/src/index.tsx index a2a8f31b27..56dc86f2da 100644 --- a/packages/react-params/src/index.tsx +++ b/packages/react-params/src/index.tsx @@ -88,6 +88,8 @@ class Params extends React.PureComponent { return null; } + overrides && console.log(overrides); + return (
+ - ); } diff --git a/packages/page-referenda/src/Referenda/Referendum.tsx b/packages/page-referenda/src/Referenda/Referendum.tsx index 7a38200038..344f42726b 100644 --- a/packages/page-referenda/src/Referenda/Referendum.tsx +++ b/packages/page-referenda/src/Referenda/Referendum.tsx @@ -1,6 +1,7 @@ // Copyright 2017-2022 @polkadot/app-referenda authors & contributors // SPDX-License-Identifier: Apache-2.0 +import type { ChartOptions } from 'chart.js'; import type { PalletConvictionVotingTally, PalletRankedCollectiveTally, PalletReferendaReferendumInfoConvictionVotingTally, PalletReferendaReferendumInfoRankedCollectiveTally } from '@polkadot/types/lookup'; import type { BN } from '@polkadot/util'; import type { BaseReferendumProps as Props, CurveGraph, ReferendumProps } from '../types'; @@ -22,17 +23,27 @@ const COMPONENTS: Record> = { Ongoing }; -const VAL_COLORS = ['rgba(140, 140, 140, 0.65)', '#9c3333', '#339c33']; +const VAL_COLORS = ['#ff8c00', '#9c3333', '#339c33']; const BOX_COLORS = ['rgba(140, 140, 140, 0.2)', 'rgba(140, 0, 0, 0.2)', 'rgba(0, 140, 0, 0.2)']; const PT_CUR = 0; const PT_NEG = 1; const PT_POS = 2; -const OPTIONS = { +const OPTIONS: ChartOptions = { animation: { duration: 0 }, aspectRatio: 2.25, + elements: { + point: { + hoverRadius: 6, + radius: 0 + } + }, + interaction: { + intersect: false, + mode: 'index' + }, maintainAspectRatio: true, scales: { y: { @@ -115,12 +126,13 @@ function getChartResult (totalEligible: BN, isConvictionVote: boolean, info: Pal function getChartProps (bestNumber: BN, chartProps: ChartResultExt[]): ChartProps[] { return chartProps.map(({ changeX, currentY, labels, progress, since, trackGraph: { x }, values }, index): ChartProps => { - const currentX = 100 * ( + const maxX = labels.length; + const currentX = maxX * ( bestNumber.sub(since.add(x[0])).toNumber() / x[x.length - 1].sub(x[0]).toNumber() ); const swapX = changeX === -1 ? -1 - : 100 * (changeX / x.length); + : maxX * (changeX / x.length); return { colors: VAL_COLORS, @@ -135,7 +147,7 @@ function getChartProps (bestNumber: BN, chartProps: ChartResultExt[]): ChartProp borderWidth: 0, type: 'box', xMax: swapX === -1 - ? 100 + ? maxX : swapX, xMin: 0, yMax: currentY, @@ -159,7 +171,7 @@ function getChartProps (bestNumber: BN, chartProps: ChartResultExt[]): ChartProp backgroundColor: BOX_COLORS[2], borderWidth: 0, type: 'box', - xMax: 100, + xMax: maxX, xMin: swapX, yMax: currentY, yMin: 0 diff --git a/packages/page-referenda/src/Referenda/util.ts b/packages/page-referenda/src/Referenda/util.ts index f4806efc59..1da935ed96 100644 --- a/packages/page-referenda/src/Referenda/util.ts +++ b/packages/page-referenda/src/Referenda/util.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import type { PalletReferendaDeposit } from '@polkadot/types/lookup'; +import type { Referendum } from '../types'; import { Option } from '@polkadot/types'; @@ -10,3 +11,14 @@ export function unwrapDeposit (value: PalletReferendaDeposit | Option + info.isOngoing && + info.asOngoing.deciding.isSome + ).length; +} diff --git a/packages/page-referenda/src/types.ts b/packages/page-referenda/src/types.ts index 5424a74d15..67a89d6791 100644 --- a/packages/page-referenda/src/types.ts +++ b/packages/page-referenda/src/types.ts @@ -74,6 +74,10 @@ export interface TrackInfo { text?: string; } +export interface TrackInfoExt extends TrackInfo { + track: TrackDescription; +} + export interface Lock { classId: BN; endBlock: BN; diff --git a/packages/page-referenda/src/util.ts b/packages/page-referenda/src/util.ts index 5e85e0ed96..69bf945aee 100644 --- a/packages/page-referenda/src/util.ts +++ b/packages/page-referenda/src/util.ts @@ -3,12 +3,12 @@ import type { ApiPromise } from '@polkadot/api'; import type { PalletConvictionVotingTally, PalletRankedCollectiveTally, PalletReferendaCurve, PalletReferendaReferendumInfoConvictionVotingTally, PalletReferendaReferendumInfoRankedCollectiveTally, PalletReferendaTrackInfo } from '@polkadot/types/lookup'; -import type { CurveGraph, TrackDescription, TrackInfo } from './types'; +import type { CurveGraph, TrackDescription, TrackInfoExt } from './types'; import { getGovernanceTracks } from '@polkadot/apps-config'; -import { BN, BN_BILLION, BN_ONE, BN_ZERO, bnMax, bnMin, formatNumber, stringPascalCase } from '@polkadot/util'; +import { BN, BN_BILLION, BN_ONE, BN_ZERO, bnMax, bnMin, formatNumber, objectSpread, stringPascalCase } from '@polkadot/util'; -const CALC_CURVE_LENGTH = 100; +const CURVE_LENGTH = 500; export function getTrackName (trackId: BN, { name }: PalletReferendaTrackInfo): string { return `${ @@ -22,20 +22,23 @@ export function getTrackName (trackId: BN, { name }: PalletReferendaTrackInfo): }`; } -export function getTrackInfo (api: ApiPromise, specName: string, palletReferenda: string, tracks?: TrackDescription[], trackId?: number): TrackInfo | undefined { - let info: TrackInfo | undefined; +export function getTrackInfo (api: ApiPromise, specName: string, palletReferenda: string, tracks?: TrackDescription[], trackId?: number): TrackInfoExt | undefined { + let info: TrackInfoExt | undefined; if (tracks && trackId !== undefined) { const originMap = getGovernanceTracks(api, specName, palletReferenda); - const trackInfo = tracks.find(({ id }) => id.eqn(trackId)); + const track = tracks.find(({ id }) => id.eqn(trackId)); - if (trackInfo && originMap) { - const trackName = trackInfo.info.name.toString(); - - info = originMap.find(({ id, name }) => + if (track && originMap) { + const trackName = track.info.name.toString(); + const base = originMap.find(({ id, name }) => id === trackId && name === trackName ); + + if (base) { + info = objectSpread({ track }, base); + } } } @@ -202,11 +205,11 @@ export function calcDecidingEnd (totalEligible: BN, tally: PalletRankedCollectiv } export function calcCurves ({ decisionPeriod, minApproval, minSupport }: PalletReferendaTrackInfo): CurveGraph { - const approval = new Array(100); - const support = new Array(100); - const x = new Array(100); - const step = decisionPeriod.divn(CALC_CURVE_LENGTH); - const last = CALC_CURVE_LENGTH - 1; + const approval = new Array(CURVE_LENGTH); + const support = new Array(CURVE_LENGTH); + const x = new Array(CURVE_LENGTH); + const step = decisionPeriod.divn(CURVE_LENGTH); + const last = CURVE_LENGTH - 1; let current = new BN(0); for (let i = 0; i < last; i++) { diff --git a/packages/react-components/src/Call.tsx b/packages/react-components/src/Call.tsx index da40be19c4..0163542676 100644 --- a/packages/react-components/src/Call.tsx +++ b/packages/react-components/src/Call.tsx @@ -1,6 +1,7 @@ // Copyright 2017-2022 @polkadot/react-components authors & contributors // SPDX-License-Identifier: Apache-2.0 +import type { ComponentMap } from '@polkadot/react-params/types'; import type { ExtrinsicSignature } from '@polkadot/types/interfaces'; import type { Codec, IExtrinsic, IMethod, TypeDef } from '@polkadot/types/types'; import type { BN } from '@polkadot/util'; @@ -9,6 +10,7 @@ import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import Params from '@polkadot/react-params'; +import BalanceParam from '@polkadot/react-params/Param/Balance'; import { FormatBalance } from '@polkadot/react-query'; import { Enum, getTypeDef } from '@polkadot/types'; @@ -16,6 +18,7 @@ import Static from './Static'; import { useTranslation } from './translate'; export interface Props { + callName?: string; children?: React.ReactNode; className?: string; labelHash?: React.ReactNode; @@ -42,12 +45,27 @@ interface Value { interface Extracted { hash: string | null; + overrides?: ComponentMap; params: Param[]; signature: string | null; signatureType: string | null; values: Value[]; } +const BALANCE_CALLS = [ + 'balances.forceTransfer', 'balances.forceUnreserve', 'balances.setBalance', 'balances.transfer', 'balances.transferKeepAlive', + 'crowdloan.contribute', 'crowdloan.create', 'crowdloan.edit', + 'democracy.delegate', 'democracy.propose', + 'nominationPools.bondExtra', 'nominationPools.join', 'nominationPools.unbond', + 'staking.bond', 'staking.bondExtra', 'staking.rebond', 'staking.unbond', + 'treasury.proposeSpend', 'treasury.spend', + 'vesting.forceVestedTransfer', 'vesting.vestedTransfer' +]; + +const BALANCE_OVERRIDE: ComponentMap = { + 'Compact': BalanceParam +}; + function isExtrinsic (value: IExtrinsic | IMethod): value is IExtrinsic { return !!(value as IExtrinsic).signature; } @@ -58,7 +76,10 @@ function getRawSignature (value: IExtrinsic): ExtrinsicSignature | undefined { return (value as any)._raw?.signature?.multiSignature as ExtrinsicSignature; } -function extractState (value: IExtrinsic | IMethod, withHash?: boolean, withSignature?: boolean): Extracted { +function extractState (value: IExtrinsic | IMethod, withHash?: boolean, withSignature?: boolean, callName?: string): Extracted { + const overrides = callName && BALANCE_CALLS.includes(callName) + ? BALANCE_OVERRIDE + : undefined; const params = value.meta.args.map(({ name, type }): Param => ({ name: name.toString(), type: getTypeDef(type.toString()) @@ -82,22 +103,23 @@ function extractState (value: IExtrinsic | IMethod, withHash?: boolean, withSign : null; } - return { hash, params, signature, signatureType, values }; + return { hash, overrides, params, signature, signatureType, values }; } -function Call ({ children, className = '', labelHash, labelSignature, mortality, noIndent, onError, tip, value, withBorder, withHash, withSignature }: Props): React.ReactElement { +function Call ({ callName, children, className = '', labelHash, labelSignature, mortality, noIndent, onError, tip, value, withBorder, withHash, withSignature }: Props): React.ReactElement { const { t } = useTranslation(); - const [{ hash, params, signature, signatureType, values }, setExtracted] = useState({ hash: null, params: [], signature: null, signatureType: null, values: [] }); + const [{ hash, overrides, params, signature, signatureType, values }, setExtracted] = useState({ hash: null, params: [], signature: null, signatureType: null, values: [] }); useEffect((): void => { - setExtracted(extractState(value, withHash, withSignature)); - }, [value, withHash, withSignature]); + setExtracted(extractState(value, withHash, withSignature, callName)); + }, [callName, value, withHash, withSignature]); return (
{section}.{method}
} + summaryHead={
{callName}
} summaryMeta={meta} > { +const BALANCE_EVENTS = [ + 'balances.Deposit', 'balances.Endowed', 'balances.Transfer', 'balances.Withdraw', + 'staking.Bonded', 'staking.Rewarded', 'staking.Unbonded', 'staking.Withdrawn', + 'transactionPayment.TransactionFeePaid', + 'treasury.Deposit' +]; + +const BALANCE_OVERRIDE: ComponentMap = { + 'Compact': BalanceParam, + u128: BalanceParam +}; + +function EventDisplay ({ children, className = '', eventName, value }: Props): React.ReactElement { const { t } = useTranslation(); const names = value.data.names; const params = value.typeDef.map((type, i) => ({ @@ -38,6 +53,13 @@ function EventDisplay ({ children, className = '', value }: Props): React.ReactE })); const values = value.data.map((value) => ({ isValid: true, value })); + const overrides = useMemo( + () => eventName && BALANCE_EVENTS.includes(eventName) + ? BALANCE_OVERRIDE + : undefined, + [eventName] + ); + const abiEvent = useMemo( (): AbiEvent | null => { // for contracts, we decode the actual event @@ -72,6 +94,7 @@ function EventDisplay ({ children, className = '', value }: Props): React.ReactE {children} diff --git a/packages/react-components/src/InputBalance.tsx b/packages/react-components/src/InputBalance.tsx index 5a801a80f4..888b9edbf5 100644 --- a/packages/react-components/src/InputBalance.tsx +++ b/packages/react-components/src/InputBalance.tsx @@ -8,7 +8,7 @@ import React, { useMemo } from 'react'; import styled from 'styled-components'; import { BitLengthOption } from '@polkadot/react-components/constants'; -import { BN, formatBalance, isUndefined } from '@polkadot/util'; +import { BN, BN_TEN, formatBalance, isString, isUndefined } from '@polkadot/util'; import InputNumber from './InputNumber'; @@ -40,26 +40,34 @@ interface Props { const DEFAULT_BITLENGTH = BitLengthOption.CHAIN_SPEC as BitLength; -function reformat (value?: string | BN, isDisabled?: boolean, siDecimals?: number): [string?, SiDef?] { +function reformat (value?: string | BN, isDisabled?: boolean, siDecimals?: number): { defaultValue?: string; siDefault?: SiDef } { if (!value) { - return []; + return {}; } const decimals = isUndefined(siDecimals) ? formatBalance.getDefaults().decimals : siDecimals; - const si = isDisabled + const maxDisabled = BN_TEN.pow(new BN(decimals - 1)).toString(10); + const siDefault = isDisabled && isString(value) && (value.length < maxDisabled.length) && value !== '0' ? formatBalance.calcSi(value.toString(), decimals) : formatBalance.findSi('-'); + const defaultValue = formatBalance(value, { + decimals, + forceUnit: siDefault.value, + withSi: false + }); - return [ - formatBalance(value, { decimals, forceUnit: si.value, withSi: false }).replace(/,/g, isDisabled ? ',' : ''), - si - ]; + return { + defaultValue: isDisabled + ? defaultValue + : defaultValue.replace(/,/g, isDisabled ? ',' : ''), + siDefault + }; } function InputBalance ({ autoFocus, children, className = '', defaultValue: inDefault, help, isDisabled, isError, isFull, isWarning, isZeroable, label, labelExtra, maxValue, onChange, onEnter, onEscape, placeholder, siDecimals, siSymbol, value, withEllipsis, withLabel, withMax }: Props): React.ReactElement { - const [defaultValue, siDefault] = useMemo( + const { defaultValue, siDefault } = useMemo( () => reformat(inDefault, isDisabled, siDecimals), [inDefault, isDisabled, siDecimals] ); diff --git a/packages/react-components/src/Table/Head.tsx b/packages/react-components/src/Table/Head.tsx index a8293f0387..ffa8cdb803 100644 --- a/packages/react-components/src/Table/Head.tsx +++ b/packages/react-components/src/Table/Head.tsx @@ -9,13 +9,14 @@ import Icon from '../Icon'; type HeaderDef = [React.ReactNode?, string?, number?, (() => void)?]; interface Props { + children?: React.ReactNode; className?: string; filter?: React.ReactNode; header?: (null | undefined | HeaderDef)[]; isEmpty: boolean; } -function Head ({ className = '', filter, header, isEmpty }: Props): React.ReactElement | null { +function Head ({ children, className = '', filter, header, isEmpty }: Props): React.ReactElement | null { if (!header?.length) { return null; } @@ -52,6 +53,7 @@ function Head ({ className = '', filter, header, isEmpty }: Props): React.ReactE )}