Display individual ranked votes (#8480)

* Display individual ranked votes

* Adjust
This commit is contained in:
Jaco 2022-12-05 14:13:12 +02:00 committed by GitHub
parent 30cae80ce5
commit 7cd2b81583
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 129 additions and 29 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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