Re-add tc with length & weights (#2840)

* Re-add tc with length & weights

* Apply fixes

* Move useVotingstatus
This commit is contained in:
Jaco Greeff 2020-05-28 08:54:01 +02:00 committed by GitHub
parent 182c47fa9c
commit 0789371b71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 182 additions and 36 deletions

View File

@ -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;

View File

@ -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>

View 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);

View File

@ -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>
);

View File

@ -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>

View File

@ -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;
}

View File

@ -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}
/>
))}

View File

@ -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';

View File

@ -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;
}