From 3dfaf17ca83260494b907a487c32cccc81d8bd3c Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Sun, 29 Mar 2020 10:04:21 +0200 Subject: [PATCH] Combine status event types (#2478) * Combine status event types * Adjust some tables * Adjust targets --- packages/page-council/src/Motions/Motion.tsx | 7 +- packages/page-council/src/Motions/index.tsx | 6 +- .../page-staking/src/Targets/Validator.tsx | 2 +- .../page-tech-comm/src/Proposals/Proposal.tsx | 4 +- .../page-tech-comm/src/Proposals/Propose.tsx | 12 +- .../page-tech-comm/src/Proposals/index.tsx | 8 +- .../page-treasury/src/Overview/Proposal.tsx | 13 +- .../page-treasury/src/Overview/Proposals.tsx | 8 +- .../react-components/src/Button/Group.tsx | 4 + .../react-components/src/Status/Queue.tsx | 306 ++++++++++-------- packages/react-components/src/Table.tsx | 11 +- packages/react-hooks/src/useAccounts.ts | 3 +- packages/react-hooks/src/useAddresses.ts | 3 +- packages/react-hooks/src/useMembers.ts | 6 +- packages/react-hooks/src/useOwnStashes.ts | 8 +- packages/react-hooks/src/useRegistrars.ts | 4 +- packages/react-hooks/src/useSudo.ts | 4 +- packages/react-signer/src/Modal.tsx | 31 +- 18 files changed, 251 insertions(+), 189 deletions(-) diff --git a/packages/page-council/src/Motions/Motion.tsx b/packages/page-council/src/Motions/Motion.tsx index 4f453b90d3..6a2853477e 100644 --- a/packages/page-council/src/Motions/Motion.tsx +++ b/packages/page-council/src/Motions/Motion.tsx @@ -51,7 +51,7 @@ function Motion ({ className, isMember, members, motion: { hash, proposal, votes )} - + {ayes.map((address, index): React.ReactNode => ( ))} - + {nays.map((address, index): React.ReactNode => ( + + diff --git a/packages/page-council/src/Motions/index.tsx b/packages/page-council/src/Motions/index.tsx index 7354c86583..4a8c77ed97 100644 --- a/packages/page-council/src/Motions/index.tsx +++ b/packages/page-council/src/Motions/index.tsx @@ -51,9 +51,9 @@ function Proposals ({ className, motions, prime }: Props): React.ReactElement  {t('threshold')} {t('voting end')} - {t('aye')} - {t('nay')} -   + {t('aye')} + {t('nay')} +   {motions?.map((motion: DeriveCollectiveProposal): React.ReactNode => ( diff --git a/packages/page-staking/src/Targets/Validator.tsx b/packages/page-staking/src/Targets/Validator.tsx index fc85195443..ff9cc11dd4 100644 --- a/packages/page-staking/src/Targets/Validator.tsx +++ b/packages/page-staking/src/Targets/Validator.tsx @@ -32,7 +32,7 @@ function Validator ({ info: { accountId, bondOther, bondOwn, bondTotal, commissi toggleFavorite={toggleFavorite} /> {formatNumber(rankOverall)} - + diff --git a/packages/page-tech-comm/src/Proposals/Proposal.tsx b/packages/page-tech-comm/src/Proposals/Proposal.tsx index 77cc90fdc4..0424b2463f 100644 --- a/packages/page-tech-comm/src/Proposals/Proposal.tsx +++ b/packages/page-tech-comm/src/Proposals/Proposal.tsx @@ -41,7 +41,7 @@ function Proposal ({ className, imageHash, prime }: Props): React.ReactElement

{formatNumber(ayes.length)}/{formatNumber(threshold)} - + {ayes.map((address, index): React.ReactNode => ( ))} - + {nays.map((address, index): React.ReactNode => ( { [members] ); - const _onChangeExtrinsic = (method?: SubmittableExtrinsic<'promise'>): void => - setProposal(() => method || null); - const _onChangeThreshold = (threshold?: BN): void => - setThreshold([threshold || null, _hasThreshold(threshold)]); + const _onChangeExtrinsic = useCallback( + (method?: SubmittableExtrinsic<'promise'>): void => setProposal(() => method || null), + [] + ); + const _onChangeThreshold = useCallback( + (threshold?: BN): void => setThreshold([threshold || null, _hasThreshold(threshold)]), + [_hasThreshold] + ); return ( <> diff --git a/packages/page-tech-comm/src/Proposals/index.tsx b/packages/page-tech-comm/src/Proposals/index.tsx index 000bcd9189..d22db211de 100644 --- a/packages/page-tech-comm/src/Proposals/index.tsx +++ b/packages/page-tech-comm/src/Proposals/index.tsx @@ -29,13 +29,13 @@ function Proposals ({ className, isMember, members, prime, proposals }: Props): - - - + + + - {proposals?.map((hash: Hash): React.ReactNode => ( + {proposals.map((hash: Hash): React.ReactNode => (

{formatNumber(id)}

-
- - + + diff --git a/packages/page-treasury/src/Overview/Proposals.tsx b/packages/page-treasury/src/Overview/Proposals.tsx index 8403a07314..9addb24cfa 100644 --- a/packages/page-treasury/src/Overview/Proposals.tsx +++ b/packages/page-treasury/src/Overview/Proposals.tsx @@ -48,10 +48,10 @@ function ProposalsBase ({ className, isApprovals, proposals }: Props): React.Rea
 {t('threshold')}{t('aye')}{t('nay')}{t('threshold')}{t('aye')}{t('nay')}   + - - + + + {withSend && ( <> @@ -52,9 +52,12 @@ function ProposalDisplay ({ className, isMember, proposal: { council, id, propos /> )} +
- - - - + + + + {proposals.map((proposal): React.ReactNode => ( diff --git a/packages/react-components/src/Button/Group.tsx b/packages/react-components/src/Button/Group.tsx index 47b844414c..0b03dfe3da 100644 --- a/packages/react-components/src/Button/Group.tsx +++ b/packages/react-components/src/Button/Group.tsx @@ -35,6 +35,10 @@ const Memo = React.memo(styled(ButtonGroup)` margin-top: 0.75rem; } + > .ui.buttons { + vertical-align: middle; + } + &.centered { margin-bottom: 0.5rem; text-align: center; diff --git a/packages/react-components/src/Status/Queue.tsx b/packages/react-components/src/Status/Queue.tsx index 9e80d8d26e..8d7c62536f 100644 --- a/packages/react-components/src/Status/Queue.tsx +++ b/packages/react-components/src/Status/Queue.tsx @@ -8,7 +8,7 @@ import { ITuple, SignerPayloadJSON } from '@polkadot/types/types'; import { BareProps } from '../types'; import { ActionStatus, PartialQueueTxExtrinsic, PartialQueueTxRpc, QueueStatus, QueueTx, QueueTxExtrinsic, QueueTxRpc, QueueTxStatus, SignerCallback } from './types'; -import React, { useRef, useState } from 'react'; +import React, { useCallback, useRef, useState } from 'react'; import { SubmittableResult } from '@polkadot/api'; import { registry } from '@polkadot/react-api'; import jsonrpc from '@polkadot/types/interfaces/jsonrpc'; @@ -21,153 +21,195 @@ export interface Props extends BareProps { children: React.ReactNode; } +interface StatusCount { + count: number; + status: ActionStatus; +} + let nextId = 0; const REMOVE_TIMEOUT = 7500; const SUBMIT_RPC = jsonrpc.author.submitAndWatchExtrinsic; -function Queue ({ children }: Props): React.ReactElement { - const [_stqueue, _setStQueue] = useState([]); - const [_txqueue, _setTxQueue] = useState([]); - const stRef = useRef(_stqueue); - const txRef = useRef(_txqueue); +function mergeStatus (status: ActionStatus[]): ActionStatus[] { + return status + .reduce((result: StatusCount[], status): StatusCount[] => { + const prev = result.find(({ status: prev }) => prev.action === status.action && prev.status === status.status); - const setStQueue = (st: QueueStatus[]): void => { - stRef.current = st; - _setStQueue(st); - }; - const setTxQueue = (tx: QueueTx[]): void => { - txRef.current = tx; - _setTxQueue(tx); - }; + if (prev) { + prev.count++; + } else { + result.push({ count: 1, status }); + } - const addToTxQueue = (value: QueueTxExtrinsic | QueueTxRpc | QueueTx): void => { - const id = ++nextId; - const removeItem = (): void => - setTxQueue([...txRef.current.map((item): QueueTx => - item.id === id - ? { ...item, status: 'completed' } - : item - )]); + return result; + }, []) + .map(({ count, status }): ActionStatus => + count === 1 + ? status + : { ...status, action: `${status.action} (x${count})` } + ); +} - setTxQueue([...txRef.current, { - ...value, - id, - removeItem, - rpc: (value as QueueTxRpc).rpc || SUBMIT_RPC, - status: 'queued' - }]); - }; +function extractEvents (result?: SubmittableResult): ActionStatus[] { + return mergeStatus( + ((result && result.events) || []) + // filter events handled globally, or those we are not interested in, these are + // handled by the global overview, so don't add them here + .filter((record): boolean => !!record.event && record.event.section !== 'democracy') + .map(({ event: { data, method, section } }): ActionStatus => { + if (section === 'system' && method === 'ExtrinsicFailed') { + const [dispatchError] = data as unknown as ITuple<[DispatchError]>; + let message = dispatchError.type; - const queueAction = (status: ActionStatus | ActionStatus[]): void => { - const _status = Array.isArray(status) ? status : [status]; + if (dispatchError.isModule) { + try { + const mod = dispatchError.asModule; + const error = registry.findMetaError(new Uint8Array([mod.index.toNumber(), mod.error.toNumber()])); - if (_status.length) { - setStQueue([...stRef.current, ...(_status.map((item): QueueStatus => { - const id = ++nextId; - const removeItem = (): void => - setStQueue([...stRef.current.filter((item): boolean => item.id !== id)]); - - setTimeout(removeItem, REMOVE_TIMEOUT); - - return { - ...item, - id, - isCompleted: false, - removeItem - }; - }))]); - } - }; - const queueExtrinsic = ({ accountId, extrinsic, txFailedCb, txSuccessCb, txStartCb, txUpdateCb, isUnsigned }: PartialQueueTxExtrinsic): void => - addToTxQueue({ - accountId, - extrinsic, - isUnsigned, - txFailedCb, - txSuccessCb, - txStartCb, - txUpdateCb - }); - const queuePayload = (payload: SignerPayloadJSON, signerCb: SignerCallback): void => - addToTxQueue({ - accountId: payload.address, - // this is not great, but the Extrinsic we don't need a submittable - extrinsic: createType(registry, 'Extrinsic', - { method: createType(registry, 'Call', payload.method) }, - { version: payload.version } - ) as unknown as SubmittableExtrinsic, - payload, - signerCb - }); - const queueRpc = ({ accountId, rpc, values }: PartialQueueTxRpc): void => - addToTxQueue({ - accountId, - rpc, - values - }); - const queueSetTxStatus = (id: number, status: QueueTxStatus, result?: SubmittableResult, error?: Error): void => { - setTxQueue([...txRef.current.map((item): QueueTx => - item.id === id - ? { - ...item, - error: error === undefined - ? item.error - : error, - result: result === undefined - ? item.result - : result, - status: item.status === 'completed' - ? item.status - : status - } - : item - )]); - - queueAction( - ((result && result.events) || []) - // filter events handled globally, or those we are not interested in, these are - // handled by the global overview, so don't add them here - .filter((record): boolean => !!record.event && record.event.section !== 'democracy') - .map(({ event: { data, method, section } }): ActionStatus => { - if (section === 'system' && method === 'ExtrinsicFailed') { - const [dispatchError] = data as unknown as ITuple<[DispatchError]>; - let message = dispatchError.type; - - if (dispatchError.isModule) { - try { - const mod = dispatchError.asModule; - const error = registry.findMetaError(new Uint8Array([mod.index.toNumber(), mod.error.toNumber()])); - - message = `${error.section}.${error.name}`; - } catch (error) { - // swallow - } + message = `${error.section}.${error.name}`; + } catch (error) { + // swallow } - - return { - action: `${section}.${method}`, - status: 'error', - message - }; } return { action: `${section}.${method}`, - status: 'event', - message: 'extrinsic event' + status: 'error', + message }; - }) - ); + } - if (STATUS_COMPLETE.includes(status)) { - setTimeout((): void => { - const item = txRef.current.find((item): boolean => item.id === id); + return { + action: `${section}.${method}`, + status: 'event', + message: 'extrinsic event' + }; + }) + ); +} - item && item.removeItem(); - }, REMOVE_TIMEOUT); - } - }; +function Queue ({ children }: Props): React.ReactElement { + const [stqueue, _setStQueue] = useState([]); + const [txqueue, _setTxQueue] = useState([]); + const stRef = useRef(stqueue); + const txRef = useRef(txqueue); + + const setStQueue = useCallback( + (st: QueueStatus[]): void => { + stRef.current = st; + _setStQueue(st); + }, + [] + ); + const setTxQueue = useCallback( + (tx: QueueTx[]): void => { + txRef.current = tx; + _setTxQueue(tx); + }, + [] + ); + const addToTxQueue = useCallback( + (value: QueueTxExtrinsic | QueueTxRpc | QueueTx): void => { + const id = ++nextId; + const removeItem = (): void => + setTxQueue([...txRef.current.map((item): QueueTx => + item.id === id + ? { ...item, status: 'completed' } + : item + )]); + + setTxQueue([...txRef.current, { + ...value, + id, + removeItem, + rpc: (value as QueueTxRpc).rpc || SUBMIT_RPC, + status: 'queued' + }]); + }, + [setTxQueue] + ); + const queueAction = useCallback( + (_status: ActionStatus | ActionStatus[]): void => { + const status = Array.isArray(_status) ? _status : [_status]; + + status.length && setStQueue([ + ...stRef.current, + ...(status.map((item): QueueStatus => { + const id = ++nextId; + const removeItem = (): void => + setStQueue([...stRef.current.filter((item): boolean => item.id !== id)]); + + setTimeout(removeItem, REMOVE_TIMEOUT); + + return { + ...item, + id, + isCompleted: false, + removeItem + }; + })) + ]); + }, + [setStQueue] + ); + const queueExtrinsic = useCallback( + (value: PartialQueueTxExtrinsic): void => + addToTxQueue({ ...value }), + [addToTxQueue] + ); + const queuePayload = useCallback( + (payload: SignerPayloadJSON, signerCb: SignerCallback): void => + addToTxQueue({ + accountId: payload.address, + // this is not great, but the Extrinsic we don't need a submittable + extrinsic: createType(registry, 'Extrinsic', + { method: createType(registry, 'Call', payload.method) }, + { version: payload.version } + ) as unknown as SubmittableExtrinsic, + payload, + signerCb + }), + [addToTxQueue] + ); + const queueRpc = useCallback( + (value: PartialQueueTxRpc): void => + addToTxQueue({ ...value }), + [addToTxQueue] + ); + const queueSetTxStatus = useCallback( + (id: number, status: QueueTxStatus, result?: SubmittableResult, error?: Error): void => { + setTxQueue([...txRef.current.map((item): QueueTx => + item.id === id + ? { + ...item, + error: error === undefined + ? item.error + : error, + result: result === undefined + ? item.result + : result, + status: item.status === 'completed' + ? item.status + : status + } + : item + )]); + + queueAction( + extractEvents(result) + ); + + if (STATUS_COMPLETE.includes(status)) { + setTimeout((): void => { + const item = txRef.current.find((item): boolean => item.id === id); + + item && item.removeItem(); + }, REMOVE_TIMEOUT); + } + }, + [queueAction, setTxQueue] + ); return ( { queuePayload, queueRpc, queueSetTxStatus, - stqueue: _stqueue, - txqueue: _txqueue + stqueue, + txqueue }}> {children} diff --git a/packages/react-components/src/Table.tsx b/packages/react-components/src/Table.tsx index f43c9ed501..25dd61ff63 100644 --- a/packages/react-components/src/Table.tsx +++ b/packages/react-components/src/Table.tsx @@ -61,7 +61,7 @@ const Memo = React.memo(styled(Table)` width: 100%; thead tr { - background: #fafafa; + background: #f5f5f5; } tbody tr { @@ -130,14 +130,11 @@ const Memo = React.memo(styled(Table)` } &.address { + min-width: 11rem; padding: 0.85rem 1rem; > .ui--AddressMini, > .ui--AddressMini.padded { - padding: 0; - margin-bottom: -0.85rem; - margin-top: -0.25rem; - label.ui--AddressMini-label { margin-bottom: -0.3rem; } @@ -169,9 +166,9 @@ const Memo = React.memo(styled(Table)` } th { - color: #999; + color: #888; font-family: sans-serif; - font-weight: 500; + font-weight: 400; text-align: right; white-space: nowrap; diff --git a/packages/react-hooks/src/useAccounts.ts b/packages/react-hooks/src/useAccounts.ts index 734e7ce1b3..0748e57639 100644 --- a/packages/react-hooks/src/useAccounts.ts +++ b/packages/react-hooks/src/useAccounts.ts @@ -29,8 +29,7 @@ export default function useAccounts (): UseAccounts { return (): void => { setTimeout(() => subscription.unsubscribe(), 0); }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [mountedRef]); return state; } diff --git a/packages/react-hooks/src/useAddresses.ts b/packages/react-hooks/src/useAddresses.ts index af9f95f440..156c6bd60d 100644 --- a/packages/react-hooks/src/useAddresses.ts +++ b/packages/react-hooks/src/useAddresses.ts @@ -29,8 +29,7 @@ export default function useAccounts (): UseAccounts { return (): void => { setTimeout(() => subscription.unsubscribe(), 0); }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [mountedRef]); return state; } diff --git a/packages/react-hooks/src/useMembers.ts b/packages/react-hooks/src/useMembers.ts index e4ce529e4c..67cf573cd3 100644 --- a/packages/react-hooks/src/useMembers.ts +++ b/packages/react-hooks/src/useMembers.ts @@ -14,17 +14,17 @@ interface Result { export default function useMembers (collective: 'council' | 'technicalCommittee'): Result { const { api } = useApi(); - const { allAccounts } = useAccounts(); + const { allAccounts, hasAccounts } = useAccounts(); const [state, setState] = useState({ isMember: false, members: [] }); const retrieved = ( collective === 'council' // eslint-disable-next-line react-hooks/rules-of-hooks - ? useCall(api.query.electionsPhragmen?.members || api.query.elections.members, [], { + ? useCall(hasAccounts && (api.query.electionsPhragmen?.members || api.query.elections.members), [], { transform: (accounts: [AccountId, Balance][]): string[] => accounts.map(([accountId]) => accountId.toString()) }) // eslint-disable-next-line react-hooks/rules-of-hooks - : useCall(api.query.technicalCommittee.members, [], { + : useCall(hasAccounts && api.query.technicalCommittee.members, [], { transform: (accounts: AccountId[]): string[] => accounts.map((accountId) => accountId.toString()) }) diff --git a/packages/react-hooks/src/useOwnStashes.ts b/packages/react-hooks/src/useOwnStashes.ts index 155e6ba967..dd12b07e53 100644 --- a/packages/react-hooks/src/useOwnStashes.ts +++ b/packages/react-hooks/src/useOwnStashes.ts @@ -33,15 +33,15 @@ function getStashes (allAccounts: string[], ownBonded: Option[], ownL } export default function useOwnStashes (): [string, IsInKeyring][] | undefined { - const { allAccounts } = useAccounts(); + const { allAccounts, hasAccounts } = useAccounts(); const mountedRef = useIsMountedRef(); const { api } = useApi(); - const ownBonded = useCall[]>(api.query.staking?.bonded.multi as any, [allAccounts]); - const ownLedger = useCall[]>(api.query.staking?.ledger.multi as any, [allAccounts]); + const ownBonded = useCall[]>(hasAccounts && api.query.staking?.bonded.multi as any, [allAccounts]); + const ownLedger = useCall[]>(hasAccounts && api.query.staking?.ledger.multi as any, [allAccounts]); const [state, setState] = useState<[string, IsInKeyring][] | undefined>(); useEffect((): void => { - mountedRef.current && allAccounts && ownBonded && ownLedger && setState( + mountedRef.current && ownBonded && ownLedger && setState( getStashes(allAccounts, ownBonded, ownLedger) ); }, [allAccounts, mountedRef, ownBonded, ownLedger]); diff --git a/packages/react-hooks/src/useRegistrars.ts b/packages/react-hooks/src/useRegistrars.ts index fc3579455c..650ad72301 100644 --- a/packages/react-hooks/src/useRegistrars.ts +++ b/packages/react-hooks/src/useRegistrars.ts @@ -18,8 +18,8 @@ interface State { export default function useRegistrars (): State { const { api } = useApi(); - const { allAccounts } = useAccounts(); - const query = useCall[]>(api.query.identity?.registrars, []); + const { allAccounts, hasAccounts } = useAccounts(); + const query = useCall[]>(hasAccounts && api.query.identity?.registrars, []); const [state, setState] = useState({ isRegistrar: false, registrars: [] }); // determine if we have a registrar or not - registrars are allowed to approve diff --git a/packages/react-hooks/src/useSudo.ts b/packages/react-hooks/src/useSudo.ts index f7bcf1de00..788cc276ef 100644 --- a/packages/react-hooks/src/useSudo.ts +++ b/packages/react-hooks/src/useSudo.ts @@ -12,8 +12,8 @@ import useCall from './useCall'; export default function useSudo (): UseSudo { const { api } = useApi(); - const { allAccounts } = useAccounts(); - const sudoKey = useCall(api.query.sudo?.key, [], { + const { allAccounts, hasAccounts } = useAccounts(); + const sudoKey = useCall(hasAccounts && api.query.sudo?.key, [], { transform: (k): string => k.toString() }); diff --git a/packages/react-signer/src/Modal.tsx b/packages/react-signer/src/Modal.tsx index 469a026d10..d4a19eb32f 100644 --- a/packages/react-signer/src/Modal.tsx +++ b/packages/react-signer/src/Modal.tsx @@ -192,7 +192,11 @@ class Signer extends React.PureComponent { const { isExternal, isHardware, hardwareType } = extractExternal(currentItem.accountId); return ( - + {!isRenderError && (!isQrVisible || !isQrScanning) && !signedTx && ( <> {!currentItem.isUnsigned && this.renderSignToggle()} @@ -239,15 +243,22 @@ class Signer extends React.PureComponent { tip={tip} value={currentItem} > - {isQrVisible ? ( - - ) : ( - <> - {this.renderTip()} - {this.renderUnlock()} - {!isSubmit && this.renderSignFields()} - - )} + {isQrVisible + ? ( + + ) + : ( + <> + {this.renderTip()} + {this.renderUnlock()} + {!isSubmit && this.renderSignFields()} + + )} ); }
 {t('bond')}{t('beneficiary')}{t('value')} {t('beneficiary')}{t('payment')}{t('bond')}