Adjust Call/Event display (known balance args) (#8569)

* Adjust Call/Event display (known balance args)

* Additional

* Adjust

* smooth

* adjust colors
This commit is contained in:
Jaco 2022-12-18 15:48:03 +02:00 committed by GitHub
parent 081adc934c
commit 1e3cf6094b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 198 additions and 62 deletions

View File

@ -112,6 +112,8 @@ function ExtrinsicDisplay ({ blockNumber, className = '', events, index, maxBloc
[index, events, maxBlockWeight]
);
const callName = `${section}.${method}`;
return (
<tr
className={className}
@ -122,10 +124,11 @@ function ExtrinsicDisplay ({ blockNumber, className = '', events, index, maxBloc
colSpan={2}
>
<Expander
summary={`${section}.${method}`}
summary={callName}
summaryMeta={meta}
>
<Call
callName={callName}
className='details'
mortality={mortality}
tip={value.tip?.toBn()}

View File

@ -13,16 +13,19 @@ interface Props {
}
function Event ({ className = '', value: { event } }: Props): React.ReactElement<Props> {
const eventName = `${event.section}.${event.method}`;
return (
<Expander
className={className}
summary={`${event.section}.${event.method}`}
summary={eventName}
summaryMeta={event.meta}
>
{event.data.length
? (
<EventDisplay
className='details'
eventName={eventName}
value={event}
/>
)

View File

@ -14,5 +14,8 @@
"version": "0.122.3-132-x",
"dependencies": {
"@babel/runtime": "^7.20.6"
},
"devDependencies": {
"chart.js": "^3.9.1"
}
}

View File

@ -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<Props> {
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 && (
<ExpandButton
expanded={isExpanded}
onClick={toggleExpanded}
/>
),
isExpanded && trackInfo && (
<tr>
<th colSpan={9}>Hello expanded</th>
</tr>
)
],
[isExpanded, toggleExpanded, trackInfo]
);
const [header, key] = useMemo(
() => [
[
[trackName ? <>{trackName}<div>{trackInfo?.text}</div></> : t('referenda'), 'start', 5],
[undefined, 'number', 3],
[headerButton]
],
trackName
? `track:${trackName}`
: 'untracked'
],
[headerButton, t, trackInfo, trackName]
);
return (
<Table
className={className}
empty={referenda && t<string>('No active referenda')}
header={[
[trackName ? <>{trackName}<div>{trackInfo?.text}</div></> : 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;
}
`);

View File

@ -49,7 +49,10 @@ function Tuple ({ palletReferenda, value: { id, info, track } }: Props): React.R
return (
<>
<td className='all' />
<td
className='all'
colSpan={4}
/>
<Deposits
canRefund
decision={decision}
@ -62,10 +65,6 @@ function Tuple ({ palletReferenda, value: { id, info, track } }: Props): React.R
label={info.type}
when={when}
/>
<td
className='number'
colSpan={4}
/>
</>
);
}

View File

@ -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<string, React.ComponentType<ReferendumProps>> = {
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

View File

@ -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<PalletRefe
? value.unwrapOr(null)
: value;
}
export function getNumDeciding (referenda?: Referendum[]): number {
if (!referenda) {
return 0;
}
return referenda.filter(({ info }) =>
info.isOngoing &&
info.asOngoing.deciding.isSome
).length;
}

View File

@ -74,6 +74,10 @@ export interface TrackInfo {
text?: string;
}
export interface TrackInfoExt extends TrackInfo {
track: TrackDescription;
}
export interface Lock {
classId: BN;
endBlock: BN;

View File

@ -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<TrackInfoExt>({ 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<BN>(100);
const support = new Array<BN>(100);
const x = new Array<BN>(100);
const step = decisionPeriod.divn(CALC_CURVE_LENGTH);
const last = CALC_CURVE_LENGTH - 1;
const approval = new Array<BN>(CURVE_LENGTH);
const support = new Array<BN>(CURVE_LENGTH);
const x = new Array<BN>(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++) {

View File

@ -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<u128>': 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<Props> {
function Call ({ callName, children, className = '', labelHash, labelSignature, mortality, noIndent, onError, tip, value, withBorder, withHash, withSignature }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const [{ hash, params, signature, signatureType, values }, setExtracted] = useState<Extracted>({ hash: null, params: [], signature: null, signatureType: null, values: [] });
const [{ hash, overrides, params, signature, signatureType, values }, setExtracted] = useState<Extracted>({ 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 (
<div className={`ui--Extrinsic ${className}`}>
<Params
isDisabled
onError={onError}
overrides={overrides}
params={params}
registry={value.registry}
values={values}

View File

@ -29,14 +29,16 @@ function CallExpander ({ children, className = '', labelHash, value, withHash }:
}
const { meta, method, section } = call;
const callName = `${section}.${method}`;
return (
<div className={`ui--CallExpander ${className}`}>
<Expander
summaryHead={<div>{section}.{method}</div>}
summaryHead={<div>{callName}</div>}
summaryMeta={meta}
>
<CallDisplay
callName={callName}
labelHash={labelHash}
value={value}
withHash={withHash}

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import type { DecodedEvent } from '@polkadot/api-contract/types';
import type { ComponentMap } from '@polkadot/react-params/types';
import type { Bytes } from '@polkadot/types';
import type { Event } from '@polkadot/types/interfaces';
import type { Codec } from '@polkadot/types/types';
@ -10,6 +11,7 @@ import React, { useMemo } from 'react';
import { Input } from '@polkadot/react-components';
import Params from '@polkadot/react-params';
import BalanceParam from '@polkadot/react-params/Param/Balance';
import { useTranslation } from './translate';
import { getContractAbi } from './util';
@ -17,6 +19,7 @@ import { getContractAbi } from './util';
export interface Props {
children?: React.ReactNode;
className?: string;
eventName?: string;
value: Event;
}
@ -29,7 +32,19 @@ interface AbiEvent extends DecodedEvent {
values: Value[];
}
function EventDisplay ({ children, className = '', value }: Props): React.ReactElement<Props> {
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<u128>': BalanceParam,
u128: BalanceParam
};
function EventDisplay ({ children, className = '', eventName, value }: Props): React.ReactElement<Props> {
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}
<Params
isDisabled
overrides={overrides}
params={params}
values={values}
>

View File

@ -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<Props> {
const [defaultValue, siDefault] = useMemo(
const { defaultValue, siDefault } = useMemo(
() => reformat(inDefault, isDisabled, siDecimals),
[inDefault, isDisabled, siDecimals]
);

View File

@ -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<Props> | null {
function Head ({ children, className = '', filter, header, isEmpty }: Props): React.ReactElement<Props> | null {
if (!header?.length) {
return null;
}
@ -52,6 +53,7 @@ function Head ({ className = '', filter, header, isEmpty }: Props): React.ReactE
</th>
)}
</tr>
{children}
</thead>
);
}
@ -100,7 +102,8 @@ export default React.memo(styled(Head)`
padding: 0;
}
&.expand {
&.expand,
&.number {
text-align: right;
}

View File

@ -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<TableProps> {
function Table ({ children, className = '', empty, emptySpinner, filter, footer, header, headerChildren, isFixed, isInline, legend, noBodyTag, withCollapsibleRows = false }: TableProps): React.ReactElement<TableProps> {
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}
</Head>
<Body
empty={empty}
emptySpinner={emptySpinner}

View File

@ -24,6 +24,7 @@ function CallDisplay (props: Props): React.ReactElement<Props> {
const call = value as Extrinsic;
const { method, section } = call.registry.findMetaCall(call.callIndex);
const callName = `${section}.${method}`;
return (
<Bare>
@ -32,9 +33,10 @@ function CallDisplay (props: Props): React.ReactElement<Props> {
label={label}
withLabel={withLabel}
>
{section}.{method}
{callName}
</Static>
<Call
callName={callName}
labelHash={t<string>('call hash / {{section}}.{{method}}', { replace: { method, section } })}
noIndent
value={call}

View File

@ -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 {

View File

@ -88,6 +88,8 @@ class Params extends React.PureComponent<Props, State> {
return null;
}
overrides && console.log(overrides);
return (
<Holder
className={className}

View File

@ -3027,6 +3027,7 @@ __metadata:
resolution: "@polkadot/app-referenda@workspace:packages/page-referenda"
dependencies:
"@babel/runtime": ^7.20.6
chart.js: ^3.9.1
languageName: unknown
linkType: soft