Introduce tracks for gov2 referenda (#8141)
* Introduce tracks for gov2 referenda * Adjust * Track name * Submit dialog * Start of origin selection * Submission cont * At/After blocks
This commit is contained in:
parent
9798950283
commit
fcdca155eb
@ -63,6 +63,7 @@ function Propose ({ className = '', onClose }: Props): React.ReactElement<Props>
|
||||
<Input
|
||||
autoFocus
|
||||
help={t<string>('The preimage hash of the proposal')}
|
||||
isError={!isHashValid}
|
||||
label={t<string>('preimage hash')}
|
||||
onChange={_onChangeHash}
|
||||
value={hash}
|
||||
|
||||
@ -37,7 +37,7 @@ function Add ({ className, imageHash }: Props): React.ReactElement<Props> {
|
||||
useEffect((): void => {
|
||||
const encodedProposal = (proposal as SubmittableExtrinsic<'promise'>)?.method.toHex() || '';
|
||||
|
||||
// we currently don't have a constant exposed, or rather, maybe somwhere else...
|
||||
// we currently don't have a constant exposed, or rather, maybe somewhere else...
|
||||
// const storageFee = api.consts.democracy.preimageByteDeposit.mul(
|
||||
// encodedProposal
|
||||
// ? new BN((encodedProposal.length - 2) / 2)
|
||||
|
||||
@ -15,7 +15,7 @@ const Components: Record<string, React.ComponentType<Props>> = {
|
||||
};
|
||||
|
||||
function Referendum (props: Props): React.ReactElement<Props> {
|
||||
const { className, value: { id, info } } = props;
|
||||
const { className, value: { id, info, track } } = props;
|
||||
|
||||
const Component = useMemo(
|
||||
() => Components[info.type] || Other,
|
||||
@ -27,6 +27,9 @@ function Referendum (props: Props): React.ReactElement<Props> {
|
||||
<td className='number'>
|
||||
<h1>{formatNumber(id)}</h1>
|
||||
</td>
|
||||
<td className='number'>
|
||||
{track && track.name.toString()}
|
||||
</td>
|
||||
{/* <td>
|
||||
<textarea
|
||||
rows={8}
|
||||
|
||||
169
packages/page-referenda/src/Referenda/Submit.tsx
Normal file
169
packages/page-referenda/src/Referenda/Submit.tsx
Normal file
@ -0,0 +1,169 @@
|
||||
// Copyright 2017-2022 @polkadot/app-referenda authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { RawParam } from '@polkadot/react-params/types';
|
||||
import type { PalletReferendaTrackInfo } from '@polkadot/types/lookup';
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { PalletReferenda } from '../types';
|
||||
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Input, InputAddress, Modal, TxButton } from '@polkadot/react-components';
|
||||
import { useApi, useBestNumber } from '@polkadot/react-hooks';
|
||||
import Params from '@polkadot/react-params';
|
||||
import { Available } from '@polkadot/react-query';
|
||||
import { getTypeDef } from '@polkadot/types/create';
|
||||
import { formatNumber, isHex } from '@polkadot/util';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
members?: string[];
|
||||
onClose: () => void;
|
||||
palletReferenda: PalletReferenda;
|
||||
tracks: [BN, PalletReferendaTrackInfo][];
|
||||
}
|
||||
|
||||
interface HashState {
|
||||
hash?: string;
|
||||
isHashValid: boolean;
|
||||
}
|
||||
|
||||
function Submit ({ className = '', members, onClose, palletReferenda }: Props): React.ReactElement<Props> | null {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const bestNumber = useBestNumber();
|
||||
const [accountId, setAccountId] = useState<string | null>(null);
|
||||
// const [track, setTrack] = useState<number | undefined>();
|
||||
const [origin, setOrigin] = useState<RawParam['value'] | null>(null);
|
||||
const [atAfter, setAtAfter] = useState<RawParam['value'] | null>(null);
|
||||
const [{ hash, isHashValid }, setHash] = useState<HashState>({ hash: '', isHashValid: false });
|
||||
|
||||
const isInvalidAt = useMemo(
|
||||
() => !bestNumber || !atAfter || (
|
||||
(atAfter as { At: BN }).At
|
||||
? (atAfter as { At: BN }).At.lte(bestNumber)
|
||||
: (atAfter as { After: BN }).After.lte(bestNumber)
|
||||
),
|
||||
[atAfter, bestNumber]
|
||||
);
|
||||
|
||||
const [originType, atAfterType] = useMemo(
|
||||
() => [
|
||||
[{
|
||||
type: getTypeDef(api.tx[palletReferenda as 'referenda'].submit.meta.args[0].type.toString())
|
||||
}],
|
||||
[{
|
||||
type: getTypeDef(api.tx[palletReferenda as 'referenda'].submit.meta.args[2].type.toString())
|
||||
}]
|
||||
],
|
||||
[api, palletReferenda]
|
||||
);
|
||||
|
||||
// Idially we would just like to use the track - need a mapping for these
|
||||
// const trackOpts = useMemo(
|
||||
// () => tracks.map(([id, track]) => ({
|
||||
// text: track.name.toString(),
|
||||
// value: id.toNumber()
|
||||
// })),
|
||||
// [tracks]
|
||||
// );
|
||||
|
||||
const _onChangeOrigin = useCallback(
|
||||
([{ isValid, value }]: RawParam[]) =>
|
||||
setOrigin(isValid ? value : null),
|
||||
[]
|
||||
);
|
||||
|
||||
const _onChangeAtAfter = useCallback(
|
||||
([{ isValid, value }]: RawParam[]) =>
|
||||
setAtAfter(isValid ? value : null),
|
||||
[]
|
||||
);
|
||||
|
||||
const _onChangeHash = useCallback(
|
||||
(hash?: string) =>
|
||||
setHash({ hash, isHashValid: isHex(hash, 256) }),
|
||||
[]
|
||||
);
|
||||
|
||||
console.log(atAfter);
|
||||
|
||||
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 register the proposal from')}
|
||||
label={t<string>('send 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 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
|
||||
label={t<string>('submission track')}
|
||||
onChange={setTrack}
|
||||
options={trackOpts}
|
||||
/> */}
|
||||
<Params
|
||||
className='originSelect'
|
||||
onChange={_onChangeOrigin}
|
||||
params={originType}
|
||||
/>
|
||||
</Modal.Columns>
|
||||
<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}
|
||||
/>
|
||||
</Modal.Columns>
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<TxButton
|
||||
accountId={accountId}
|
||||
icon='plus'
|
||||
isDisabled={!origin || !atAfter || !isHashValid || !accountId || isInvalidAt}
|
||||
label={t<string>('Submit proposal')}
|
||||
onStart={onClose}
|
||||
params={[origin, hash, atAfter]}
|
||||
tx={api.tx[palletReferenda as 'referenda'].submit}
|
||||
/>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(styled(Submit)`
|
||||
.originSelect, .timeSelect {
|
||||
> .ui--Params-Content {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
`);
|
||||
@ -40,7 +40,7 @@ function Votes ({ className = '', isConvictionVote, tally }: Props): React.React
|
||||
summary={
|
||||
<>
|
||||
{t<string>('Nay')}
|
||||
<div><FormatBalance value={tally.ayes} /></div>
|
||||
<div><FormatBalance value={tally.nays} /></div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
@ -5,13 +5,15 @@ import type { PalletReferenda, PalletVote } from '../types';
|
||||
|
||||
import React, { useMemo, useRef } from 'react';
|
||||
|
||||
import { Table } from '@polkadot/react-components';
|
||||
import { useAccounts } from '@polkadot/react-hooks';
|
||||
import AddPreimage from '@polkadot/app-preimages/Preimages/Add';
|
||||
import { Button, Table } from '@polkadot/react-components';
|
||||
import { useAccounts, useToggle } from '@polkadot/react-hooks';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
import useReferenda from '../useReferenda';
|
||||
import useSummary from '../useSummary';
|
||||
import Referendum from './Referendum';
|
||||
import Submit from './Submit';
|
||||
import Summary from './Summary';
|
||||
|
||||
export { useCounterNamed as useCounter } from '../useCounter';
|
||||
@ -26,8 +28,9 @@ interface Props {
|
||||
function Referenda ({ className, members, palletReferenda, palletVote }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { allAccounts } = useAccounts();
|
||||
const referenda = useReferenda(palletReferenda);
|
||||
const [referenda, tracks] = useReferenda(palletReferenda);
|
||||
const summary = useSummary(palletReferenda, referenda);
|
||||
const [isSubmitOpen, toggleSubmit] = useToggle();
|
||||
|
||||
const isMember = useMemo(
|
||||
() => !members || allAccounts.some((a) => members.includes(a)),
|
||||
@ -42,6 +45,27 @@ function Referenda ({ className, members, palletReferenda, palletVote }: Props):
|
||||
return (
|
||||
<div className={className}>
|
||||
<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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Button.Group>
|
||||
<Table
|
||||
className={className}
|
||||
empty={referenda && t<string>('No referendums found')}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// Copyright 2017-2022 @polkadot/app-referenda authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { PalletReferendaReferendumInfoConvictionVotingTally, PalletReferendaReferendumInfoRankedCollectiveTally } from '@polkadot/types/lookup';
|
||||
import type { PalletReferendaReferendumInfoConvictionVotingTally, PalletReferendaReferendumInfoRankedCollectiveTally, PalletReferendaTrackInfo } from '@polkadot/types/lookup';
|
||||
import type { BN } from '@polkadot/util';
|
||||
|
||||
export type PalletReferenda = 'referenda' | 'rankedPolls' | 'fellowshipReferenda';
|
||||
@ -13,6 +13,7 @@ export interface Referendum {
|
||||
info: PalletReferendaReferendumInfoConvictionVotingTally | PalletReferendaReferendumInfoRankedCollectiveTally;
|
||||
isConvictionVote: boolean;
|
||||
key: string;
|
||||
track?: PalletReferendaTrackInfo;
|
||||
}
|
||||
|
||||
export interface ReferendumProps {
|
||||
|
||||
@ -10,11 +10,11 @@ import { createNamedHook } from '@polkadot/react-hooks';
|
||||
import useReferenda from './useReferenda';
|
||||
|
||||
export function useCounterNamed (palletReferenda: PalletReferenda): number {
|
||||
const refs = useReferenda(palletReferenda);
|
||||
const [referanda] = useReferenda(palletReferenda);
|
||||
|
||||
return useMemo(
|
||||
() => (refs && refs.filter(({ info }) => info.isOngoing).length) || 0,
|
||||
[refs]
|
||||
() => (referanda && referanda.filter(({ info }) => info.isOngoing).length) || 0,
|
||||
[referanda]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Option } from '@polkadot/types';
|
||||
import type { PalletReferendaReferendumInfoConvictionVotingTally, PalletReferendaReferendumInfoRankedCollectiveTally } from '@polkadot/types/lookup';
|
||||
import type { PalletReferendaReferendumInfoConvictionVotingTally, PalletReferendaReferendumInfoRankedCollectiveTally, PalletReferendaTrackInfo } from '@polkadot/types/lookup';
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { PalletReferenda, Referendum } from './types';
|
||||
|
||||
@ -11,6 +11,7 @@ import { useMemo } from 'react';
|
||||
import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';
|
||||
|
||||
import useReferendaIds from './useReferendaIds';
|
||||
import useTracks from './useTracks';
|
||||
|
||||
const ORDER = <const> ['Ongoing', 'Approved', 'Rejected', 'Cancelled', 'Killed', 'TimedOut'];
|
||||
|
||||
@ -64,16 +65,38 @@ const OPT_MULTI = {
|
||||
withParamsTransform: true
|
||||
};
|
||||
|
||||
function useReferendaImpl (palletReferenda: PalletReferenda): Referendum[] | undefined {
|
||||
function getResult (referenda: Referendum[], tracks?: [BN, PalletReferendaTrackInfo][]): Referendum[] | undefined {
|
||||
if (tracks) {
|
||||
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));
|
||||
|
||||
if (track) {
|
||||
referenda[i].track = track[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return referenda;
|
||||
}
|
||||
|
||||
function useReferendaImpl (palletReferenda: PalletReferenda): [Referendum[] | undefined, [BN, PalletReferendaTrackInfo][] | undefined] {
|
||||
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);
|
||||
|
||||
return useMemo(
|
||||
() => ids && ids.length === 0
|
||||
? []
|
||||
: referenda,
|
||||
[ids, referenda]
|
||||
() => [
|
||||
(ids && ids.length === 0)
|
||||
? []
|
||||
: referenda
|
||||
? getResult(referenda, tracks)
|
||||
: undefined,
|
||||
tracks
|
||||
],
|
||||
[ids, referenda, tracks]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
23
packages/page-referenda/src/useTracks.ts
Normal file
23
packages/page-referenda/src/useTracks.ts
Normal file
@ -0,0 +1,23 @@
|
||||
// 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 { useMemo } from 'react';
|
||||
|
||||
import { createNamedHook, useApi } from '@polkadot/react-hooks';
|
||||
|
||||
function useTracksImpl (palletReferenda: PalletReferenda): [BN, PalletReferendaTrackInfo][] | undefined {
|
||||
const { api, isApiReady } = useApi();
|
||||
|
||||
return useMemo(
|
||||
() => isApiReady
|
||||
? api.consts[palletReferenda as 'referenda'] && api.consts[palletReferenda as 'referenda'].tracks
|
||||
: undefined,
|
||||
[api, isApiReady, palletReferenda]
|
||||
);
|
||||
}
|
||||
|
||||
export default createNamedHook('useTraqcks', useTracksImpl);
|
||||
@ -66,7 +66,7 @@ export default React.memo(styled(ProposedAction)`
|
||||
margin-left: 2rem;
|
||||
|
||||
.ui--ProposedAction-extrinsic {
|
||||
.ui--Params-Content {
|
||||
> .ui--Params-Content {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,6 +127,7 @@ function EnumParam (props: Props): React.ReactElement<Props> {
|
||||
/>
|
||||
{current && (
|
||||
<Params
|
||||
isError={isError}
|
||||
onChange={_onChangeParam}
|
||||
overrides={overrides}
|
||||
params={current}
|
||||
|
||||
@ -23,7 +23,7 @@ function formatJSON (input: string): string {
|
||||
.replace(/,/g, ', ');
|
||||
}
|
||||
|
||||
function Param ({ className = '', defaultValue, isDisabled, isInOption, isOptional, name, onChange, onEnter, onEscape, overrides, registry, type }: Props): React.ReactElement<Props> | null {
|
||||
function Param ({ className = '', defaultValue, isDisabled, isError, isInOption, isOptional, name, onChange, onEnter, onEscape, overrides, registry, type }: Props): React.ReactElement<Props> | null {
|
||||
const Component = useMemo(
|
||||
() => findComponent(registry, type, overrides),
|
||||
[registry, type, overrides]
|
||||
@ -62,6 +62,7 @@ function Param ({ className = '', defaultValue, isDisabled, isInOption, isOption
|
||||
className={`ui--Param ${className}`}
|
||||
defaultValue={defaultValue}
|
||||
isDisabled={isDisabled}
|
||||
isError={isError}
|
||||
isInOption={isInOption}
|
||||
key={`${name || 'unknown'}:${label}`}
|
||||
label={label}
|
||||
|
||||
@ -12,6 +12,7 @@ interface Props {
|
||||
defaultValue: RawParam;
|
||||
index: number;
|
||||
isDisabled?: boolean;
|
||||
isError?: boolean;
|
||||
name?: string;
|
||||
onChange: (index: number, value: RawParamOnChangeValue) => void;
|
||||
onEnter?: () => void;
|
||||
@ -22,7 +23,7 @@ interface Props {
|
||||
values?: RawParams | null;
|
||||
}
|
||||
|
||||
function ParamComp ({ defaultValue, index, isDisabled, name, onChange, onEnter, onEscape, overrides, registry, type }: Props): React.ReactElement<Props> {
|
||||
function ParamComp ({ defaultValue, index, isDisabled, isError, name, onChange, onEnter, onEscape, overrides, registry, type }: Props): React.ReactElement<Props> {
|
||||
const _onChange = useCallback(
|
||||
(value: RawParamOnChangeValue): void =>
|
||||
onChange(index, value),
|
||||
@ -34,6 +35,7 @@ function ParamComp ({ defaultValue, index, isDisabled, name, onChange, onEnter,
|
||||
<Param
|
||||
defaultValue={defaultValue}
|
||||
isDisabled={isDisabled}
|
||||
isError={isError}
|
||||
key={`input:${index}`}
|
||||
name={name}
|
||||
onChange={_onChange}
|
||||
|
||||
@ -19,6 +19,7 @@ import { createValue } from './values';
|
||||
interface Props extends I18nProps {
|
||||
children?: React.ReactNode;
|
||||
isDisabled?: boolean;
|
||||
isError?: boolean;
|
||||
onChange?: (value: RawParams) => void;
|
||||
onEnter?: () => void;
|
||||
onError?: () => void;
|
||||
@ -80,7 +81,7 @@ class Params extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
public override render (): React.ReactNode {
|
||||
const { children, className = '', isDisabled, onEnter, onEscape, overrides, params, registry = api.registry, withBorder = true } = this.props;
|
||||
const { children, className = '', isDisabled, isError, onEnter, onEscape, overrides, params, registry = api.registry, withBorder = true } = this.props;
|
||||
const { values = this.props.values } = this.state;
|
||||
|
||||
if (!values || !values.length) {
|
||||
@ -99,6 +100,7 @@ class Params extends React.PureComponent<Props, State> {
|
||||
defaultValue={values[index]}
|
||||
index={index}
|
||||
isDisabled={isDisabled}
|
||||
isError={isError}
|
||||
key={`${name || ''}:${type.type.toString()}:${index}:${isDisabled ? stringify(values[index]) : ''}`}
|
||||
name={name}
|
||||
onChange={this.onChangeParam}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user