Disable ranked voting (based on compares) (#8493)
This commit is contained in:
parent
fd2a1ceee4
commit
7ee315dad9
@ -1,30 +1,40 @@
|
||||
// Copyright 2017-2022 @polkadot/app-config authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { TrackInfo } from './types';
|
||||
|
||||
function compareFellowshipRank (trackId: number): (rank: BN) => boolean {
|
||||
return (rank: BN): boolean =>
|
||||
rank.gten(trackId);
|
||||
}
|
||||
|
||||
export const kusama: Record<string, TrackInfo[]> = {
|
||||
fellowshipReferenda: [
|
||||
{
|
||||
|
||||
compare: compareFellowshipRank(0),
|
||||
id: 0,
|
||||
name: 'candidates',
|
||||
origin: { Origins: 'FellowshipInitiates' },
|
||||
text: 'Origin commanded by any members of the Polkadot Fellowship (no Dan grade needed)'
|
||||
},
|
||||
{
|
||||
compare: compareFellowshipRank(1),
|
||||
id: 1,
|
||||
name: 'members',
|
||||
origin: { Origins: 'Fellowship1Dan' },
|
||||
text: 'Origin commanded by rank 1 of the Polkadot Fellowship and with a success of 1'
|
||||
},
|
||||
{
|
||||
compare: compareFellowshipRank(2),
|
||||
id: 2,
|
||||
name: 'proficients',
|
||||
origin: { Origins: 'Fellowship2Dan' },
|
||||
text: 'Origin commanded by rank 2 of the Polkadot Fellowship and with a success of 2'
|
||||
},
|
||||
{
|
||||
|
||||
compare: compareFellowshipRank(3),
|
||||
id: 3,
|
||||
name: 'fellows',
|
||||
origin: [
|
||||
@ -34,12 +44,14 @@ export const kusama: Record<string, TrackInfo[]> = {
|
||||
text: 'Origin commanded by Polkadot Fellows (3rd Dan fellows or greater)'
|
||||
},
|
||||
{
|
||||
compare: compareFellowshipRank(4),
|
||||
id: 4,
|
||||
name: 'senior fellows',
|
||||
origin: { Origins: 'Fellowship4Dan' },
|
||||
text: 'Origin commanded by rank 4 of the Polkadot Fellowship and with a success of 4'
|
||||
},
|
||||
{
|
||||
compare: compareFellowshipRank(5),
|
||||
id: 5,
|
||||
name: 'experts',
|
||||
origin: [
|
||||
@ -49,12 +61,14 @@ export const kusama: Record<string, TrackInfo[]> = {
|
||||
text: 'Origin commanded by Polkadot Experts (5th Dan fellows or greater)'
|
||||
},
|
||||
{
|
||||
compare: compareFellowshipRank(6),
|
||||
id: 6,
|
||||
name: 'senior experts',
|
||||
origin: { Origins: 'Fellowship6Dan' },
|
||||
text: 'Origin commanded by rank 6 of the Polkadot Fellowship and with a success of 6'
|
||||
},
|
||||
{
|
||||
compare: compareFellowshipRank(7),
|
||||
id: 7,
|
||||
name: 'masters',
|
||||
origin: [
|
||||
@ -64,12 +78,14 @@ export const kusama: Record<string, TrackInfo[]> = {
|
||||
text: 'Origin commanded by Polkadot Masters (7th Dan fellows of greater)'
|
||||
},
|
||||
{
|
||||
compare: compareFellowshipRank(8),
|
||||
id: 8,
|
||||
name: 'senior masters',
|
||||
origin: { Origins: 'Fellowship8Dan' },
|
||||
text: 'Origin commanded by rank 8 of the Polkadot Fellowship and with a success of 8'
|
||||
},
|
||||
{
|
||||
compare: compareFellowshipRank(9),
|
||||
id: 9,
|
||||
name: 'grand masters',
|
||||
origin: { Origins: 'Fellowship9Dan' },
|
||||
|
||||
@ -1,9 +1,13 @@
|
||||
// Copyright 2017-2022 @polkadot/app-config authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BN } from '@polkadot/util';
|
||||
|
||||
export type Origin = { system: string } | { Origins: string };
|
||||
|
||||
export interface TrackInfo {
|
||||
// TODO We would want to make this generic for non-fellowship
|
||||
compare?: (memberInfo: BN) => boolean;
|
||||
id: number;
|
||||
name: string;
|
||||
origin: Origin | Origin[];
|
||||
|
||||
@ -52,6 +52,7 @@ function App ({ basePath, className, palletColl, palletPoll }: Props): React.Rea
|
||||
members={members && members.memberIds}
|
||||
palletReferenda={palletPoll}
|
||||
palletVote={palletColl}
|
||||
ranks={members && members.memberRanks}
|
||||
/>
|
||||
</Route>
|
||||
<Route>
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
import type { Option } from '@polkadot/types';
|
||||
import type { AccountId32 } from '@polkadot/types/interfaces';
|
||||
import type { PalletRankedCollectiveMemberRecord } from '@polkadot/types/lookup';
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { Member, PalletColl } from './types';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
@ -14,6 +15,7 @@ import useMembersIds from './useMemberIds';
|
||||
|
||||
interface Result {
|
||||
memberIds: string[];
|
||||
memberRanks: BN[];
|
||||
members: Member[];
|
||||
}
|
||||
|
||||
@ -27,10 +29,10 @@ const OPT_MEM = {
|
||||
accountId: accountId.toString(),
|
||||
info
|
||||
}));
|
||||
const memberIds = members.map(({ accountId }) => accountId);
|
||||
|
||||
return {
|
||||
memberIds,
|
||||
memberIds: members.map(({ accountId }) => accountId),
|
||||
memberRanks: members.map(({ info }) => info.rank),
|
||||
members
|
||||
};
|
||||
},
|
||||
@ -44,7 +46,7 @@ function useMembersImpl (collective: PalletColl): Result | undefined {
|
||||
|
||||
return useMemo(
|
||||
() => ids && ids.length === 0
|
||||
? { memberIds: [], members: [] }
|
||||
? { memberIds: [], memberRanks: [], members: [] }
|
||||
: result,
|
||||
[ids, result]
|
||||
);
|
||||
|
||||
74
packages/page-referenda/src/Referenda/Group.tsx
Normal file
74
packages/page-referenda/src/Referenda/Group.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
// Copyright 2017-2022 @polkadot/app-referenda authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { PalletReferendaTrackInfo } from '@polkadot/types/lookup';
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { PalletReferenda, PalletVote, ReferendaGroup } from '../types';
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Table } from '@polkadot/react-components';
|
||||
import { useApi } from '@polkadot/react-hooks';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
import { getTrackInfo } from '../util';
|
||||
import Referendum from './Referendum';
|
||||
|
||||
interface Props extends ReferendaGroup {
|
||||
className?: string;
|
||||
isMember: boolean;
|
||||
members?: string[];
|
||||
palletReferenda: PalletReferenda;
|
||||
palletVote: PalletVote;
|
||||
ranks?: BN[];
|
||||
tracks?: [BN, PalletReferendaTrackInfo][] | undefined;
|
||||
}
|
||||
|
||||
function Group ({ className, isMember, members, palletReferenda, palletVote, ranks, referenda, trackId, trackName, tracks }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api, specName } = useApi();
|
||||
|
||||
const trackInfo = useMemo(
|
||||
() => getTrackInfo(api, specName, palletReferenda, tracks, trackId?.toNumber()),
|
||||
[api, specName, palletReferenda, tracks, trackId]
|
||||
);
|
||||
|
||||
return (
|
||||
<Table
|
||||
className={className}
|
||||
empty={referenda && t<string>('No active referenda')}
|
||||
header={[
|
||||
[trackName ? <>{trackName}<div>{trackInfo?.text}</div></> : t('referenda'), 'start', 7],
|
||||
[undefined, undefined, 1]
|
||||
]}
|
||||
key={
|
||||
trackName
|
||||
? `track:${trackName}`
|
||||
: 'untracked'
|
||||
}
|
||||
>
|
||||
{referenda && referenda.map((r) => (
|
||||
<Referendum
|
||||
isMember={isMember}
|
||||
key={r.key}
|
||||
members={members}
|
||||
palletReferenda={palletReferenda}
|
||||
palletVote={palletVote}
|
||||
ranks={ranks}
|
||||
trackInfo={trackInfo}
|
||||
value={r}
|
||||
/>
|
||||
))}
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(styled(Group)`
|
||||
th > h1 > div {
|
||||
display: inline-block;
|
||||
font-size: 1rem;
|
||||
padding-left: 1.5rem;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
`);
|
||||
@ -71,7 +71,7 @@ function expandOngoing (info: Referendum['info'], track?: PalletReferendaTrackIn
|
||||
};
|
||||
}
|
||||
|
||||
function Ongoing ({ isMember, members, palletReferenda, palletVote, value: { id, info, isConvictionVote, track } }: Props): React.ReactElement<Props> {
|
||||
function Ongoing ({ isMember, members, palletReferenda, palletVote, ranks, trackInfo, value: { id, info, isConvictionVote, track } }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { ongoing: { decisionDeposit, submissionDeposit, tally }, periods: { confirmEnd, decideEnd, periodEnd }, proposalHash, shortHash, tallyTotal } = useMemo(
|
||||
@ -133,6 +133,8 @@ function Ongoing ({ isMember, members, palletReferenda, palletVote, value: { id,
|
||||
members={members}
|
||||
palletVote={palletVote}
|
||||
preimage={preimage}
|
||||
ranks={ranks}
|
||||
trackInfo={trackInfo}
|
||||
/>
|
||||
</td>
|
||||
</>
|
||||
|
||||
@ -3,9 +3,9 @@
|
||||
|
||||
import type { Preimage } from '@polkadot/app-preimages/types';
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { PalletVote } from '../types';
|
||||
import type { PalletVote, TrackInfo } from '../types';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
|
||||
import { Button, ConvictionDropdown, Modal, ProposedAction, TxButton, VoteAccount, VoteValue } from '@polkadot/react-components';
|
||||
import { useAccounts, useApi, useToggle } from '@polkadot/react-hooks';
|
||||
@ -19,17 +19,49 @@ interface Props {
|
||||
members?: string[];
|
||||
palletVote: PalletVote;
|
||||
preimage?: Preimage;
|
||||
ranks?: BN[];
|
||||
trackInfo?: TrackInfo;
|
||||
}
|
||||
|
||||
function Voting ({ id, isConvictionVote, isMember, members, palletVote, preimage }: Props): React.ReactElement<Props> | null {
|
||||
function filterMembers (allAccounts: string[], members?: string[], ranks?: BN[], trackInfo?: TrackInfo): string[] | undefined {
|
||||
if (members) {
|
||||
const accounts = members.filter((a) => allAccounts.includes(a));
|
||||
|
||||
if (ranks && trackInfo && trackInfo.compare) {
|
||||
const cmp = trackInfo.compare;
|
||||
|
||||
return accounts.filter((_, i) => cmp(ranks[i]));
|
||||
}
|
||||
|
||||
return accounts;
|
||||
}
|
||||
|
||||
return members;
|
||||
}
|
||||
|
||||
function Voting ({ id, isConvictionVote, isMember, members, palletVote, preimage, ranks, trackInfo }: Props): React.ReactElement<Props> | null {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const { hasAccounts } = useAccounts();
|
||||
const { allAccounts, hasAccounts } = useAccounts();
|
||||
const [accountId, setAccountId] = useState<string | null>(null);
|
||||
const [balance, setBalance] = useState<BN | undefined>();
|
||||
const [conviction, setConviction] = useState(0);
|
||||
const [isVotingOpen, toggleVoting] = useToggle();
|
||||
|
||||
const filteredMembers = useMemo(
|
||||
() => filterMembers(allAccounts, members, ranks, trackInfo),
|
||||
[allAccounts, members, ranks, trackInfo]
|
||||
);
|
||||
|
||||
const isDisabled = useMemo(
|
||||
() => !isMember || (
|
||||
members && filteredMembers
|
||||
? !filteredMembers.length
|
||||
: false
|
||||
),
|
||||
[filteredMembers, isMember, members]
|
||||
);
|
||||
|
||||
if (!hasAccounts) {
|
||||
return null;
|
||||
}
|
||||
@ -53,7 +85,7 @@ function Voting ({ id, isConvictionVote, isMember, members, palletVote, preimage
|
||||
)}
|
||||
<Modal.Columns hint={t<string>('The vote will be recorded for this account. If another account delegated to this one, the delegated votes will also be counted.')}>
|
||||
<VoteAccount
|
||||
filter={members}
|
||||
filter={filteredMembers}
|
||||
onChange={setAccountId}
|
||||
/>
|
||||
</Modal.Columns>
|
||||
@ -84,7 +116,6 @@ function Voting ({ id, isConvictionVote, isMember, members, palletVote, preimage
|
||||
<TxButton
|
||||
accountId={accountId}
|
||||
icon='ban'
|
||||
isDisabled={!isMember}
|
||||
label={t<string>('Vote Nay')}
|
||||
onStart={toggleVoting}
|
||||
params={
|
||||
@ -97,7 +128,6 @@ function Voting ({ id, isConvictionVote, isMember, members, palletVote, preimage
|
||||
<TxButton
|
||||
accountId={accountId}
|
||||
icon='check'
|
||||
isDisabled={!isMember}
|
||||
label={t<string>('Vote Aye')}
|
||||
onStart={toggleVoting}
|
||||
params={
|
||||
@ -112,6 +142,7 @@ function Voting ({ id, isConvictionVote, isMember, members, palletVote, preimage
|
||||
)}
|
||||
<Button
|
||||
icon='check-to-slot'
|
||||
isDisabled={isDisabled}
|
||||
label={t<string>('Vote')}
|
||||
onClick={toggleVoting}
|
||||
/>
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
// Copyright 2017-2022 @polkadot/app-referenda authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { PalletReferenda, PalletVote, ReferendaGroup } from '../types';
|
||||
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import AddPreimage from '@polkadot/app-preimages/Preimages/Add';
|
||||
import { Button, Dropdown, Table } from '@polkadot/react-components';
|
||||
import { useAccounts, useApi } from '@polkadot/react-hooks';
|
||||
import { Button, Dropdown } from '@polkadot/react-components';
|
||||
import { useAccounts } from '@polkadot/react-hooks';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
import useReferenda from '../useReferenda';
|
||||
import useSummary from '../useSummary';
|
||||
import { getTrackInfo } from '../util';
|
||||
import Referendum from './Referendum';
|
||||
import Group from './Group';
|
||||
import Submit from './Submit';
|
||||
import Summary from './Summary';
|
||||
|
||||
@ -25,11 +25,11 @@ interface Props {
|
||||
members?: string[];
|
||||
palletReferenda: PalletReferenda;
|
||||
palletVote: PalletVote;
|
||||
ranks?: BN[];
|
||||
}
|
||||
|
||||
function Referenda ({ className, members, palletReferenda, palletVote }: Props): React.ReactElement<Props> {
|
||||
function Referenda ({ className, members, palletReferenda, palletVote, ranks }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api, specName } = useApi();
|
||||
const { allAccounts } = useAccounts();
|
||||
const [grouped, tracks] = useReferenda(palletReferenda);
|
||||
const summary = useSummary(palletReferenda, grouped);
|
||||
@ -80,43 +80,25 @@ function Referenda ({ className, members, palletReferenda, palletVote }: Props):
|
||||
tracks={tracks}
|
||||
/>
|
||||
</Button.Group>
|
||||
{filtered.map(({ referenda, trackId, trackName }: ReferendaGroup) => (
|
||||
<Table
|
||||
empty={referenda && t<string>('No active referenda')}
|
||||
header={[
|
||||
[trackName ? <>{trackName}<div>{getTrackInfo(api, specName, palletReferenda, tracks, trackId?.toNumber())?.text}</div></> : t('referenda'), 'start', 7],
|
||||
[undefined, undefined, 1]
|
||||
]}
|
||||
key={
|
||||
trackName
|
||||
? `track:${trackName}`
|
||||
: 'untracked'
|
||||
}
|
||||
>
|
||||
{referenda && referenda.map((r) => (
|
||||
<Referendum
|
||||
isMember={isMember}
|
||||
key={r.key}
|
||||
members={members}
|
||||
palletReferenda={palletReferenda}
|
||||
palletVote={palletVote}
|
||||
value={r}
|
||||
/>
|
||||
))}
|
||||
</Table>
|
||||
{filtered.map(({ key, referenda, trackId, trackName }: ReferendaGroup) => (
|
||||
<Group
|
||||
isMember={isMember}
|
||||
key={key}
|
||||
members={members}
|
||||
palletReferenda={palletReferenda}
|
||||
palletVote={palletVote}
|
||||
ranks={ranks}
|
||||
referenda={referenda}
|
||||
trackId={trackId}
|
||||
trackName={trackName}
|
||||
tracks={tracks}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(styled(Referenda)`
|
||||
th > h1 > div {
|
||||
display: inline-block;
|
||||
font-size: 1rem;
|
||||
padding-left: 1.5rem;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.ui--Dropdown.topDropdown {
|
||||
min-width: 25rem;
|
||||
padding-left: 0;
|
||||
|
||||
@ -9,6 +9,7 @@ export type PalletReferenda = 'referenda' | 'rankedPolls' | 'fellowshipReferenda
|
||||
export type PalletVote = 'convictionVoting' | 'rankedCollective' | 'fellowshipCollective';
|
||||
|
||||
export interface ReferendaGroup {
|
||||
key: string;
|
||||
track?: PalletReferendaTrackInfo;
|
||||
trackId?: BN;
|
||||
trackName?: string;
|
||||
@ -34,6 +35,8 @@ export interface ReferendumProps {
|
||||
members?: string[];
|
||||
palletReferenda: PalletReferenda;
|
||||
palletVote: PalletVote;
|
||||
ranks?: BN[];
|
||||
trackInfo?: TrackInfo;
|
||||
value: Referendum;
|
||||
}
|
||||
|
||||
@ -42,3 +45,9 @@ export interface Summary {
|
||||
refActive?: number;
|
||||
refCount?: BN;
|
||||
}
|
||||
|
||||
export interface TrackInfo {
|
||||
compare?: (input: BN) => boolean;
|
||||
origin: Record<string, string> | Record<string, string>[];
|
||||
text?: string;
|
||||
}
|
||||
|
||||
@ -92,14 +92,14 @@ const OPT_MULTI = {
|
||||
function group (referenda?: Referendum[], tracks?: [BN, PalletReferendaTrackInfo][]): ReferendaGroup[] {
|
||||
if (!referenda) {
|
||||
// return an empty group when we have no referenda
|
||||
return [{}];
|
||||
return [{ key: 'empty' }];
|
||||
} else if (!tracks) {
|
||||
// if we have no tracks, we just return the referenda sorted
|
||||
return [{ referenda: referenda.sort(sortReferenda) }];
|
||||
return [{ key: 'referenda', referenda: referenda.sort(sortReferenda) }];
|
||||
}
|
||||
|
||||
const grouped: ReferendaGroupKnown[] = [];
|
||||
const other: ReferendaGroupKnown = { referenda: [] };
|
||||
const other: ReferendaGroupKnown = { key: 'referenda', referenda: [] };
|
||||
|
||||
// sort the referenda by track inside groups
|
||||
for (let i = 0; i < referenda.length; i++) {
|
||||
@ -119,6 +119,7 @@ function group (referenda?: Referendum[], tracks?: [BN, PalletReferendaTrackInfo
|
||||
if (!group) {
|
||||
// we don't have a group as of yet, create one
|
||||
grouped.push({
|
||||
key: `track:${ref.trackId.toString()}`,
|
||||
referenda: [ref],
|
||||
track: ref.track,
|
||||
trackId: ref.trackId,
|
||||
@ -157,7 +158,7 @@ function useReferendaImpl (palletReferenda: PalletReferenda): [ReferendaGroup[],
|
||||
return useMemo(
|
||||
() => [
|
||||
(ids && ids.length === 0)
|
||||
? [{ referenda: [] }]
|
||||
? [{ key: 'referenda', referenda: [] }]
|
||||
: group(referenda, tracks),
|
||||
tracks
|
||||
],
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
import type { ApiPromise } from '@polkadot/api';
|
||||
import type { PalletReferendaTrackInfo } from '@polkadot/types/lookup';
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { TrackInfo } from './types';
|
||||
|
||||
import { getGovernanceTracks } from '@polkadot/apps-config';
|
||||
import { formatNumber, stringPascalCase } from '@polkadot/util';
|
||||
@ -20,8 +21,8 @@ export function getTrackName (trackId: BN, { name }: PalletReferendaTrackInfo):
|
||||
}`;
|
||||
}
|
||||
|
||||
export function getTrackInfo (api: ApiPromise, specName: string, palletReferenda: string, tracks?: [BN, PalletReferendaTrackInfo][], trackId?: number): { origin: Record<string, string> | Record<string, string>[], text?: string } | undefined {
|
||||
let info: { origin: Record<string, string> | Record<string, string>[], text?: string } | undefined;
|
||||
export function getTrackInfo (api: ApiPromise, specName: string, palletReferenda: string, tracks?: [BN, PalletReferendaTrackInfo][], trackId?: number): TrackInfo | undefined {
|
||||
let info: TrackInfo | undefined;
|
||||
|
||||
if (tracks && trackId !== undefined) {
|
||||
const originMap = getGovernanceTracks(api, specName, palletReferenda);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user