Basic referenda list (#7944)
* Basic referenda list * preimage * Debug display * Adjust Referendum displays * shortHash calc * Show deposits * Remove debug column
This commit is contained in:
parent
e86fa527a8
commit
6932d4339b
@ -25,6 +25,7 @@ import nfts from './nfts';
|
||||
import parachains from './parachains';
|
||||
import poll from './poll';
|
||||
import preimages from './preimages';
|
||||
import referenda from './referenda';
|
||||
import rpc from './rpc';
|
||||
import runtime from './runtime';
|
||||
import settings from './settings';
|
||||
@ -52,6 +53,7 @@ export default function create (t: TFunction): Routes {
|
||||
staking(t),
|
||||
collator(t),
|
||||
democracy(t),
|
||||
referenda(t),
|
||||
council(t),
|
||||
treasury(t),
|
||||
bounties(t),
|
||||
|
||||
23
packages/apps-routing/src/referenda.ts
Normal file
23
packages/apps-routing/src/referenda.ts
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright 2017-2022 @polkadot/apps-routing authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { TFunction } from 'i18next';
|
||||
import type { Route } from './types';
|
||||
|
||||
import Component from '@polkadot/app-referenda';
|
||||
|
||||
export default function create (t: TFunction): Route {
|
||||
return {
|
||||
Component,
|
||||
display: {
|
||||
needsApi: [
|
||||
'tx.referenda.submit',
|
||||
'tx.convictionVoting.vote'
|
||||
]
|
||||
},
|
||||
group: 'governance',
|
||||
icon: 'person-booth',
|
||||
name: 'referenda',
|
||||
text: t('nav.referenda', 'Referenda', { ns: 'apps-routing' })
|
||||
};
|
||||
}
|
||||
@ -1,43 +1,24 @@
|
||||
// Copyright 2017-2022 @polkadot/app-preimages authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ApiPromise } from '@polkadot/api';
|
||||
import type { Bytes } from '@polkadot/types';
|
||||
import type { Call } from '@polkadot/types/interfaces';
|
||||
import type { HexString } from '@polkadot/util/types';
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { CallExpander } from '@polkadot/react-components';
|
||||
import { useApi } from '@polkadot/react-hooks';
|
||||
import { formatNumber } from '@polkadot/util';
|
||||
|
||||
import { useTranslation } from './translate';
|
||||
import useHashInfo from './useHashInfo';
|
||||
import usePreimage from './usePreimage';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
value: HexString;
|
||||
}
|
||||
|
||||
function decodeBytes (api: ApiPromise, bytes: Bytes): Call | null {
|
||||
try {
|
||||
return api.registry.createType('Call', bytes.toU8a(true));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function Hash ({ className, value }: Props): React.ReactElement<Props> {
|
||||
function Preimage ({ className, value }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const info = useHashInfo(value);
|
||||
const decoded = useMemo(
|
||||
() => info && info.bytes && decodeBytes(api, info.bytes),
|
||||
[api, info]
|
||||
);
|
||||
const info = usePreimage(value);
|
||||
|
||||
return (
|
||||
<tr className={ className }>
|
||||
@ -45,10 +26,10 @@ function Hash ({ className, value }: Props): React.ReactElement<Props> {
|
||||
{value}
|
||||
</td>
|
||||
<td className='all'>
|
||||
{decoded && (
|
||||
{info && info.proposal && (
|
||||
<CallExpander
|
||||
labelHash={t<string>('proposal')}
|
||||
value={decoded}
|
||||
value={info.proposal}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
@ -62,4 +43,4 @@ function Hash ({ className, value }: Props): React.ReactElement<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Hash);
|
||||
export default React.memo(Preimage);
|
||||
@ -8,9 +8,9 @@ import React, { useRef } from 'react';
|
||||
import { Button, Table } from '@polkadot/react-components';
|
||||
|
||||
import Add from './Add';
|
||||
import Hash from './Hash';
|
||||
import Preimage from './Preimage';
|
||||
import { useTranslation } from './translate';
|
||||
import useHashes from './useHashes';
|
||||
import usePreimages from './usePreimages';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
@ -20,7 +20,7 @@ interface Props {
|
||||
|
||||
function Hashes ({ className }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const hashes = useHashes();
|
||||
const hashes = usePreimages();
|
||||
|
||||
const headerRef = useRef([
|
||||
[t('preimages'), 'start', 2],
|
||||
@ -39,7 +39,7 @@ function Hashes ({ className }: Props): React.ReactElement<Props> {
|
||||
header={headerRef.current}
|
||||
>
|
||||
{hashes && hashes.map((h) => (
|
||||
<Hash
|
||||
<Preimage
|
||||
key={h}
|
||||
value={h}
|
||||
/>
|
||||
@ -5,7 +5,7 @@ import React, { useRef } from 'react';
|
||||
|
||||
import { Tabs } from '@polkadot/react-components';
|
||||
|
||||
import Hashes from './Hashes';
|
||||
import Preimages from './Preimages';
|
||||
import { useTranslation } from './translate';
|
||||
|
||||
interface Props {
|
||||
@ -30,7 +30,7 @@ function App ({ basePath, className }: Props): React.ReactElement<Props> {
|
||||
basePath={basePath}
|
||||
items={tabsRef.current}
|
||||
/>
|
||||
<Hashes />
|
||||
<Preimages />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
12
packages/page-preimages/src/types.ts
Normal file
12
packages/page-preimages/src/types.ts
Normal file
@ -0,0 +1,12 @@
|
||||
// Copyright 2017-2022 @polkadot/app-preimages authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Bytes } from '@polkadot/types';
|
||||
import type { Call } from '@polkadot/types/interfaces';
|
||||
import type { PalletPreimageRequestStatus } from '@polkadot/types/lookup';
|
||||
|
||||
export interface Preimage {
|
||||
bytes: Bytes | null;
|
||||
proposal: Call | null;
|
||||
status: PalletPreimageRequestStatus | null;
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
// Copyright 2017-2022 @polkadot/app-preimages authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Bytes, Option } from '@polkadot/types';
|
||||
import type { PalletPreimageRequestStatus } from '@polkadot/types/lookup';
|
||||
import type { HexString } from '@polkadot/util/types';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';
|
||||
|
||||
interface Result {
|
||||
bytes: Bytes | null;
|
||||
status: PalletPreimageRequestStatus | null;
|
||||
}
|
||||
|
||||
function createResult (preimage: Option<Bytes>, status: Option<PalletPreimageRequestStatus>): Result {
|
||||
return {
|
||||
bytes: preimage.unwrapOr(null),
|
||||
status: status.unwrapOr(null)
|
||||
};
|
||||
}
|
||||
|
||||
function useHashInfoImpl (hash: HexString): Result | undefined {
|
||||
const { api } = useApi();
|
||||
const preimage = useCall<Option<Bytes>>(api.query.preimage.preimageFor, [hash]);
|
||||
const status = useCall<Option<PalletPreimageRequestStatus>>(api.query.preimage.statusFor, [hash]);
|
||||
|
||||
return useMemo(
|
||||
() => preimage && status && createResult(preimage, status),
|
||||
[preimage, status]
|
||||
);
|
||||
}
|
||||
|
||||
export default createNamedHook('useHashInfo', useHashInfoImpl);
|
||||
45
packages/page-preimages/src/usePreimage.ts
Normal file
45
packages/page-preimages/src/usePreimage.ts
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright 2017-2022 @polkadot/app-preimages authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ApiPromise } from '@polkadot/api';
|
||||
import type { Bytes, Option } from '@polkadot/types';
|
||||
import type { Call, Hash } from '@polkadot/types/interfaces';
|
||||
import type { PalletPreimageRequestStatus } from '@polkadot/types/lookup';
|
||||
import type { HexString } from '@polkadot/util/types';
|
||||
import type { Preimage } from './types';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';
|
||||
|
||||
function createResult (api: ApiPromise, preimage: Option<Bytes>, status: Option<PalletPreimageRequestStatus>): Preimage {
|
||||
const bytes = preimage.unwrapOr(null);
|
||||
let proposal: Call | null = null;
|
||||
|
||||
if (bytes) {
|
||||
try {
|
||||
proposal = api.registry.createType('Call', bytes.toU8a(true));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
bytes,
|
||||
proposal,
|
||||
status: status.unwrapOr(null)
|
||||
};
|
||||
}
|
||||
|
||||
function usePreimageImpl (hash: Hash | HexString): Preimage | undefined {
|
||||
const { api } = useApi();
|
||||
const preimage = useCall<Option<Bytes>>(api.query.preimage.preimageFor, [hash]);
|
||||
const status = useCall<Option<PalletPreimageRequestStatus>>(api.query.preimage.statusFor, [hash]);
|
||||
|
||||
return useMemo(
|
||||
() => preimage && status && createResult(api, preimage, status),
|
||||
[api, preimage, status]
|
||||
);
|
||||
}
|
||||
|
||||
export default createNamedHook('usePreimage', usePreimageImpl);
|
||||
@ -30,7 +30,7 @@ function filter (records: EventRecord[]): Changes<Hash> {
|
||||
return { added, removed };
|
||||
}
|
||||
|
||||
function useHashesImpl (): HexString[] | undefined {
|
||||
function usePreimagesImpl (): HexString[] | undefined {
|
||||
const { api } = useApi();
|
||||
const startValue = useMapKeys(api.query.preimage.statusFor, OPT_HASH);
|
||||
const hashes = useEventChanges([
|
||||
@ -44,4 +44,4 @@ function useHashesImpl (): HexString[] | undefined {
|
||||
);
|
||||
}
|
||||
|
||||
export default createNamedHook('useHashes', useHashesImpl);
|
||||
export default createNamedHook('usePreimages', usePreimagesImpl);
|
||||
42
packages/page-referenda/src/Deposit.tsx
Normal file
42
packages/page-referenda/src/Deposit.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
// 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 Tuple ({ 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(Tuple);
|
||||
80
packages/page-referenda/src/RefOngoing.tsx
Normal file
80
packages/page-referenda/src/RefOngoing.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
// Copyright 2017-2022 @polkadot/app-referenda authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { Referendum, ReferendumProps as Props } from './types';
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import usePreimage from '@polkadot/app-preimages/usePreimage';
|
||||
import { CallExpander, Progress } from '@polkadot/react-components';
|
||||
|
||||
import Deposit from './Deposit';
|
||||
import { useTranslation } from './translate';
|
||||
import Vote from './Vote';
|
||||
|
||||
interface Expanded {
|
||||
ongoing: Referendum['info']['asOngoing'];
|
||||
tallyTotal: BN;
|
||||
shortHash: string;
|
||||
}
|
||||
|
||||
function expandOngoing (info: Referendum['info']): Expanded {
|
||||
const ongoing = info.asOngoing;
|
||||
const hexHash = ongoing.proposalHash.toHex();
|
||||
|
||||
return {
|
||||
ongoing,
|
||||
shortHash: `${hexHash.slice(0, 8)}…${hexHash.slice(-6)}`,
|
||||
tallyTotal: ongoing.tally.ayes.add(ongoing.tally.nays)
|
||||
};
|
||||
}
|
||||
|
||||
function Ongoing ({ isMember, members, palletVote, value: { id, info } }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { ongoing: { decisionDeposit, proposalHash, submissionDeposit, tally }, shortHash, tallyTotal } = useMemo(
|
||||
() => expandOngoing(info),
|
||||
[info]
|
||||
);
|
||||
const preimage = usePreimage(proposalHash);
|
||||
|
||||
return (
|
||||
<>
|
||||
<td className='all'>
|
||||
{preimage && preimage.proposal
|
||||
? (
|
||||
<CallExpander
|
||||
labelHash={t<string>('preimage')}
|
||||
value={preimage.proposal}
|
||||
withHash
|
||||
/>
|
||||
)
|
||||
: t('preimage {{shortHash}}', { replace: { shortHash } })
|
||||
}
|
||||
</td>
|
||||
<Deposit
|
||||
decision={decisionDeposit}
|
||||
submit={submissionDeposit}
|
||||
/>
|
||||
<td className='middle chart'>
|
||||
<Progress
|
||||
total={tallyTotal}
|
||||
value={tally.ayes}
|
||||
/>
|
||||
</td>
|
||||
<td className='button'>
|
||||
{preimage && (
|
||||
<Vote
|
||||
id={id}
|
||||
isMember={isMember}
|
||||
members={members}
|
||||
palletVote={palletVote}
|
||||
preimage={preimage}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Ongoing);
|
||||
19
packages/page-referenda/src/RefOther.tsx
Normal file
19
packages/page-referenda/src/RefOther.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright 2017-2022 @polkadot/app-referenda authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ReferendumProps as Props } from './types';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
function RefOther ({ value: { info: { type } } }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<td
|
||||
className='number'
|
||||
colSpan={3}
|
||||
>
|
||||
{type}
|
||||
</td>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(RefOther);
|
||||
69
packages/page-referenda/src/RefTuple.tsx
Normal file
69
packages/page-referenda/src/RefTuple.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
// 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 type { Referendum, ReferendumProps as Props } from './types';
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import Deposit from './Deposit';
|
||||
|
||||
interface Expanded {
|
||||
decision: Option<PalletReferendaDeposit> | null;
|
||||
submit: PalletReferendaDeposit | null;
|
||||
when: Date | null;
|
||||
}
|
||||
|
||||
function expandTuple (info: Referendum['info']): Expanded {
|
||||
const data = info.isApproved
|
||||
? info.asApproved
|
||||
: info.isRejected
|
||||
? info.asRejected
|
||||
: info.isCancelled
|
||||
? info.asCancelled
|
||||
: info.isTimedOut
|
||||
? info.asTimedOut
|
||||
: null;
|
||||
|
||||
if (!data) {
|
||||
return {
|
||||
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> {
|
||||
const { decision, submit, when } = useMemo(
|
||||
() => expandTuple(info),
|
||||
[info]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<td className='all' />
|
||||
<Deposit
|
||||
decision={decision}
|
||||
submit={submit}
|
||||
/>
|
||||
<td className='number'>
|
||||
{when && (
|
||||
when.toUTCString()
|
||||
)}
|
||||
</td>
|
||||
<td className='number'>
|
||||
{info.type}
|
||||
</td>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Tuple);
|
||||
58
packages/page-referenda/src/Referenda.tsx
Normal file
58
packages/page-referenda/src/Referenda.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
// Copyright 2017-2022 @polkadot/app-referenda authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
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 Referendum from './Referendum';
|
||||
import { useTranslation } from './translate';
|
||||
import useReferenda from './useReferenda';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
members?: string[];
|
||||
palletReferenda: PalletReferenda;
|
||||
palletVote: PalletVote;
|
||||
}
|
||||
|
||||
function Referenda ({ className, members, palletReferenda, palletVote }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { allAccounts } = useAccounts();
|
||||
const referenda = useReferenda(palletReferenda);
|
||||
|
||||
const isMember = useMemo(
|
||||
() => !members || allAccounts.some((a) => members.includes(a)),
|
||||
[allAccounts, members]
|
||||
);
|
||||
|
||||
const headerRef = useRef([
|
||||
[t('referenda'), 'start', 2],
|
||||
[undefined, undefined, 3]
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Table
|
||||
className={className}
|
||||
empty={referenda && t<string>('No referendums found')}
|
||||
header={headerRef.current}
|
||||
>
|
||||
{referenda && referenda.map((r) => (
|
||||
<Referendum
|
||||
isMember={isMember}
|
||||
key={r.key}
|
||||
members={members}
|
||||
palletVote={palletVote}
|
||||
value={r}
|
||||
/>
|
||||
))}
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Referenda);
|
||||
41
packages/page-referenda/src/Referendum.tsx
Normal file
41
packages/page-referenda/src/Referendum.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright 2017-2022 @polkadot/app-referenda authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ReferendumProps as Props } from './types';
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { formatNumber } from '@polkadot/util';
|
||||
|
||||
import Ongoing from './RefOngoing';
|
||||
import Other from './RefOther';
|
||||
|
||||
const Components: Record<string, React.ComponentType<Props>> = {
|
||||
Ongoing
|
||||
};
|
||||
|
||||
function Referendum (props: Props): React.ReactElement<Props> {
|
||||
const { className, value: { id, info } } = props;
|
||||
|
||||
const Component = useMemo(
|
||||
() => Components[info.type] || Other,
|
||||
[info]
|
||||
);
|
||||
|
||||
return (
|
||||
<tr className={className}>
|
||||
<td className='number'>
|
||||
<h1>{formatNumber(id)}</h1>
|
||||
</td>
|
||||
{/* <td>
|
||||
<textarea
|
||||
rows={8}
|
||||
style={{ width: 512 }}
|
||||
>{JSON.stringify(info.toHuman(), null, 2)}</textarea>
|
||||
</td> */}
|
||||
<Component {...props} />
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Referendum);
|
||||
113
packages/page-referenda/src/Vote.tsx
Normal file
113
packages/page-referenda/src/Vote.tsx
Normal file
@ -0,0 +1,113 @@
|
||||
// Copyright 2017-2022 @polkadot/app-democracy authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Preimage } from '@polkadot/app-preimages/types';
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { PalletVote } from './types';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { Button, ConvictionDropdown, Modal, ProposedAction, TxButton, VoteAccount, VoteValue } from '@polkadot/react-components';
|
||||
import { useAccounts, useApi, useToggle } from '@polkadot/react-hooks';
|
||||
|
||||
import { useTranslation } from './translate';
|
||||
|
||||
interface Props {
|
||||
id: BN;
|
||||
isMember: boolean;
|
||||
members?: string[];
|
||||
palletVote: PalletVote;
|
||||
preimage: Preimage;
|
||||
}
|
||||
|
||||
function Voting ({ id, isMember, members, palletVote, preimage: { proposal } }: Props): React.ReactElement<Props> | null {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const { hasAccounts } = useAccounts();
|
||||
const [accountId, setAccountId] = useState<string | null>(null);
|
||||
const [balance, setBalance] = useState<BN | undefined>();
|
||||
const [conviction, setConviction] = useState(0);
|
||||
const [isVotingOpen, toggleVoting] = useToggle();
|
||||
|
||||
if (!hasAccounts) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{isVotingOpen && (
|
||||
<Modal
|
||||
header={t<string>('Vote on proposal')}
|
||||
onClose={toggleVoting}
|
||||
size='large'
|
||||
>
|
||||
<Modal.Content>
|
||||
<Modal.Columns hint={t<string>('If this proposal is passed, the changes will be applied via dispatch and the deposit returned.')}>
|
||||
<ProposedAction
|
||||
idNumber={id}
|
||||
proposal={proposal}
|
||||
/>
|
||||
</Modal.Columns>
|
||||
<Modal.Columns hint={t<string>('The vote will be recorded for this account. If another account delegated to this one, the delegated votes will also be counted.')}>
|
||||
<VoteAccount
|
||||
filter={members}
|
||||
onChange={setAccountId}
|
||||
/>
|
||||
</Modal.Columns>
|
||||
<Modal.Columns
|
||||
hint={
|
||||
<>
|
||||
<p>{t<string>('The balance associated with the vote will be locked as per the conviction specified and will not be available for transfer during this period.')}</p>
|
||||
<p>{t<string>('Conviction locks do overlap and are not additive, meaning that funds locked during a previous vote can be locked again.')}</p>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<VoteValue
|
||||
accountId={accountId}
|
||||
autoFocus
|
||||
onChange={setBalance}
|
||||
/>
|
||||
<ConvictionDropdown
|
||||
help={t<string>('The conviction to use for this vote, with an appropriate lock period.')}
|
||||
label={t<string>('conviction')}
|
||||
onChange={setConviction}
|
||||
value={conviction}
|
||||
/>
|
||||
</Modal.Columns>
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<TxButton
|
||||
accountId={accountId}
|
||||
icon='ban'
|
||||
isDisabled={!isMember}
|
||||
label={t<string>('Vote Nay')}
|
||||
onStart={toggleVoting}
|
||||
params={
|
||||
[id, { Standard: { balance, vote: { aye: false, conviction } } }]
|
||||
}
|
||||
tx={api.tx[palletVote].vote}
|
||||
/>
|
||||
<TxButton
|
||||
accountId={accountId}
|
||||
icon='check'
|
||||
isDisabled={!isMember}
|
||||
label={t<string>('Vote Aye')}
|
||||
onStart={toggleVoting}
|
||||
params={
|
||||
[id, { Standard: { balance, vote: { aye: true, conviction } } }]
|
||||
}
|
||||
tx={api.tx[palletVote].vote}
|
||||
/>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
)}
|
||||
<Button
|
||||
icon='check-to-slot'
|
||||
label={t<string>('Vote')}
|
||||
onClick={toggleVoting}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Voting);
|
||||
@ -1,16 +1,40 @@
|
||||
// Copyright 2017-2022 @polkadot/app-referenda authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React from 'react';
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
import { Tabs } from '@polkadot/react-components';
|
||||
|
||||
import Referenda from './Referenda';
|
||||
import { useTranslation } from './translate';
|
||||
|
||||
interface Props {
|
||||
basePath: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function App ({ className }: Props): React.ReactElement<Props> {
|
||||
function App ({ basePath, className }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const tabsRef = useRef([
|
||||
{
|
||||
isRoot: true,
|
||||
name: 'overview',
|
||||
text: t<string>('Overview')
|
||||
}
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className={className} />
|
||||
<main className={className}>
|
||||
<Tabs
|
||||
basePath={basePath}
|
||||
items={tabsRef.current}
|
||||
/>
|
||||
<Referenda
|
||||
palletReferenda='referenda'
|
||||
palletVote='convictionVoting'
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
23
packages/page-referenda/src/types.ts
Normal file
23
packages/page-referenda/src/types.ts
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright 2017-2022 @polkadot/app-referenda authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { PalletReferendaReferendumInfoConvictionVotingTally } from '@polkadot/types/lookup';
|
||||
import type { BN } from '@polkadot/util';
|
||||
|
||||
export interface Referendum {
|
||||
id: BN;
|
||||
info: PalletReferendaReferendumInfoConvictionVotingTally;
|
||||
key: string;
|
||||
}
|
||||
|
||||
export interface ReferendumProps {
|
||||
className?: string;
|
||||
isMember: boolean;
|
||||
members?: string[];
|
||||
palletVote: PalletVote;
|
||||
value: Referendum;
|
||||
}
|
||||
|
||||
export type PalletReferenda = 'referenda';
|
||||
|
||||
export type PalletVote = 'convictionVoting';
|
||||
41
packages/page-referenda/src/useIds.ts
Normal file
41
packages/page-referenda/src/useIds.ts
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright 2017-2022 @polkadot/app-referenda authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Changes } from '@polkadot/react-hooks/useEventChanges';
|
||||
import type { StorageKey, u32 } from '@polkadot/types';
|
||||
import type { EventRecord } from '@polkadot/types/interfaces';
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { PalletReferenda } from './types';
|
||||
|
||||
import { createNamedHook, useApi, useEventChanges, useMapKeys } from '@polkadot/react-hooks';
|
||||
|
||||
const OPT_ID = {
|
||||
transform: (keys: StorageKey<[u32]>[]): u32[] =>
|
||||
keys.map(({ args: [id] }) => id)
|
||||
};
|
||||
|
||||
function filter (records: EventRecord[]): Changes<u32> {
|
||||
const added: u32[] = [];
|
||||
const removed: u32[] = [];
|
||||
|
||||
records.forEach(({ event: { data: [id], method } }): void => {
|
||||
if (method === 'Submitted') {
|
||||
added.push(id as u32);
|
||||
} else {
|
||||
removed.push(id as u32);
|
||||
}
|
||||
});
|
||||
|
||||
return { added, removed };
|
||||
}
|
||||
|
||||
function useIdsImpl (palletReferenda: PalletReferenda): BN[] | undefined {
|
||||
const { api } = useApi();
|
||||
const startValue = useMapKeys(api.query[palletReferenda].referendumInfoFor, OPT_ID);
|
||||
|
||||
return useEventChanges([
|
||||
api.events[palletReferenda].Submitted
|
||||
], filter, startValue);
|
||||
}
|
||||
|
||||
export default createNamedHook('useIds', useIdsImpl);
|
||||
78
packages/page-referenda/src/useReferenda.ts
Normal file
78
packages/page-referenda/src/useReferenda.ts
Normal file
@ -0,0 +1,78 @@
|
||||
// Copyright 2017-2022 @polkadot/app-referenda authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Option } from '@polkadot/types';
|
||||
import type { PalletReferendaReferendumInfoConvictionVotingTally } from '@polkadot/types/lookup';
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { PalletReferenda, Referendum } from './types';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';
|
||||
|
||||
import useIds from './useIds';
|
||||
|
||||
const ORDER = <const> ['Ongoing', 'Approved', 'Rejected', 'Cancelled', 'Killed', 'TimedOut'];
|
||||
|
||||
function sortOngoing (a: Referendum, b: Referendum): number {
|
||||
const ona = a.info.asOngoing;
|
||||
const onb = b.info.asOngoing;
|
||||
|
||||
return ona.track.cmp(onb.track) || a.id.cmp(b.id);
|
||||
}
|
||||
|
||||
function sortOther (a: Referendum, b: Referendum): number {
|
||||
const idxa = ORDER.indexOf(a.info.type);
|
||||
const idxb = ORDER.indexOf(b.info.type);
|
||||
|
||||
return (
|
||||
idxa === idxb
|
||||
? 0
|
||||
: idxa === -1
|
||||
? 1
|
||||
: idxb === -1
|
||||
? -1
|
||||
: idxa - idxb
|
||||
) || a.id.cmp(b.id);
|
||||
}
|
||||
|
||||
const OPT_MULTI = {
|
||||
transform: ([[ids], all]: [[BN[]], Option<PalletReferendaReferendumInfoConvictionVotingTally>[]]) =>
|
||||
all
|
||||
.map((o, i) =>
|
||||
o.isSome
|
||||
? [ids[i], o.unwrap()]
|
||||
: null
|
||||
)
|
||||
.filter((r): r is [BN, PalletReferendaReferendumInfoConvictionVotingTally] => !!r)
|
||||
.map(([id, info]): Referendum => ({
|
||||
id,
|
||||
info,
|
||||
key: id.toString()
|
||||
}))
|
||||
.sort((a, b) =>
|
||||
a.info.isOngoing === b.info.isOngoing
|
||||
? a.info.isOngoing
|
||||
? sortOngoing(a, b)
|
||||
: sortOther(a, b)
|
||||
: a.info.isOngoing
|
||||
? -1
|
||||
: 1
|
||||
),
|
||||
withParamsTransform: true
|
||||
};
|
||||
|
||||
function useReferendaImpl (palletReferenda: PalletReferenda): Referendum[] | undefined {
|
||||
const { api } = useApi();
|
||||
const ids = useIds(palletReferenda);
|
||||
const referenda = useCall(ids && ids.length !== 0 && api.query[palletReferenda].referendumInfoFor.multi, [ids], OPT_MULTI);
|
||||
|
||||
return useMemo(
|
||||
() => ids && ids.length === 0
|
||||
? []
|
||||
: referenda,
|
||||
[ids, referenda]
|
||||
);
|
||||
}
|
||||
|
||||
export default createNamedHook('useReferenda', useReferendaImpl);
|
||||
@ -1,7 +1,7 @@
|
||||
// Copyright 2017-2022 @polkadot/app-democracy authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Proposal } from '@polkadot/types/interfaces';
|
||||
import type { Call } from '@polkadot/types/interfaces';
|
||||
import type { BN } from '@polkadot/util';
|
||||
|
||||
import React from 'react';
|
||||
@ -9,7 +9,7 @@ import styled from 'styled-components';
|
||||
|
||||
import { formatNumber, isString } from '@polkadot/util';
|
||||
|
||||
import Call from './Call';
|
||||
import CallDisplay from './Call';
|
||||
import Expander from './Expander';
|
||||
import { useTranslation } from './translate';
|
||||
import TreasuryProposal from './TreasuryProposal';
|
||||
@ -17,7 +17,7 @@ import { isTreasuryProposalVote } from './util';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
proposal?: Proposal | null;
|
||||
proposal?: Call | null;
|
||||
idNumber: BN | number | string;
|
||||
withLinks?: boolean;
|
||||
expandNested?: boolean;
|
||||
@ -55,7 +55,7 @@ function ProposedAction ({ className = '', expandNested, idNumber, proposal, wit
|
||||
proposalId={proposal.args[0].toString()}
|
||||
/>
|
||||
)
|
||||
: <Call value={proposal} />
|
||||
: <CallDisplay value={proposal} />
|
||||
}
|
||||
</Expander>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user