Re-add tc with length & weights (#2840)
* Re-add tc with length & weights * Apply fixes * Move useVotingstatus
This commit is contained in:
parent
182c47fa9c
commit
0789371b71
@ -8,13 +8,13 @@ import { DeriveCollectiveProposal } from '@polkadot/api-derive/types';
|
||||
import React from 'react';
|
||||
import ProposalCell from '@polkadot/app-democracy/Overview/ProposalCell';
|
||||
import { LinkExternal } from '@polkadot/react-components';
|
||||
import { useVotingStatus } from '@polkadot/react-hooks';
|
||||
import { BlockToTime } from '@polkadot/react-query';
|
||||
import { formatNumber } from '@polkadot/util';
|
||||
|
||||
import Close from './Close';
|
||||
import Voters from './Votes';
|
||||
import Voting from './Voting';
|
||||
import useVotingStatus from './useVotingStatus';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
@ -25,7 +25,7 @@ interface Props {
|
||||
}
|
||||
|
||||
function Motion ({ className = '', isMember, members, motion: { hash, proposal, votes }, prime }: Props): React.ReactElement<Props> | null {
|
||||
const { isCloseable, isVoteable, remainingBlocks } = useVotingStatus(votes, members.length);
|
||||
const { isCloseable, isVoteable, remainingBlocks } = useVotingStatus(votes, members.length, 'council');
|
||||
|
||||
if (!votes) {
|
||||
return null;
|
||||
|
||||
@ -17,6 +17,11 @@ interface Props {
|
||||
threshold: VoteThreshold;
|
||||
}
|
||||
|
||||
interface ProposalState {
|
||||
proposal?: SubmittableExtrinsic<'promise'> | null;
|
||||
proposalLength: number;
|
||||
}
|
||||
|
||||
const ONE_MIN = (1 * 60) / 6;
|
||||
const DEF_DELAY = new BN(ONE_MIN);
|
||||
const DEF_VOTING = new BN(ONE_MIN * 60 * 3);
|
||||
@ -30,7 +35,7 @@ function Fasttrack ({ imageHash, threshold }: Props): React.ReactElement<Props>
|
||||
const [accountId, setAcountId] = useState<string | null>(null);
|
||||
const [delayBlocks, setDelayBlocks] = useState<BN | undefined>(DEF_DELAY);
|
||||
const [votingBlocks, setVotingBlocks] = useState<BN | undefined>(DEF_VOTING);
|
||||
const [proposal, setProposal] = useState<SubmittableExtrinsic<'promise'> | null>(null);
|
||||
const [{ proposal, proposalLength }, setProposal] = useState<ProposalState>({ proposalLength: 0 });
|
||||
const memberThreshold = useMemo(
|
||||
() => Math.ceil(members.length * 0.5),
|
||||
[members]
|
||||
@ -41,11 +46,11 @@ function Fasttrack ({ imageHash, threshold }: Props): React.ReactElement<Props>
|
||||
}, [isMember, threshold]);
|
||||
|
||||
useEffect((): void => {
|
||||
setProposal(
|
||||
() => delayBlocks && delayBlocks.gtn(0) && votingBlocks && votingBlocks.gtn(0)
|
||||
? api.tx.democracy.fastTrack(imageHash, votingBlocks, delayBlocks)
|
||||
: null
|
||||
);
|
||||
const proposal = delayBlocks && delayBlocks.gtn(0) && votingBlocks && votingBlocks.gtn(0)
|
||||
? api.tx.democracy.fastTrack(imageHash, votingBlocks, delayBlocks)
|
||||
: null;
|
||||
|
||||
setProposal({ proposal, proposalLength: proposal?.length || 0 });
|
||||
}, [api, delayBlocks, imageHash, votingBlocks]);
|
||||
|
||||
return (
|
||||
@ -92,7 +97,11 @@ function Fasttrack ({ imageHash, threshold }: Props): React.ReactElement<Props>
|
||||
isPrimary
|
||||
label={t<string>('Fast track')}
|
||||
onStart={toggleFasttrack}
|
||||
params={[memberThreshold, proposal]}
|
||||
params={
|
||||
api.tx.technicalCommittee.propose.meta.args.length === 3
|
||||
? [threshold, proposal, proposalLength]
|
||||
: [memberThreshold, proposal]
|
||||
}
|
||||
tx='technicalCommittee.propose'
|
||||
/>
|
||||
</Modal.Actions>
|
||||
|
||||
95
packages/page-tech-comm/src/Proposals/Close.tsx
Normal file
95
packages/page-tech-comm/src/Proposals/Close.tsx
Normal file
@ -0,0 +1,95 @@
|
||||
// Copyright 2017-2020 @polkadot/app-tech-comm authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { Hash, Proposal, ProposalIndex, Weight } from '@polkadot/types/interfaces';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, InputAddress, Modal, ProposedAction, TxButton } from '@polkadot/react-components';
|
||||
import { useAccounts, useApi, useToggle } from '@polkadot/react-hooks';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
|
||||
interface Props {
|
||||
hash: Hash;
|
||||
idNumber: ProposalIndex;
|
||||
isDisabled: boolean;
|
||||
members: string[];
|
||||
proposal: Proposal;
|
||||
}
|
||||
|
||||
function Close ({ hash, idNumber, isDisabled, members, proposal }: Props): React.ReactElement<Props> | null {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const { allAccounts } = useAccounts();
|
||||
const [isOpen, toggleOpen] = useToggle();
|
||||
const [accountId, setAccountId] = useState<string | null>(null);
|
||||
const [proposalLength] = useState(proposal.encodedLength);
|
||||
const [proposalWeight, setProposalWeight] = useState<Weight | null>(null);
|
||||
|
||||
useEffect((): void => {
|
||||
allAccounts.length && (async () => setProposalWeight(
|
||||
(await api.tx(proposal).paymentInfo(allAccounts[0])).weight
|
||||
))().catch(console.error);
|
||||
}, [api, allAccounts, proposal]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isOpen && (
|
||||
<Modal
|
||||
header={t<string>('Close proposal')}
|
||||
size='large'
|
||||
>
|
||||
<Modal.Content>
|
||||
<Modal.Columns>
|
||||
<Modal.Column>
|
||||
<ProposedAction
|
||||
idNumber={idNumber}
|
||||
proposal={proposal}
|
||||
/>
|
||||
</Modal.Column>
|
||||
<Modal.Column>
|
||||
<p>{t<string>('The proposal that will be affected. Once closed for the current voting round, it would need to be re-submitted for a subsequent voting round.')}</p>
|
||||
</Modal.Column>
|
||||
</Modal.Columns>
|
||||
<Modal.Columns>
|
||||
<Modal.Column>
|
||||
<InputAddress
|
||||
filter={members}
|
||||
help={t<string>('Select the account you wish close the proposal with.')}
|
||||
label={t<string>('sending account')}
|
||||
onChange={setAccountId}
|
||||
type='account'
|
||||
/>
|
||||
</Modal.Column>
|
||||
<Modal.Column>
|
||||
<p>{t<string>('The committee account that will apply the close for the current round.')}</p>
|
||||
</Modal.Column>
|
||||
</Modal.Columns>
|
||||
</Modal.Content>
|
||||
<Modal.Actions onCancel={toggleOpen}>
|
||||
<TxButton
|
||||
accountId={accountId}
|
||||
isDisabled={!proposalWeight}
|
||||
onStart={toggleOpen}
|
||||
params={
|
||||
api.tx.technicalCommittee.close.meta.args.length === 4
|
||||
? [hash, idNumber, proposalWeight, proposalLength]
|
||||
: [hash, idNumber]
|
||||
}
|
||||
tx='technicalCommittee.close'
|
||||
/>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
)}
|
||||
<Button
|
||||
icon='cancel'
|
||||
isDisabled={isDisabled}
|
||||
label={t<string>('Close')}
|
||||
onClick={toggleOpen}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Close);
|
||||
@ -2,36 +2,42 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { AccountId, Proposal as ProposalType, Votes } from '@polkadot/types/interfaces';
|
||||
import { AccountId, Hash, Proposal as ProposalType, Votes } from '@polkadot/types/interfaces';
|
||||
|
||||
import React from 'react';
|
||||
import { AddressMini } from '@polkadot/react-components';
|
||||
import { useApi, useCall } from '@polkadot/react-hooks';
|
||||
import { useApi, useCall, useVotingStatus } from '@polkadot/react-hooks';
|
||||
import { BlockToTime } from '@polkadot/react-query';
|
||||
import ProposalCell from '@polkadot/app-democracy/Overview/ProposalCell';
|
||||
import { Option } from '@polkadot/types';
|
||||
import { formatNumber } from '@polkadot/util';
|
||||
|
||||
import Close from './Close';
|
||||
import Voting from './Voting';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
imageHash: string;
|
||||
imageHash: Hash;
|
||||
isMember: boolean;
|
||||
members: string[];
|
||||
prime?: AccountId | null;
|
||||
}
|
||||
|
||||
function Proposal ({ className = '', imageHash, prime }: Props): React.ReactElement<Props> | null {
|
||||
function Proposal ({ className = '', imageHash, isMember, members, prime }: Props): React.ReactElement<Props> | null {
|
||||
const { api } = useApi();
|
||||
const optProposal = useCall<Option<ProposalType>>(api.query.technicalCommittee.proposalOf, [imageHash]);
|
||||
const proposal = useCall<ProposalType | null>(api.query.technicalCommittee.proposalOf, [imageHash], {
|
||||
transform: (optProp: Option<ProposalType>) => optProp.unwrapOr(null)
|
||||
});
|
||||
const votes = useCall<Votes | null>(api.query.technicalCommittee.voting, [imageHash], {
|
||||
transform: (optVotes: Option<Votes>) => optVotes.unwrapOr(null)
|
||||
});
|
||||
const { isCloseable, isVoteable, remainingBlocks } = useVotingStatus(votes, members.length, 'technicalCommittee');
|
||||
|
||||
if (!optProposal?.isSome || !votes) {
|
||||
if (!proposal || !votes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const proposal = optProposal.unwrap();
|
||||
const { ayes, index, nays, threshold } = votes;
|
||||
const { ayes, end, index, nays, threshold } = votes;
|
||||
|
||||
return (
|
||||
<tr className={className}>
|
||||
@ -43,6 +49,14 @@ function Proposal ({ className = '', imageHash, prime }: Props): React.ReactElem
|
||||
<td className='number'>
|
||||
{formatNumber(ayes.length)}/{formatNumber(threshold)}
|
||||
</td>
|
||||
<td className='number together'>
|
||||
{remainingBlocks && end && (
|
||||
<>
|
||||
<BlockToTime blocks={remainingBlocks} />
|
||||
#{formatNumber(end)}
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
<td className='address'>
|
||||
{ayes.map((address, index): React.ReactNode => (
|
||||
<AddressMini
|
||||
@ -62,11 +76,22 @@ function Proposal ({ className = '', imageHash, prime }: Props): React.ReactElem
|
||||
))}
|
||||
</td>
|
||||
<td className='button'>
|
||||
<Voting
|
||||
hash={imageHash}
|
||||
prime={prime}
|
||||
proposalId={index}
|
||||
/>
|
||||
{isVoteable && (
|
||||
<Voting
|
||||
hash={imageHash}
|
||||
prime={prime}
|
||||
proposalId={index}
|
||||
/>
|
||||
)}
|
||||
{isCloseable && (
|
||||
<Close
|
||||
hash={imageHash}
|
||||
idNumber={index}
|
||||
isDisabled={!isMember}
|
||||
members={members}
|
||||
proposal={proposal}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
||||
@ -16,12 +16,17 @@ interface Props {
|
||||
members: string[];
|
||||
}
|
||||
|
||||
interface ProposalState {
|
||||
proposal?: SubmittableExtrinsic<'promise'> | null;
|
||||
proposalLength: number;
|
||||
}
|
||||
|
||||
function Propose ({ isMember, members }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { apiDefaultTxSudo } = useApi();
|
||||
const { api, apiDefaultTxSudo } = useApi();
|
||||
const { isOpen, onClose, onOpen } = useModal();
|
||||
const [accountId, setAcountId] = useState<string | null>(null);
|
||||
const [proposal, setProposal] = useState<SubmittableExtrinsic<'promise'> | null>(null);
|
||||
const [{ proposal, proposalLength }, setProposal] = useState<ProposalState>({ proposalLength: 0 });
|
||||
const [[threshold, hasThreshold], setThreshold] = useState<[BN | null, boolean]>([
|
||||
new BN(members.length / 2 + 1),
|
||||
true
|
||||
@ -34,7 +39,10 @@ function Propose ({ isMember, members }: Props): React.ReactElement<Props> {
|
||||
);
|
||||
|
||||
const _onChangeExtrinsic = useCallback(
|
||||
(method?: SubmittableExtrinsic<'promise'>): void => setProposal(() => method || null),
|
||||
(proposal?: SubmittableExtrinsic<'promise'>): void => setProposal({
|
||||
proposal,
|
||||
proposalLength: proposal?.length || 0
|
||||
}),
|
||||
[]
|
||||
);
|
||||
const _onChangeThreshold = useCallback(
|
||||
@ -78,7 +86,11 @@ function Propose ({ isMember, members }: Props): React.ReactElement<Props> {
|
||||
accountId={accountId}
|
||||
isDisabled={!hasThreshold || !proposal}
|
||||
onStart={onClose}
|
||||
params={[threshold, proposal]}
|
||||
params={
|
||||
api.tx.technicalCommittee.propose.meta.args.length === 3
|
||||
? [threshold, proposal, proposalLength]
|
||||
: [threshold, proposal]
|
||||
}
|
||||
tx='technicalCommittee.propose'
|
||||
/>
|
||||
</Modal.Actions>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { AccountId } from '@polkadot/types/interfaces';
|
||||
import { AccountId, Hash } from '@polkadot/types/interfaces';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
@ -13,7 +13,7 @@ import { isBoolean } from '@polkadot/util';
|
||||
import { useTranslation } from '../translate';
|
||||
|
||||
interface Props {
|
||||
hash: string;
|
||||
hash: Hash | string;
|
||||
prime?: AccountId | null;
|
||||
proposalId: BN | number;
|
||||
}
|
||||
|
||||
@ -37,8 +37,10 @@ function Proposals ({ className = '', isMember, members, prime, proposals }: Pro
|
||||
>
|
||||
{proposals?.map((hash: Hash): React.ReactNode => (
|
||||
<Proposal
|
||||
imageHash={hash.toHex()}
|
||||
imageHash={hash}
|
||||
isMember={isMember}
|
||||
key={hash.toHex()}
|
||||
members={members}
|
||||
prime={prime}
|
||||
/>
|
||||
))}
|
||||
|
||||
@ -24,3 +24,4 @@ export { default as useRegistrars } from './useRegistrars';
|
||||
export { default as useStashIds } from './useStashIds';
|
||||
export { default as useSudo } from './useSudo';
|
||||
export { default as useToggle } from './useToggle';
|
||||
export { default as useVotingStatus } from './useVotingStatus';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright 2017-2020 @polkadot/app-democracy authors & contributors
|
||||
// Copyright 2017-2020 @polkadot/react-hooks authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
@ -7,7 +7,9 @@ import { BlockNumber, Votes } from '@polkadot/types/interfaces';
|
||||
import BN from 'bn.js';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ApiPromise } from '@polkadot/api';
|
||||
import { useApi, useCall } from '@polkadot/react-hooks';
|
||||
|
||||
import useApi from './useApi';
|
||||
import useCall from './useCall';
|
||||
|
||||
interface State {
|
||||
isCloseable: boolean;
|
||||
@ -15,7 +17,7 @@ interface State {
|
||||
remainingBlocks: BN | null;
|
||||
}
|
||||
|
||||
function getStatus (api: ApiPromise, bestNumber: BlockNumber, votes: Votes, numMembers: number): State {
|
||||
function getStatus (api: ApiPromise, bestNumber: BlockNumber, votes: Votes, numMembers: number, section: 'council' | 'technicalCommittee'): State {
|
||||
if (!votes.end) {
|
||||
return {
|
||||
isCloseable: false,
|
||||
@ -29,7 +31,7 @@ function getStatus (api: ApiPromise, bestNumber: BlockNumber, votes: Votes, numM
|
||||
const isFailing = votes.threshold.gtn(Math.abs(numMembers - votes.nays.length));
|
||||
|
||||
return {
|
||||
isCloseable: api.tx.council.close.meta.args.length === 2
|
||||
isCloseable: api.tx[section].close.meta.args.length === 2
|
||||
? isEnd
|
||||
: isEnd || isPassing || isFailing,
|
||||
isVoteable: !isEnd,
|
||||
@ -39,16 +41,16 @@ function getStatus (api: ApiPromise, bestNumber: BlockNumber, votes: Votes, numM
|
||||
};
|
||||
}
|
||||
|
||||
export default function useVotingStatus (votes: Votes | null, numMembers: number): State {
|
||||
export default function useVotingStatus (votes: Votes | null | undefined, numMembers: number, section: 'council' | 'technicalCommittee'): State {
|
||||
const { api } = useApi();
|
||||
const bestNumber = useCall<BlockNumber>(api.derive.chain.bestNumber, []);
|
||||
const [state, setState] = useState<State>({ isCloseable: false, isVoteable: false, remainingBlocks: null });
|
||||
|
||||
useEffect((): void => {
|
||||
bestNumber && votes && setState(
|
||||
getStatus(api, bestNumber, votes, numMembers)
|
||||
getStatus(api, bestNumber, votes, numMembers, section)
|
||||
);
|
||||
}, [api, bestNumber, numMembers, votes]);
|
||||
}, [api, bestNumber, numMembers, section, votes]);
|
||||
|
||||
return state;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user