Formatting changes for referenda (#8151)

* Formatting changes for referenda

* Deposit

* Refund deposit

* group functionality
This commit is contained in:
Jaco 2022-09-21 11:29:26 +03:00 committed by GitHub
parent 29a5cfca0f
commit 3a354ecc32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 397 additions and 201 deletions

View File

@ -1,42 +0,0 @@
// Copyright 2017-2022 @polkadot/app-referenda authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Option } from '@polkadot/types';
import type { PalletReferendaDeposit } from '@polkadot/types/lookup';
import React, { useMemo } from 'react';
import { AddressMini } from '@polkadot/react-components';
interface Props {
decision: Option<PalletReferendaDeposit> | null;
submit: PalletReferendaDeposit | null;
}
function Deposit ({ decision, submit }: Props): React.ReactElement<Props> {
const [first, second] = useMemo(
() => [submit, decision && decision.unwrapOr(null)],
[decision, submit]
);
return (
<td className='address'>
{first && (
<AddressMini
balance={first.amount}
value={first.who}
withBalance
/>
)}
{second && (
<AddressMini
balance={second.amount}
value={second.who}
withBalance
/>
)}
</td>
);
}
export default React.memo(Deposit);

View File

@ -0,0 +1,89 @@
// Copyright 2017-2022 @polkadot/app-referenda authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { PalletReferendaTrackInfo } from '@polkadot/types/lookup';
import type { BN } from '@polkadot/util';
import type { PalletReferenda } from '../../types';
import React, { useState } from 'react';
import { Button, InputAddress, InputBalance, InputNumber, Modal, TxButton } from '@polkadot/react-components';
import { useApi, useToggle } from '@polkadot/react-hooks';
import { Available } from '@polkadot/react-query';
import { useTranslation } from '../../translate';
interface Props {
className?: string;
id: BN;
palletReferenda: PalletReferenda;
track: PalletReferendaTrackInfo;
}
function Deposit ({ className = '', id, palletReferenda, track }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { api } = useApi();
const [isOpen, toggleOpen] = useToggle();
const [accountId, setAccountId] = useState<string | null>(null);
return (
<>
{isOpen && (
<Modal
className={className}
header={t<string>('Place decision deposit')}
onClose={toggleOpen}
size='large'
>
<Modal.Content>
<Modal.Columns hint={t<string>('The deposit will be registered from this account and the balance lock will be applied here.')}>
<InputAddress
help={t<string>('The account you want to deposit from')}
label={t<string>('deposit from account')}
labelExtra={
<Available
label={<span className='label'>{t<string>('transferrable')}</span>}
params={accountId}
/>
}
onChange={setAccountId}
type='account'
/>
</Modal.Columns>
<Modal.Columns hint={t<string>('The referendum this deposit would apply to.')}>
<InputNumber
defaultValue={id}
isDisabled
label={t<string>('referendum id')}
/>
</Modal.Columns>
<Modal.Columns hint={t<string>('The deposit for this proposal will be locked for the referendum duration.')}>
<InputBalance
defaultValue={track.decisionDeposit}
isDisabled
label={t<string>('decision deposit')}
/>
</Modal.Columns>
</Modal.Content>
<Modal.Actions>
<TxButton
accountId={accountId}
icon='plus'
label={t<string>('Place deposit')}
onStart={toggleOpen}
params={[id]}
tx={api.tx[palletReferenda as 'referenda'].placeDecisionDeposit}
/>
</Modal.Actions>
</Modal>
)}
<Button
icon='plus'
label={t<string>('Decision deposit')}
onClick={toggleOpen}
/>
</>
);
}
export default React.memo(Deposit);

View File

@ -0,0 +1,73 @@
// Copyright 2017-2022 @polkadot/app-referenda authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BN } from '@polkadot/util';
import type { PalletReferenda } from '../../types';
import React, { useState } from 'react';
import { Button, InputAddress, InputNumber, Modal, TxButton } from '@polkadot/react-components';
import { useApi, useToggle } from '@polkadot/react-hooks';
import { useTranslation } from '../../translate';
interface Props {
className?: string;
id: BN;
palletReferenda: PalletReferenda;
}
function Refund ({ className = '', id, palletReferenda }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { api } = useApi();
const [isOpen, toggleOpen] = useToggle();
const [accountId, setAccountId] = useState<string | null>(null);
return (
<>
{isOpen && (
<Modal
className={className}
header={t<string>('Refund decision deposit')}
onClose={toggleOpen}
size='large'
>
<Modal.Content>
<Modal.Columns hint={t<string>('The transaction will be submitted from this account.')}>
<InputAddress
help={t<string>('The account you want to refund from')}
label={t<string>('refund from account')}
onChange={setAccountId}
type='account'
/>
</Modal.Columns>
<Modal.Columns hint={t<string>('The referendum this deposit would apply to.')}>
<InputNumber
defaultValue={id}
isDisabled
label={t<string>('referendum id')}
/>
</Modal.Columns>
</Modal.Content>
<Modal.Actions>
<TxButton
accountId={accountId}
icon='minus'
label={t<string>('Refund deposit')}
onStart={toggleOpen}
params={[id]}
tx={api.tx[palletReferenda as 'referenda'].refundDecisionDeposit}
/>
</Modal.Actions>
</Modal>
)}
<Button
icon='minus'
label={t<string>('Refund deposit')}
onClick={toggleOpen}
/>
</>
);
}
export default React.memo(Refund);

View File

@ -0,0 +1,68 @@
// Copyright 2017-2022 @polkadot/app-referenda authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Option } from '@polkadot/types';
import type { PalletReferendaDeposit, PalletReferendaTrackInfo } from '@polkadot/types/lookup';
import type { BN } from '@polkadot/util';
import type { PalletReferenda } from '../../types';
import React, { useMemo } from 'react';
import { AddressMini } from '@polkadot/react-components';
import Place from './Place';
import Refund from './Refund';
interface Props {
canDeposit?: boolean;
canRefund?: boolean;
decision: Option<PalletReferendaDeposit> | null;
id: BN;
palletReferenda: PalletReferenda;
submit: PalletReferendaDeposit | null;
track?: PalletReferendaTrackInfo;
}
function Deposits ({ canDeposit, canRefund, decision, id, palletReferenda, submit, track }: Props): React.ReactElement<Props> {
const [valSubmit, valDeposit] = useMemo(
() => [submit, decision && decision.unwrapOr(null)],
[decision, submit]
);
return (
<td className='address'>
{valSubmit && (
<AddressMini
balance={valSubmit.amount}
value={valSubmit.who}
withBalance
/>
)}
{valDeposit
? (
<AddressMini
balance={valDeposit.amount}
value={valDeposit.who}
withBalance
/>
)
: canDeposit
? track && (
<Place
id={id}
palletReferenda={palletReferenda}
track={track}
/>
)
: canRefund && (
<Refund
id={id}
palletReferenda={palletReferenda}
/>
)
}
</td>
);
}
export default React.memo(Deposits);

View File

@ -10,7 +10,7 @@ import usePreimage from '@polkadot/app-preimages/usePreimage';
import { CallExpander, Progress } from '@polkadot/react-components';
import { useTranslation } from '../translate';
import Deposit from './Deposit';
import Deposits from './Deposits';
import Vote from './Vote';
import Votes from './Votes';
@ -31,12 +31,14 @@ function expandOngoing (info: Referendum['info']): Expanded {
};
}
function Ongoing ({ isMember, members, palletVote, value: { id, info, isConvictionVote } }: Props): React.ReactElement<Props> {
function Ongoing ({ isMember, members, palletReferenda, palletVote, value: { id, info, isConvictionVote, track } }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { ongoing: { decisionDeposit, proposalHash, submissionDeposit, tally }, shortHash, tallyTotal } = useMemo(
() => expandOngoing(info),
[info]
);
const preimage = usePreimage(proposalHash);
return (
@ -53,9 +55,13 @@ function Ongoing ({ isMember, members, palletVote, value: { id, info, isConvicti
: t('preimage {{shortHash}}', { replace: { shortHash } })
}
</td>
<Deposit
<Deposits
canDeposit
decision={decisionDeposit}
id={id}
palletReferenda={palletReferenda}
submit={submissionDeposit}
track={track}
/>
<Votes
id={id}

View File

@ -7,7 +7,7 @@ import type { Referendum, ReferendumProps as Props } from '../types';
import React, { useMemo } from 'react';
import Deposit from './Deposit';
import Deposits from './Deposits';
interface Expanded {
decision: Option<PalletReferendaDeposit> | null;
@ -26,22 +26,20 @@ function expandTuple (info: Referendum['info']): Expanded {
? info.asTimedOut
: null;
if (!data) {
return {
return data
? {
decision: data[2],
submit: data[1],
when: new Date(data[0].toNumber())
}
: {
decision: null,
submit: null,
when: null
};
}
return {
decision: data[2],
submit: data[1],
when: new Date(data[0].toNumber())
};
}
function Tuple ({ value: { info } }: Props): React.ReactElement<Props> {
function Tuple ({ palletReferenda, value: { id, info, track } }: Props): React.ReactElement<Props> {
const { decision, submit, when } = useMemo(
() => expandTuple(info),
[info]
@ -50,9 +48,13 @@ function Tuple ({ value: { info } }: Props): React.ReactElement<Props> {
return (
<>
<td className='all' />
<Deposit
<Deposits
canRefund
decision={decision}
id={id}
palletReferenda={palletReferenda}
submit={submit}
track={track}
/>
<td
className='number'

View File

@ -11,8 +11,8 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import styled from 'styled-components';
import { getGovernanceTracks } from '@polkadot/apps-config';
import { Dropdown, Input, InputAddress, Modal, TxButton } from '@polkadot/react-components';
import { useApi, useBestNumber } from '@polkadot/react-hooks';
import { Button, Dropdown, Input, InputAddress, InputBalance, Modal, 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';
@ -23,10 +23,10 @@ import { getTrackName } from './util';
interface Props {
className?: string;
isMember: boolean;
members?: string[];
onClose: () => void;
palletReferenda: PalletReferenda;
tracks: [BN, PalletReferendaTrackInfo][];
tracks?: [BN, PalletReferendaTrackInfo][];
}
interface HashState {
@ -34,28 +34,32 @@ interface HashState {
isHashValid: boolean;
}
function getOrigin (api: ApiPromise, specName: string, palletReferenda: string, tracks: [BN, PalletReferendaTrackInfo][], trackId: number): Record<string, string> | undefined {
const originMap = getGovernanceTracks(api, specName, palletReferenda);
const trackInfo = tracks.find(([id]) => id.eqn(trackId));
function getOrigin (api: ApiPromise, specName: string, palletReferenda: string, tracks?: [BN, PalletReferendaTrackInfo][], trackId?: number): Record<string, string> | undefined {
let origin: Record<string, string> | undefined;
if (trackInfo && originMap) {
const trackName = trackInfo[1].name.toString();
const record = originMap.find(([[id, name]]) =>
id === trackId &&
name === trackName
);
if (tracks && trackId !== undefined) {
const originMap = getGovernanceTracks(api, specName, palletReferenda);
const trackInfo = tracks.find(([id]) => id.eqn(trackId));
origin = record && record[1];
if (trackInfo && originMap) {
const trackName = trackInfo[1].name.toString();
const record = originMap.find(([[id, name]]) =>
id === trackId &&
name === trackName
);
origin = record && record[1];
}
}
return origin;
}
function Submit ({ className = '', members, onClose, palletReferenda, tracks }: Props): React.ReactElement<Props> | null {
function Submit ({ className = '', isMember, members, palletReferenda, tracks }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { api, specName } = useApi();
const bestNumber = useBestNumber();
const [isSubmitOpen, toggleSubmit] = useToggle();
const [accountId, setAccountId] = useState<string | null>(null);
const [trackId, setTrack] = useState<number | undefined>();
const [origin, setOrigin] = useState<RawParam['value'] | null>(null);
@ -73,7 +77,7 @@ function Submit ({ className = '', members, onClose, palletReferenda, tracks }:
}, [api, bestNumber]);
const originParam = useMemo(
() => trackId !== undefined && getOrigin(api, specName, palletReferenda, tracks, trackId),
() => getOrigin(api, specName, palletReferenda, tracks, trackId),
[api, palletReferenda, specName, trackId, tracks]
);
@ -101,7 +105,7 @@ function Submit ({ className = '', members, onClose, palletReferenda, tracks }:
);
const trackOpts = useMemo(
() => tracks.map(([id, track]) => ({
() => tracks && tracks.map(([id, track]) => ({
text: getTrackName(track),
value: id.toNumber()
})),
@ -127,77 +131,94 @@ function Submit ({ className = '', members, onClose, palletReferenda, tracks }:
);
return (
<Modal
className={className}
header={t<string>('Submit proposal')}
onClose={onClose}
size='large'
>
<Modal.Content>
<Modal.Columns hint={t<string>('The proposal will be registered from this account and the balance lock will be applied here.')}>
<InputAddress
filter={members}
help={t<string>('The account you want to propose from')}
label={t<string>('propose from account')}
labelExtra={
<Available
label={<span className='label'>{t<string>('transferrable')}</span>}
params={accountId}
<>
{trackOpts && isSubmitOpen && (
<Modal
className={className}
header={t<string>('Submit proposal')}
onClose={toggleSubmit}
size='large'
>
<Modal.Content>
<Modal.Columns hint={t<string>('The proposal will be registered from this account and the balance lock will be applied here.')}>
<InputAddress
filter={members}
help={t<string>('The account you want to propose from')}
label={t<string>('propose from account')}
labelExtra={
<Available
label={<span className='label'>{t<string>('transferrable')}</span>}
params={accountId}
/>
}
onChange={setAccountId}
type='account'
/>
}
onChange={setAccountId}
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={!isHashValid}
label={t<string>('preimage hash')}
onChange={_onChangeHash}
value={hash}
/>
</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}
label={t<string>('submission track')}
onChange={setTrack}
options={trackOpts}
/>
{!originParam && (
<Params
className='originSelect'
onChange={_onChangeOrigin}
params={originType}
</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={!isHashValid}
label={t<string>('preimage hash')}
onChange={_onChangeHash}
value={hash}
/>
</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}
label={t<string>('submission track')}
onChange={setTrack}
options={trackOpts}
/>
{!originParam && (
<Params
className='originSelect'
onChange={_onChangeOrigin}
params={originType}
/>
)}
</Modal.Columns>
{bestNumber && defaultAtAfter && (
<Modal.Columns hint={t<string>('The moment of enactment, either at a specific block, or after a specific block. Currently at #{{bestNumber}}, the selected block should be after the current best.', { replace: { bestNumber: formatNumber(bestNumber) } })}>
<Params
className='timeSelect'
isError={isInvalidAt}
onChange={_onChangeAtAfter}
params={atAfterType}
values={defaultAtAfter}
/>
</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}
isDisabled
label={t<string>('submission deposit')}
/>
</Modal.Columns>
</Modal.Content>
<Modal.Actions>
<TxButton
accountId={accountId}
icon='plus'
isDisabled={!(originParam || origin) || !atAfter || !isHashValid || !accountId || isInvalidAt}
label={t<string>('Submit proposal')}
onStart={toggleSubmit}
params={[originParam || origin, hash, atAfter]}
tx={api.tx[palletReferenda as 'referenda'].submit}
/>
)}
</Modal.Columns>
{bestNumber && defaultAtAfter && (
<Modal.Columns hint={t<string>('The moment of enactment, either at a specific block, or after a specific block. Currently at #{{bestNumber}}, the selected block should be after the current best.', { replace: { bestNumber: formatNumber(bestNumber) } })}>
<Params
className='timeSelect'
isError={isInvalidAt}
onChange={_onChangeAtAfter}
params={atAfterType}
values={defaultAtAfter}
/>
</Modal.Columns>
)}
</Modal.Content>
<Modal.Actions>
<TxButton
accountId={accountId}
icon='plus'
isDisabled={!(originParam || origin) || !atAfter || !isHashValid || !accountId || isInvalidAt}
label={t<string>('Submit proposal')}
onStart={onClose}
params={[originParam || origin, hash, atAfter]}
tx={api.tx[palletReferenda as 'referenda'].submit}
/>
</Modal.Actions>
</Modal>
</Modal.Actions>
</Modal>
)}
<Button
icon='plus'
isDisabled={!isMember || !trackOpts}
label={t<string>('Submit proposal')}
onClick={toggleSubmit}
/>
</>
);
}

View File

@ -25,38 +25,30 @@ function Votes ({ className = '', isConvictionVote, tally }: Props): React.React
return (
<td className={`${className} expand`}>
{isConvictionVote
? (
<>
<Expander
summary={
<>
{t<string>('Aye')}
<div><FormatBalance value={tally.ayes} /></div>
</>
}
/>
<Expander
summary={
<>
{t<string>('Nay')}
<div><FormatBalance value={tally.nays} /></div>
</>
}
/>
</>
)
: (
<>
<Expander
summary={t<string>('Aye {{count}}', { replace: { count: tally.ayes.toNumber() } })}
/>
<Expander
summary={t<string>('Nay {{count}}', { replace: { count: tally.nays.toNumber() } })}
/>
</>
)
}
<Expander
summary={
isConvictionVote
? (
<>
{t<string>('Aye')}
<div><FormatBalance value={tally.ayes} /></div>
</>
)
: t<string>('Aye {{count}}', { replace: { count: tally.ayes.toNumber() } })
}
/>
<Expander
summary={
isConvictionVote
? (
<>
{t<string>('Nay')}
<div><FormatBalance value={tally.nays} /></div>
</>
)
: t<string>('Nay {{count}}', { replace: { count: tally.nays.toNumber() } })
}
/>
</td>
);
}

View File

@ -7,7 +7,7 @@ import React, { useMemo, useRef } from 'react';
import AddPreimage from '@polkadot/app-preimages/Preimages/Add';
import { Button, Table } from '@polkadot/react-components';
import { useAccounts, useToggle } from '@polkadot/react-hooks';
import { useAccounts } from '@polkadot/react-hooks';
import { useTranslation } from '../translate';
import useReferenda from '../useReferenda';
@ -30,7 +30,6 @@ function Referenda ({ className, members, palletReferenda, palletVote }: Props):
const { allAccounts } = useAccounts();
const [referenda, tracks] = useReferenda(palletReferenda);
const summary = useSummary(palletReferenda, referenda);
const [isSubmitOpen, toggleSubmit] = useToggle();
const isMember = useMemo(
() => !members || allAccounts.some((a) => members.includes(a)),
@ -47,24 +46,12 @@ function Referenda ({ className, members, palletReferenda, palletVote }: Props):
<Summary summary={summary} />
<Button.Group>
<AddPreimage />
{tracks && (
<>
<Button
icon='plus'
isDisabled={!isMember}
label={t<string>('Submit proposal')}
onClick={toggleSubmit}
/>
{isSubmitOpen && (
<Submit
members={members}
onClose={toggleSubmit}
palletReferenda={palletReferenda}
tracks={tracks}
/>
)}
</>
)}
<Submit
isMember={isMember}
members={members}
palletReferenda={palletReferenda}
tracks={tracks}
/>
</Button.Group>
<Table
className={className}
@ -76,6 +63,7 @@ function Referenda ({ className, members, palletReferenda, palletVote }: Props):
isMember={isMember}
key={r.key}
members={members}
palletReferenda={palletReferenda}
palletVote={palletVote}
value={r}
/>

View File

@ -20,6 +20,7 @@ export interface ReferendumProps {
className?: string;
isMember: boolean;
members?: string[];
palletReferenda: PalletReferenda;
palletVote: PalletVote;
value: Referendum;
}

View File

@ -65,8 +65,8 @@ const OPT_MULTI = {
withParamsTransform: true
};
function getResult (referenda: Referendum[], tracks?: [BN, PalletReferendaTrackInfo][]): Referendum[] | undefined {
if (tracks) {
function getResult (referenda?: Referendum[], tracks?: [BN, PalletReferendaTrackInfo][]): Referendum[] | undefined {
if (tracks && referenda) {
for (let i = 0; i < referenda.length; i++) {
if (referenda[i].info.isOngoing) {
const track = tracks.find(([id]) => id.eq(referenda[i].info.asOngoing.track));
@ -85,15 +85,13 @@ function useReferendaImpl (palletReferenda: PalletReferenda): [Referendum[] | un
const { api, isApiReady } = useApi();
const ids = useReferendaIds(palletReferenda);
const tracks = useTracks(palletReferenda);
const referenda = useCall(isApiReady && ids && ids.length !== 0 && api.query[palletReferenda].referendumInfoFor.multi, [ids], OPT_MULTI);
const referenda = useCall(isApiReady && ids && ids.length !== 0 && api.query[palletReferenda as 'referenda'].referendumInfoFor.multi, [ids], OPT_MULTI);
return useMemo(
() => [
(ids && ids.length === 0)
? []
: referenda
? getResult(referenda, tracks)
: undefined,
: getResult(referenda, tracks),
tracks
],
[ids, referenda, tracks]