Display individual ranked votes (#8480)
* Display individual ranked votes * Adjust
This commit is contained in:
parent
30cae80ce5
commit
7cd2b81583
@ -29,7 +29,7 @@ function filter (records: EventRecord[]): Changes<u32> {
|
||||
|
||||
function useAssetIdsImpl (): u32[] | undefined {
|
||||
const { api } = useApi();
|
||||
const startValue = useMapKeys(api.query.assets.asset, OPT_KEY);
|
||||
const startValue = useMapKeys(api.query.assets.asset, [], OPT_KEY);
|
||||
|
||||
return useEventChanges([
|
||||
api.events.assets.Created,
|
||||
|
||||
@ -31,7 +31,7 @@ function filter (records: EventRecord[]): Changes<u32> {
|
||||
|
||||
function useCollectionIdsImpl (): u32[] | undefined {
|
||||
const { api } = useApi();
|
||||
const startValue = useMapKeys(api.query.uniques.class, OPT_KEYS);
|
||||
const startValue = useMapKeys(api.query.uniques.class, [], OPT_KEYS);
|
||||
|
||||
return useEventChanges([
|
||||
api.events.uniques.Created,
|
||||
|
||||
@ -157,7 +157,7 @@ function useFundsImpl (): Campaigns {
|
||||
const bestNumber = useBestNumber();
|
||||
const mountedRef = useIsMountedRef();
|
||||
const trigger = useEventTrigger([api.events.crowdloan?.Created]);
|
||||
const paraIds = useMapKeys(api.query.crowdloan?.funds, OPT_FUNDS, trigger.blockHash);
|
||||
const paraIds = useMapKeys(api.query.crowdloan?.funds, [], OPT_FUNDS, trigger.blockHash);
|
||||
const campaigns = useCall<Campaign[]>(api.query.crowdloan?.funds.multi, [paraIds], OPT_FUNDS_MULTI);
|
||||
const leases = useCall<ParaId[]>(api.query.slots.leases.multi, [paraIds], OPT_LEASE);
|
||||
const [result, setResult] = useState<Campaigns>(EMPTY);
|
||||
|
||||
@ -61,7 +61,7 @@ function useOwnedIdsImpl (): OwnedId[] {
|
||||
api.events.registrar.Registered,
|
||||
api.events.registrar.Reserved
|
||||
]);
|
||||
const unfiltered = useMapEntries<Owned>(api.query.registrar.paras, OPT_ENTRIES, trigger.blockHash);
|
||||
const unfiltered = useMapEntries<Owned>(api.query.registrar.paras, [], OPT_ENTRIES, trigger.blockHash);
|
||||
const hashes = useCall(api.query.paras.currentCodeHash.multi, [unfiltered ? unfiltered.ids : []], OPT_HASHES);
|
||||
|
||||
return useMemo(
|
||||
|
||||
@ -37,8 +37,8 @@ function useProposalsImpl (): Proposals | undefined {
|
||||
const { api } = useApi();
|
||||
const mountedRef = useIsMountedRef();
|
||||
const trigger = useEventTrigger([api.events.proposeParachain?.ProposeParachain]);
|
||||
const proposalIds = useMapKeys(api.query.proposeParachain?.proposals, OPT_IDS, trigger.blockHash);
|
||||
const scheduled = useMapEntries(api.query.proposeParachain?.scheduledProposals, OPT_SCHED, trigger.blockHash);
|
||||
const proposalIds = useMapKeys(api.query.proposeParachain?.proposals, [], OPT_IDS, trigger.blockHash);
|
||||
const scheduled = useMapEntries(api.query.proposeParachain?.scheduledProposals, [], OPT_SCHED, trigger.blockHash);
|
||||
const [sessionIndex, approvedIds] = useCallMulti<MultiQuery>([
|
||||
api.query.session.currentIndex,
|
||||
api.query.proposeParachain?.approvedProposals
|
||||
|
||||
@ -33,7 +33,7 @@ function useUpomingIdsImpl (): ParaId[] | undefined {
|
||||
api.events.registrar.Registered
|
||||
]);
|
||||
|
||||
return useMapEntries(api.query.paras.paraLifecycles, OPT_ENTRIES, trigger.blockHash);
|
||||
return useMapEntries(api.query.paras.paraLifecycles, [], OPT_ENTRIES, trigger.blockHash);
|
||||
}
|
||||
|
||||
export default createNamedHook('useUpomingIds', useUpomingIdsImpl);
|
||||
|
||||
@ -32,7 +32,7 @@ function filter (records: EventRecord[]): Changes<Hash> {
|
||||
|
||||
function usePreimagesImpl (): HexString[] | undefined {
|
||||
const { api } = useApi();
|
||||
const startValue = useMapKeys(api.query.preimage.statusFor, OPT_HASH);
|
||||
const startValue = useMapKeys(api.query.preimage.statusFor, [], OPT_HASH);
|
||||
const hashes = useEventChanges([
|
||||
api.events.preimage.Cleared,
|
||||
api.events.preimage.Noted
|
||||
|
||||
@ -30,7 +30,7 @@ function filter (records: EventRecord[]): Changes<AccountId32> {
|
||||
|
||||
function useMemberIdsImpl (collective: PalletColl): AccountId32[] | undefined {
|
||||
const { api } = useApi();
|
||||
const startValue = useMapKeys(api.query[collective].members, OPT_ID);
|
||||
const startValue = useMapKeys(api.query[collective].members, [], OPT_ID);
|
||||
|
||||
return useEventChanges([
|
||||
api.events[collective].MemberAdded,
|
||||
|
||||
@ -1,16 +1,18 @@
|
||||
// Copyright 2017-2022 @polkadot/app-referenda authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { u32 } from '@polkadot/types';
|
||||
import type { PalletConvictionVotingTally, PalletRankedCollectiveTally } from '@polkadot/types/lookup';
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { PalletVote } from '../types';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
|
||||
import { Expander } from '@polkadot/react-components';
|
||||
import { AddressMini, Expander } from '@polkadot/react-components';
|
||||
import { FormatBalance } from '@polkadot/react-query';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
import useVotes from './useVotes';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
@ -20,12 +22,49 @@ interface Props {
|
||||
tally: PalletConvictionVotingTally | PalletRankedCollectiveTally;
|
||||
}
|
||||
|
||||
function Votes ({ className = '', isConvictionVote, tally }: Props): React.ReactElement<Props> {
|
||||
function Votes ({ className = '', id, isConvictionVote, palletVote, tally }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const votes = useVotes(palletVote, id, isConvictionVote);
|
||||
|
||||
const [ayes, nays] = useMemo(
|
||||
() => votes
|
||||
? Object.entries(votes).reduce<[[string, u32][], [string, u32][]]>(([ayes, nays], [accountId, vote]) => {
|
||||
if (vote.isAye) {
|
||||
ayes.push([accountId, vote.asAye]);
|
||||
} else {
|
||||
nays.push([accountId, vote.asNay]);
|
||||
}
|
||||
|
||||
return [ayes, nays];
|
||||
}, [[], []])
|
||||
: [],
|
||||
[votes]
|
||||
);
|
||||
|
||||
const renderAyes = useCallback(
|
||||
() => ayes?.map(([a]) => (
|
||||
<AddressMini
|
||||
key={a}
|
||||
value={a}
|
||||
/>
|
||||
)),
|
||||
[ayes]
|
||||
);
|
||||
|
||||
const renderNays = useCallback(
|
||||
() => nays?.map(([a]) => (
|
||||
<AddressMini
|
||||
key={a}
|
||||
value={a}
|
||||
/>
|
||||
)),
|
||||
[nays]
|
||||
);
|
||||
|
||||
return (
|
||||
<td className={`${className} expand`}>
|
||||
<Expander
|
||||
renderChildren={ayes && ayes.length !== 0 && renderAyes}
|
||||
summary={
|
||||
isConvictionVote
|
||||
? (
|
||||
@ -38,6 +77,7 @@ function Votes ({ className = '', isConvictionVote, tally }: Props): React.React
|
||||
}
|
||||
/>
|
||||
<Expander
|
||||
renderChildren={nays && nays.length !== 0 && renderNays}
|
||||
summary={
|
||||
isConvictionVote
|
||||
? (
|
||||
|
||||
60
packages/page-referenda/src/Referenda/useVotes.ts
Normal file
60
packages/page-referenda/src/Referenda/useVotes.ts
Normal file
@ -0,0 +1,60 @@
|
||||
// Copyright 2017-2022 @polkadot/app-preimages authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ApiPromise } from '@polkadot/api';
|
||||
import type { Changes } from '@polkadot/react-hooks/useEventChanges';
|
||||
import type { Option, StorageKey, u32 } from '@polkadot/types';
|
||||
import type { AccountId, EventRecord } from '@polkadot/types/interfaces';
|
||||
import type { PalletRankedCollectiveVoteRecord } from '@polkadot/types/lookup';
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { PalletVote } from '../types';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { createNamedHook, useApi, useCall, useEventChanges, useMapKeys } from '@polkadot/react-hooks';
|
||||
|
||||
const OPT_ACCOUNTID = {
|
||||
transform: (keys: StorageKey<[u32, AccountId]>[]): AccountId[] =>
|
||||
keys.map(({ args: [, accountId] }) => accountId)
|
||||
};
|
||||
|
||||
const OPT_VOTES = {
|
||||
transform: ([[params], votes]: [[[[BN, AccountId][]]], Option<PalletRankedCollectiveVoteRecord>[]]): Record<string, PalletRankedCollectiveVoteRecord> =>
|
||||
params.reduce<Record<string, PalletRankedCollectiveVoteRecord>>((all, [, a], i) => {
|
||||
if (votes[i] && votes[i].isSome) {
|
||||
all[a.toString()] = votes[i].unwrap();
|
||||
}
|
||||
|
||||
return all;
|
||||
}, {}),
|
||||
withParamsTransform: true
|
||||
};
|
||||
|
||||
function filterEvents (records: EventRecord[], _: ApiPromise, id?: BN): Changes<AccountId> {
|
||||
const added: AccountId[] = [];
|
||||
|
||||
records.forEach(({ event: { data: [who, pollId] } }): void => {
|
||||
if (id && pollId.eq(id)) {
|
||||
added.push(who as AccountId);
|
||||
}
|
||||
});
|
||||
|
||||
return { added };
|
||||
}
|
||||
|
||||
function useVotesImpl (palletVote: PalletVote, id: BN, isConvictionVote: boolean): Record<string, PalletRankedCollectiveVoteRecord> | undefined {
|
||||
const { api } = useApi();
|
||||
const startAccounts = useMapKeys(isConvictionVote === false && api.query[palletVote].voting, [id], OPT_ACCOUNTID);
|
||||
const allAccounts = useEventChanges([
|
||||
api.events[palletVote].Voted
|
||||
], filterEvents, startAccounts, id);
|
||||
|
||||
const params = useMemo(
|
||||
() => allAccounts?.map((a) => [id, a]),
|
||||
[allAccounts, id]
|
||||
);
|
||||
|
||||
return useCall(params && api.query[palletVote].voting.multi, [params], OPT_VOTES);
|
||||
}
|
||||
|
||||
export default createNamedHook('useVotes', useVotesImpl);
|
||||
@ -31,7 +31,7 @@ function filter (records: EventRecord[]): Changes<u32> {
|
||||
|
||||
function useReferendaIdsImpl (palletReferenda: PalletReferenda): BN[] | undefined {
|
||||
const { api } = useApi();
|
||||
const startValue = useMapKeys(api.query[palletReferenda].referendumInfoFor, OPT_ID);
|
||||
const startValue = useMapKeys(api.query[palletReferenda].referendumInfoFor, [], OPT_ID);
|
||||
|
||||
return useEventChanges([
|
||||
api.events[palletReferenda].Submitted
|
||||
|
||||
@ -55,7 +55,7 @@ function merge (prev: BagInfo[] | undefined, curr: BagInfo[]): BagInfo[] {
|
||||
function useBagsListImpl (): BagInfo[] | undefined {
|
||||
const mod = useQueryModule();
|
||||
const [result, setResult] = useState<BagInfo[] | undefined>();
|
||||
const ids = useMapKeys(mod.listBags, KEY_OPTS);
|
||||
const ids = useMapKeys(mod.listBags, [], KEY_OPTS);
|
||||
const query = useCall(ids && ids.length !== 0 && mod.listBags.multi, [ids], MULTI_OPTS);
|
||||
|
||||
useEffect((): void => {
|
||||
|
||||
@ -86,7 +86,7 @@ function interleave (prev: MembersMap, additions: MembersMapEntry[]): MembersMap
|
||||
function useMembersImpl (): MembersMap | undefined {
|
||||
const { api } = useApi();
|
||||
const [membersMap, setMembersMap] = useState<MembersMap | undefined>();
|
||||
const queryMap = useMapEntries(api.query.nominationPools.poolMembers, OPT_ENTRIES);
|
||||
const queryMap = useMapEntries(api.query.nominationPools.poolMembers, [], OPT_ENTRIES);
|
||||
const ids = useEventChanges([
|
||||
api.events.nominationPools.Bonded
|
||||
], filterEvents, EMPTY_START);
|
||||
|
||||
@ -31,7 +31,7 @@ function filterEvents (records: EventRecord[]): Changes<u32> {
|
||||
|
||||
function usePoolIdsImpl (): u32[] | undefined {
|
||||
const { api } = useApi();
|
||||
const startValue = useMapKeys(api.query.nominationPools.bondedPools, OPT_KEYS);
|
||||
const startValue = useMapKeys(api.query.nominationPools.bondedPools, [], OPT_KEYS);
|
||||
|
||||
return useEventChanges([
|
||||
api.events.nominationPools.Created,
|
||||
|
||||
@ -19,7 +19,7 @@ function useTipHashesImpl (): string[] | undefined {
|
||||
api.events.tips?.TipRetracted
|
||||
]);
|
||||
|
||||
return useMapKeys((api.query.tips || api.query.treasury)?.tips, OPT, trigger.blockHash);
|
||||
return useMapKeys((api.query.tips || api.query.treasury)?.tips, [], OPT, trigger.blockHash);
|
||||
}
|
||||
|
||||
export default createNamedHook('useTipHashes', useTipHashesImpl);
|
||||
|
||||
@ -32,7 +32,7 @@ function filter (records: EventRecord[]): Changes<Hash> {
|
||||
|
||||
function useHashesImpl (): HexString[] | undefined {
|
||||
const { api } = useApi();
|
||||
const startValue = useMapKeys(api.query.whitelist.whitelistedCall, OPT_HASH);
|
||||
const startValue = useMapKeys(api.query.whitelist.whitelistedCall, [], OPT_HASH);
|
||||
const hashes = useEventChanges([
|
||||
api.events.whitelist.CallWhitelisted,
|
||||
api.events.whitelist.WhitelistedCallRemoved
|
||||
|
||||
@ -47,7 +47,7 @@ function interleave <T extends Codec> (existing: T[] = [], { added = [], removed
|
||||
.map(([, v]) => v);
|
||||
}
|
||||
|
||||
export function useEventChanges <T extends Codec> (checks: EventCheck[], filter: (records: EventRecord[], api: ApiPromise) => Changes<T>, startValue?: T[]): T[] | undefined {
|
||||
export function useEventChanges <T extends Codec, A> (checks: EventCheck[], filter: (records: EventRecord[], api: ApiPromise, additional?: A) => Changes<T>, startValue?: T[], additional?: A): T[] | undefined {
|
||||
const { api } = useApi();
|
||||
const [state, setState] = useState<T[] | undefined>();
|
||||
const { blockHash, events } = useEventTrigger(checks);
|
||||
@ -59,8 +59,8 @@ export function useEventChanges <T extends Codec> (checks: EventCheck[], filter:
|
||||
|
||||
// add/remove any additional items detected (only when actual events occur)
|
||||
useEffect((): void => {
|
||||
blockHash && setState((prev) => interleave(prev, filter(events, api)));
|
||||
}, [api, blockHash, events, filter]);
|
||||
blockHash && setState((prev) => interleave(prev, filter(events, api, additional)));
|
||||
}, [additional, api, blockHash, events, filter]);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
@ -10,20 +10,20 @@ interface Options <T> {
|
||||
}
|
||||
|
||||
// FIXME This is generic, we cannot really use createNamedHook
|
||||
export function useMapEntries <T = any> (entry?: QueryableStorageEntry<'promise'> | null | false, { transform }: Options<T> = {}, at?: string | null | false): T | undefined {
|
||||
export function useMapEntries <T = any> (entry: QueryableStorageEntry<'promise'> | null | false | undefined, params: unknown[], { transform }: Options<T> = {}, at?: string | null | false): T | undefined {
|
||||
const [state, setState] = useState<T | undefined>();
|
||||
|
||||
useEffect((): void => {
|
||||
entry && (
|
||||
at && at !== '0'
|
||||
? entry.entriesAt(at)
|
||||
: entry.entries()
|
||||
? entry.entriesAt(at, ...params)
|
||||
: entry.entries(...params)
|
||||
).then((entries) => setState(
|
||||
transform
|
||||
? transform(entries)
|
||||
: entries as unknown as T
|
||||
)).catch(console.error);
|
||||
}, [at, entry, transform]);
|
||||
}, [at, entry, params, transform]);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
@ -10,20 +10,20 @@ interface Options <T> {
|
||||
}
|
||||
|
||||
// FIXME This is generic, we cannot really use createNamedHook
|
||||
export function useMapKeys <T = any> (entry?: QueryableStorageEntry<'promise'> | null | false, { transform }: Options<T> = {}, at?: string | null | false): T[] | undefined {
|
||||
export function useMapKeys <T = any> (entry: QueryableStorageEntry<'promise'> | null | false | undefined, params: unknown[], { transform }: Options<T> = {}, at?: string | null | false): T[] | undefined {
|
||||
const [state, setState] = useState<T[] | undefined>();
|
||||
|
||||
useEffect((): void => {
|
||||
entry && (
|
||||
at && at !== '0'
|
||||
? entry.keysAt(at)
|
||||
: entry.keys()
|
||||
? entry.keysAt(at, ...params)
|
||||
: entry.keys(...params)
|
||||
).then((keys) => setState(
|
||||
transform
|
||||
? transform(keys)
|
||||
: keys as unknown as T[]
|
||||
)).catch(console.error);
|
||||
}, [at, entry, transform]);
|
||||
}, [at, entry, params, transform]);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user