From d048f06e606c9b9cb88ec75807830f564d37e6df Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 29 Jul 2020 19:21:21 +0200 Subject: [PATCH] Display schedule on democracy (#3287) --- .../page-democracy/src/Overview/Scheduled.tsx | 49 ++++++++++ .../page-democracy/src/Overview/Scheduler.tsx | 89 +++++++++++++++++++ .../page-democracy/src/Overview/index.tsx | 4 + packages/page-democracy/src/Overview/types.ts | 19 ++++ packages/react-components/src/Progress.tsx | 21 ++++- 5 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 packages/page-democracy/src/Overview/Scheduled.tsx create mode 100644 packages/page-democracy/src/Overview/Scheduler.tsx create mode 100644 packages/page-democracy/src/Overview/types.ts diff --git a/packages/page-democracy/src/Overview/Scheduled.tsx b/packages/page-democracy/src/Overview/Scheduled.tsx new file mode 100644 index 0000000000..269d847584 --- /dev/null +++ b/packages/page-democracy/src/Overview/Scheduled.tsx @@ -0,0 +1,49 @@ +// Copyright 2017-2020 @polkadot/app-democracy 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 { BlockNumber } from '@polkadot/types/interfaces'; +import { ScheduledExt } from './types'; + +import React from 'react'; +import { CallExpander } from '@polkadot/react-components'; +import { useApi, useCall } from '@polkadot/react-hooks'; +import { BlockToTime } from '@polkadot/react-query'; +import { formatNumber } from '@polkadot/util'; + +interface Props { + className?: string; + value: ScheduledExt; +} + +function Scheduled ({ className = '', value: { call, count, maybePeriodic, nextBlockNumber } }: Props): React.ReactElement { + const { api } = useApi(); + const bestNumber = useCall(api.derive.chain.bestNumber, []); + const period = maybePeriodic.unwrapOr(null); + + return ( + + + + {bestNumber && ( + <> + + #{formatNumber(nextBlockNumber)} + + )} + + + {period && ( + formatNumber(period[0]) + )} + + + {period && ( + formatNumber(count) + ' / ' + formatNumber(period[1]) + )} + + + ); +} + +export default React.memo(Scheduled); diff --git a/packages/page-democracy/src/Overview/Scheduler.tsx b/packages/page-democracy/src/Overview/Scheduler.tsx new file mode 100644 index 0000000000..2616a7e2c6 --- /dev/null +++ b/packages/page-democracy/src/Overview/Scheduler.tsx @@ -0,0 +1,89 @@ +// Copyright 2017-2020 @polkadot/app-democracy 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 { BlockNumber, Scheduled } from '@polkadot/types/interfaces'; +import { ScheduledExt } from './types'; + +import React, { useMemo } from 'react'; +import { Table } from '@polkadot/react-components'; +import { useApi, useCall } from '@polkadot/react-hooks'; +import { Option } from '@polkadot/types'; + +import { useTranslation } from '../translate'; +import ScheduledView from './Scheduled'; + +interface Props { + className?: string; +} + +function Schedule ({ className = '' }: Props): React.ReactElement { + const { t } = useTranslation(); + const { api } = useApi(); + const bestNumber = useCall(api.derive.chain.bestNumber, []); + const items = useCall(api.query.scheduler.agenda.entries as any, [], { + transform: (entries: [{ args: [BlockNumber] }, Option[]][]): ScheduledExt[] => + entries + .filter(([, vecSchedOpt]) => vecSchedOpt.some((schedOpt) => schedOpt.isSome)) + .reduce((items: ScheduledExt[], [key, vecSchedOpt]): ScheduledExt[] => { + const blockNumber = key.args[0]; + + return vecSchedOpt + .filter((schedOpt) => schedOpt.isSome) + .map((schedOpt) => schedOpt.unwrap()) + .reduce((items: ScheduledExt[], { call, maybeId, maybePeriodic, priority }, index) => { + items.push({ blockNumber, call, count: 0, key: `${blockNumber.toString()}-${index}`, maybeId, maybePeriodic, nextBlockNumber: blockNumber, priority }); + + return items; + }, items); + }, []) + }); + + const header = useMemo(() => [ + [t('scheduled'), 'start'], + [t('remaining')], + [t('period')], + [t('count')] + ], [t]); + + let scheduled: ScheduledExt[] | undefined; + + if (bestNumber && items) { + scheduled = items + .map((schedule): ScheduledExt => { + if (schedule.maybePeriodic.isSome) { + const [inc, max] = schedule.maybePeriodic.unwrap(); + const nextBlockNumber = schedule.blockNumber.toBn(); + let count = 0; + + while (max.gtn(count) && nextBlockNumber.lt(bestNumber)) { + count++; + nextBlockNumber.iadd(inc); + } + + schedule.count = count; + schedule.nextBlockNumber = nextBlockNumber; + } + + return schedule; + }) + .filter(({ nextBlockNumber }) => nextBlockNumber.gt(bestNumber)); + } + + return ( + ('No active schedules')} + header={header} + > + {scheduled?.map((scheduled): React.ReactNode => ( + + ))} +
+ ); +} + +export default React.memo(Schedule); diff --git a/packages/page-democracy/src/Overview/index.tsx b/packages/page-democracy/src/Overview/index.tsx index 75832093bf..166f859eaa 100644 --- a/packages/page-democracy/src/Overview/index.tsx +++ b/packages/page-democracy/src/Overview/index.tsx @@ -14,6 +14,7 @@ import DispatchQueue from './DispatchQueue'; import Externals from './Externals'; import Proposals from './Proposals'; import Referendums from './Referendums'; +import Scheduler from './Scheduler'; import Summary from './Summary'; import PreImage from './PreImage'; import Propose from './Propose'; @@ -54,6 +55,9 @@ function Overview ({ className }: Props): React.ReactElement { + {api.query.scheduler && ( + + )} ); } diff --git a/packages/page-democracy/src/Overview/types.ts b/packages/page-democracy/src/Overview/types.ts new file mode 100644 index 0000000000..23ad6481bd --- /dev/null +++ b/packages/page-democracy/src/Overview/types.ts @@ -0,0 +1,19 @@ +// Copyright 2017-2020 @polkadot/app-democracy 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 { BlockNumber, Call, SchedulePeriod, SchedulePriority } from '@polkadot/types/interfaces'; + +import BN from 'bn.js'; +import { Bytes, Option } from '@polkadot/types'; + +export interface ScheduledExt { + blockNumber: BlockNumber; + call: Call; + count: number; + key: string; + maybeId: Option; + maybePeriodic: Option; + priority: SchedulePriority; + nextBlockNumber: BN; +} diff --git a/packages/react-components/src/Progress.tsx b/packages/react-components/src/Progress.tsx index 346927cb22..c9bb23b128 100644 --- a/packages/react-components/src/Progress.tsx +++ b/packages/react-components/src/Progress.tsx @@ -11,6 +11,7 @@ import { bnToBn } from '@polkadot/util'; interface Props { className?: string; isDisabled?: boolean; + size?: 'normal' | 'small' total?: UInt | BN | number | null; value?: UInt | BN | number | null; } @@ -33,7 +34,7 @@ function DivClip ({ angle, type }: RotateProps): React.ReactElement const Clip = React.memo(DivClip); -function Progress ({ className = '', isDisabled, total, value }: Props): React.ReactElement | null { +function Progress ({ className = '', isDisabled, size = 'normal', total, value }: Props): React.ReactElement | null { const _total = bnToBn(total || 0); const angle = _total.gtn(0) ? (bnToBn(value || 0).muln(36000).div(_total).toNumber() / 100) @@ -44,7 +45,7 @@ function Progress ({ className = '', isDisabled, total, value }: Props): React.R } return ( -
+