Format track selection options (#8555)

* Format track selection options

* Adjust submit dialog
This commit is contained in:
Jaco 2022-12-16 10:06:35 +02:00 committed by GitHub
parent 707753c75e
commit b6802bfca0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 144 additions and 102 deletions

View File

@ -29,7 +29,9 @@ const SPEND_LIMITS = {
};
function formatSpend (mul: number, value: BN): string {
return formatBalance(value.muln(mul), FMT_OPTS);
// We lose the decimals here... depending on chain config, this could be non-optimal
// (A simple formatBalance(value.muln(mul), FMT_OPTS) formats to 4 decimals)
return `${formatBalance(value.muln(mul), FMT_OPTS).split('.')[0]} ${FMT_OPTS.withUnit}`;
}
function compareFellowshipRank (trackId: number): (rank: BN) => boolean {
@ -124,7 +126,8 @@ export const kusama: Record<string, TrackInfo[]> = {
{
id: 0,
name: 'root',
origin: { system: 'Root' }
origin: { system: 'Root' },
text: 'Origin for the system root'
},
{
id: 1,

View File

@ -1,6 +1,7 @@
// Copyright 2017-2022 @polkadot/app-referenda authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ApiPromise } from '@polkadot/api';
import type { RawParam } from '@polkadot/react-params/types';
import type { BN } from '@polkadot/util';
import type { HexString } from '@polkadot/util/types';
@ -10,12 +11,12 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import styled from 'styled-components';
import usePreimage from '@polkadot/app-preimages/usePreimage';
import { Button, Dropdown, Input, InputAddress, InputBalance, Modal, TxButton } from '@polkadot/react-components';
import { Button, Dropdown, Input, InputAddress, InputBalance, InputNumber, Modal, ToggleGroup, TxButton } from '@polkadot/react-components';
import { useApi, useBestNumber, useToggle } from '@polkadot/react-hooks';
import Params from '@polkadot/react-params';
import { Available } from '@polkadot/react-query';
import { getTypeDef } from '@polkadot/types/create';
import { BN_HUNDRED, BN_ONE, formatNumber, isHex } from '@polkadot/util';
import { BN_HUNDRED, BN_ONE, BN_THOUSAND, BN_ZERO, isHex } from '@polkadot/util';
import { useTranslation } from '../translate';
import { getTrackInfo, getTrackName } from '../util';
@ -34,29 +35,33 @@ interface HashState {
}
interface ImageState {
imageLen: number;
imageLen: BN;
imageLenDefault?: BN;
isImageLenValid: boolean;
}
interface DefaultAtAfter {
defaults: [{ isValid: boolean, value: { After: BN } }];
trackId: number;
interface TrackOpt {
text: React.ReactNode;
value: number;
}
function getDefaultEnactment (prev: DefaultAtAfter | null, trackId: number): DefaultAtAfter {
if (prev && prev.trackId === trackId) {
return prev;
}
function getTrackOptions (api: ApiPromise, specName: string, palletReferenda: string, tracks?: TrackDescription[]): undefined | TrackOpt[] {
return tracks && tracks.map(({ id, info }): TrackOpt => {
const trackInfo = getTrackInfo(api, specName, palletReferenda, tracks, id.toNumber());
const trackName = getTrackName(id, info);
return {
defaults: [{
isValid: true,
value: {
After: BN_HUNDRED
}
}],
trackId
};
return {
text: trackInfo?.text
? (
<div className='trackOption'>
<div className='normal'>{trackName}</div>
<div className='faded'>{trackInfo.text}</div>
</div>
)
: trackName,
value: id.toNumber()
};
});
}
function Submit ({ className = '', isMember, members, palletReferenda, tracks }: Props): React.ReactElement<Props> | null {
@ -67,20 +72,26 @@ function Submit ({ className = '', isMember, members, palletReferenda, tracks }:
const [accountId, setAccountId] = useState<string | null>(null);
const [trackId, setTrack] = useState<number | undefined>(undefined);
const [origin, setOrigin] = useState<RawParam['value'] | null>(null);
const [atAfter, setAtAfter] = useState<RawParam['value'] | null>(null);
const [defaultAtAfter, setDefaultAtAfter] = useState<DefaultAtAfter | null>(null);
const [{ imageHash, isImageHashValid }, setImageHash] = useState<HashState>({ imageHash: null, isImageHashValid: false });
const [{ imageLen, isImageLenValid }, setImageLen] = useState<ImageState>({ imageLen: 0, isImageLenValid: false });
const [{ imageLen, imageLenDefault, isImageLenValid }, setImageLen] = useState<ImageState>({ imageLen: BN_ZERO, isImageLenValid: false });
const [enactIndex, setEnactIndex] = useState(0);
const [afterBlocks, setAfterBlocks] = useState(BN_HUNDRED);
const [atBlock, setAtBlock] = useState(BN_ONE);
const [initialAt, setInitialAt] = useState<BN | undefined>();
const preimage = usePreimage(imageHash);
useEffect((): void => {
trackId !== undefined && setDefaultAtAfter((prev) =>
getDefaultEnactment(prev, trackId)
bestNumber && setInitialAt((prev) =>
prev || bestNumber.add(BN_THOUSAND)
);
}, [trackId]);
}, [bestNumber]);
useEffect((): void => {
preimage?.proposalLength && setImageLen({ imageLen: preimage?.proposalLength.toNumber(), isImageLenValid: true });
preimage?.proposalLength && setImageLen((prev) => ({
imageLen: prev.imageLen,
imageLenDefault: preimage.proposalLength,
isImageLenValid: prev.isImageLenValid
}));
}, [preimage]);
const trackInfo = useMemo(
@ -89,25 +100,19 @@ function Submit ({ className = '', isMember, members, palletReferenda, tracks }:
);
const isInvalidAt = useMemo(
() => !bestNumber || !atAfter || (
(atAfter as { At: BN }).At
? (atAfter as { At: BN }).At.lte(bestNumber)
: (atAfter as { After: BN }).After.lt(BN_ONE)
() => !bestNumber || (
enactIndex === 0
? afterBlocks?.lt(BN_ONE)
: atBlock?.lt(bestNumber)
),
[atAfter, bestNumber]
[afterBlocks, atBlock, bestNumber, enactIndex]
);
const [originType, atAfterType] = useMemo(
() => [
[{
name: 'origin',
type: getTypeDef(api.tx[palletReferenda as 'referenda'].submit.meta.args[0].type.toString())
}],
[{
name: 'enact',
type: getTypeDef(api.tx[palletReferenda as 'referenda'].submit.meta.args[2].type.toString())
}]
],
const originType = useMemo(
() => [{
name: 'origin',
type: getTypeDef(api.tx[palletReferenda as 'referenda'].submit.meta.args[0].type.toString())
}],
[api, palletReferenda]
);
@ -129,11 +134,16 @@ function Submit ({ className = '', isMember, members, palletReferenda, tracks }:
);
const trackOpts = useMemo(
() => tracks && tracks.map(({ id, info }) => ({
text: getTrackName(id, info),
value: id.toNumber()
})),
[tracks]
() => getTrackOptions(api, specName, palletReferenda, tracks),
[api, palletReferenda, specName, tracks]
);
const enactOpts = useMemo(
() => [
{ text: t<string>('After delay'), value: 'after' },
{ text: t<string>('At block'), value: 'at' }
],
[t]
);
const _onChangeOriginMulti = useCallback(
@ -151,12 +161,6 @@ function Submit ({ className = '', isMember, members, palletReferenda, tracks }:
[]
);
const _onChangeAtAfter = useCallback(
([{ isValid, value }]: RawParam[]) =>
setAtAfter(isValid ? value : null),
[]
);
const _onChangeImageHash = useCallback(
(h?: string) =>
setImageHash({
@ -167,20 +171,12 @@ function Submit ({ className = '', isMember, members, palletReferenda, tracks }:
);
const _onChangeImageLen = useCallback(
(l?: string): void => {
try {
const imageLen = Number.parseInt(l || '0', 10);
setImageLen({
imageLen,
isImageLenValid: Number.isInteger(imageLen)
});
} catch {
setImageLen({
imageLen: 0,
isImageLenValid: false
});
}
(value: BN): void => {
setImageLen((prev) => ({
imageLen: value,
imageLenDefault: prev.imageLenDefault,
isImageLenValid: !value.isZero()
}));
},
[]
);
@ -210,25 +206,6 @@ function Submit ({ className = '', isMember, members, palletReferenda, tracks }:
type='account'
/>
</Modal.Columns>
<Modal.Columns hint={t<string>('The hash of the preimage for the proposal as previously submitted or intended.')}>
<Input
autoFocus
help={t<string>('The preimage hash of the proposal')}
isError={!isImageHashValid}
label={t<string>('preimage hash')}
onChange={_onChangeImageHash}
value={imageHash || ''}
/>
</Modal.Columns>
<Modal.Columns hint={t<string>('The value will be populated when a valid on-chain hash exists in preimages')}>
<Input
help={t<string>('The preimage length of the proposal')}
key='inputLength'
label={t<string>('preimage length')}
onChange={_onChangeImageLen}
value={imageLen.toString()}
/>
</Modal.Columns>
<Modal.Columns hint={t<string>('The origin (and by extension track) that you wish to submit for, each has a different period, different root and acceptance criteria.')}>
<Dropdown
defaultValue={trackOpts[0] && trackOpts[0].value}
@ -259,18 +236,67 @@ function Submit ({ className = '', isMember, members, palletReferenda, tracks }:
/>
)}
</Modal.Columns>
{bestNumber && defaultAtAfter && (
<Modal.Columns hint={t<string>('The moment of enactment, either at a specific block, or after a specific number of blocks.', { replace: { bestNumber: formatNumber(bestNumber) } })}>
<Params
className='timeSelect'
isError={isInvalidAt}
key={`after:${defaultAtAfter.trackId.toString()}`}
onChange={_onChangeAtAfter}
params={atAfterType}
values={defaultAtAfter.defaults}
/>
</Modal.Columns>
)}
<Modal.Columns
hint={
<>
<p>{t<string>('The hash of the preimage for the proposal as previously submitted or intended.')}</p>
<p>{t<string>('The length value witll be auto-populated from the on-chain value if is is found.')}</p>
</>
}
>
<Input
autoFocus
help={t<string>('The preimage hash of the proposal')}
isError={!isImageHashValid}
label={t<string>('preimage hash')}
onChange={_onChangeImageHash}
value={imageHash || ''}
/>
<InputNumber
defaultValue={imageLenDefault}
help={t<string>('The preimage length of the proposal')}
isDisabled={!!preimage?.proposalLength && !preimage?.proposalLength.isZero() && isImageHashValid && isImageLenValid}
isError={!isImageLenValid}
key='inputLength'
label={t<string>('preimage length')}
onChange={_onChangeImageLen}
value={imageLen}
/>
</Modal.Columns>
<Modal.Columns
className='centerEnactType'
hint={t<string>('The moment of enactment, either at a specific block, or after a specific number of blocks.')}
>
<ToggleGroup
onChange={setEnactIndex}
options={enactOpts}
value={enactIndex}
/>
</Modal.Columns>
{enactIndex === 0
? (
<Modal.Columns hint={t<string>('The number of blocks to delay enactment after proposal approval.')}>
<InputNumber
defaultValue={BN_HUNDRED}
isError={isInvalidAt}
label={t<string>('after number of blocks')}
onChange={setAfterBlocks}
value={afterBlocks}
/>
</Modal.Columns>
)
: (
<Modal.Columns hint={t<string>('A specific block to enact the proposal at.')}>
<InputNumber
defaultValue={initialAt}
isError={isInvalidAt}
label={t<string>('at specific block')}
onChange={setAtBlock}
value={atBlock}
/>
</Modal.Columns>
)
}
<Modal.Columns hint={t<string>('The deposit for this proposal will be locked for the referendum duration.')}>
<InputBalance
defaultValue={api.consts[palletReferenda as 'referenda'].submissionDeposit}
@ -283,7 +309,7 @@ function Submit ({ className = '', isMember, members, palletReferenda, tracks }:
<TxButton
accountId={accountId}
icon='plus'
isDisabled={!selectedOrigin || !atAfter || !isImageHashValid || !isImageLenValid || !accountId || isInvalidAt || !preimage?.proposalHash}
isDisabled={!selectedOrigin || !isImageHashValid || !isImageLenValid || !accountId || isInvalidAt || !preimage?.proposalHash}
label={t<string>('Submit proposal')}
onStart={toggleSubmit}
params={[
@ -293,7 +319,9 @@ function Submit ({ className = '', isMember, members, palletReferenda, tracks }:
? { hash: preimage.proposalHash, len: imageLen }
: { hash: imageHash, len: imageLen }
},
atAfter
enactIndex === 0
? { After: afterBlocks }
: { At: atBlock }
]}
tx={api.tx[palletReferenda as 'referenda'].submit}
/>
@ -316,4 +344,15 @@ export default React.memo(styled(Submit)`
padding-left: 0;
}
}
.trackOption {
.faded {
margin-top: 0.25rem;
opacity: 0.5;
}
}
.ui--Modal-Columns.centerEnactType > div:first-child {
text-align: center;
}
`);