Browse Source

Display council voters (#1787)

* Display council voters

* Show voting stake

* Adjust AddressMini widths
Jaco Greeff 5 years ago
parent
commit
b18a8e7e98

+ 6 - 2
packages/app-council/src/Overview/Candidate.tsx

@@ -9,17 +9,21 @@ import React from 'react';
 import { AddressCard } from '@polkadot/react-components';
 
 import translate from '../translate';
+import Voters from './Voters';
 
 interface Props extends I18nProps {
   address: AccountId;
+  voters?: AccountId[];
 }
 
-function Candidate ({ address, t }: Props): React.ReactElement<Props> {
+function Candidate ({ address, t, voters }: Props): React.ReactElement<Props> {
   return (
     <AddressCard
       defaultName={t('candidate')}
       value={address}
-    />
+    >
+      <Voters voters={voters} />
+    </AddressCard>
   );
 }
 

+ 6 - 2
packages/app-council/src/Overview/Member.tsx

@@ -9,17 +9,21 @@ import React from 'react';
 import { AddressCard } from '@polkadot/react-components';
 
 import translate from '../translate';
+import Voters from './Voters';
 
 interface Props extends I18nProps {
   address: AccountId;
+  voters?: AccountId[];
 }
 
-function Member ({ address, t }: Props): React.ReactElement<Props> {
+function Member ({ address, t, voters }: Props): React.ReactElement<Props> {
   return (
     <AddressCard
       defaultName={t('council member')}
       value={address}
-    />
+    >
+      <Voters voters={voters} />
+    </AddressCard>
   );
 }
 

+ 29 - 3
packages/app-council/src/Overview/Members.tsx

@@ -3,19 +3,23 @@
 // This software may be modified and distributed under the terms
 // of the Apache-2.0 license. See the LICENSE file for details.
 
+import { AccountId } from '@polkadot/types/interfaces';
 import { I18nProps } from '@polkadot/react-components/types';
 import { ComponentProps } from './types';
 
 import React from 'react';
+import { withCalls } from '@polkadot/react-api';
 import { Columar, Column } from '@polkadot/react-components';
 
 import translate from '../translate';
 import Candidate from './Candidate';
 import Member from './Member';
 
-interface Props extends I18nProps, ComponentProps {}
+interface Props extends I18nProps, ComponentProps {
+  allVotes?: Record<string, AccountId[]>;
+}
 
-function Members ({ electionsInfo: { candidates, members }, t }: Props): React.ReactElement<Props> {
+function Members ({ allVotes = {}, electionsInfo: { candidates, members }, t }: Props): React.ReactElement<Props> {
   return (
     <Columar>
       <Column
@@ -26,6 +30,7 @@ function Members ({ electionsInfo: { candidates, members }, t }: Props): React.R
           <Member
             address={address}
             key={address.toString()}
+            voters={allVotes[address.toString()]}
           />
         ))}
       </Column>
@@ -37,6 +42,7 @@ function Members ({ electionsInfo: { candidates, members }, t }: Props): React.R
           <Candidate
             address={address}
             key={address.toString()}
+            voters={allVotes[address.toString()]}
           />
         ))}
       </Column>
@@ -44,4 +50,24 @@ function Members ({ electionsInfo: { candidates, members }, t }: Props): React.R
   );
 }
 
-export default translate(Members);
+export default translate(
+  withCalls<Props>(
+    ['query.electionsPhragmen.votesOf', {
+      propName: 'allVotes',
+      transform: ([voters, casted]: [AccountId[], AccountId[][]]): Record<string, AccountId[]> =>
+        voters.reduce((result: Record<string, AccountId[]>, voter, index): Record<string, AccountId[]> => {
+          casted[index].forEach((candidate): void => {
+            const address = candidate.toString();
+
+            if (!result[address]) {
+              result[address] = [];
+            }
+
+            result[address].push(voter);
+          });
+
+          return result;
+        }, {})
+    }]
+  )(Members)
+);

+ 42 - 0
packages/app-council/src/Overview/Voters.tsx

@@ -0,0 +1,42 @@
+// Copyright 2017-2019 @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 { I18nProps } from '@polkadot/react-components/types';
+import { AccountId } from '@polkadot/types/interfaces';
+
+import React from 'react';
+import { AddressMini } from '@polkadot/react-components';
+
+import translate from '../translate';
+
+interface Props extends I18nProps {
+  voters?: AccountId[];
+}
+
+function Voters ({ voters, t }: Props): React.ReactElement<Props> | null {
+  if (!voters || !voters.length) {
+    return null;
+  }
+
+  return (
+    <details>
+      <summary>
+        {t('Voters ({{count}})', {
+          replace: {
+            count: voters.length
+          }
+        })}
+      </summary>
+      {voters.map((who): React.ReactNode =>
+        <AddressMini
+          key={who.toString()}
+          value={who}
+          withLockedVote
+        />
+      )}
+    </details>
+  );
+}
+
+export default translate(Voters);

+ 22 - 32
packages/react-components/src/AddressMini.tsx

@@ -14,6 +14,7 @@ import { classes, getAddressName, toShortAddress } from './util';
 import BalanceDisplay from './Balance';
 import BondedDisplay from './Bonded';
 import IdentityIcon from './IdentityIcon';
+import LockedVote from './LockedVote';
 
 interface Props extends BareProps {
   balance?: BN | BN[];
@@ -27,6 +28,7 @@ interface Props extends BareProps {
   withAddress?: boolean;
   withBalance?: boolean;
   withBonded?: boolean;
+  withLockedVote?: boolean;
 }
 
 function renderAddressOrName ({ isShort = true, withAddress = true, type }: Props, address: string): React.ReactNode {
@@ -47,35 +49,8 @@ function renderAddressOrName ({ isShort = true, withAddress = true, type }: Prop
   );
 }
 
-function renderBalance ({ balance, value, withBalance = false }: Props): React.ReactNode {
-  if (!withBalance || !value) {
-    return null;
-  }
-
-  return (
-    <BalanceDisplay
-      balance={balance}
-      params={value}
-    />
-  );
-}
-
-function renderBonded ({ bonded, value, withBonded = false }: Props): React.ReactNode {
-  if (!withBonded || !value) {
-    return null;
-  }
-
-  return (
-    <BondedDisplay
-      bonded={bonded}
-      label=''
-      params={value}
-    />
-  );
-}
-
 function AddressMini (props: Props): React.ReactElement<Props> | null {
-  const { children, className, iconInfo, isPadded = true, style, value } = props;
+  const { balance, bonded, children, className, iconInfo, isPadded = true, style, value, withBalance = false, withBonded = false, withLockedVote = false } = props;
 
   if (!value) {
     return null;
@@ -104,8 +79,22 @@ function AddressMini (props: Props): React.ReactElement<Props> | null {
         )}
       </div>
       <div className='ui--AddressMini-balances'>
-        {renderBalance(props)}
-        {renderBonded(props)}
+        {withBalance && (
+          <BalanceDisplay
+            balance={balance}
+            params={value}
+          />
+        )}
+        {withBonded && (
+          <BondedDisplay
+            bonded={bonded}
+            label=''
+            params={value}
+          />
+        )}
+        {withLockedVote && (
+          <LockedVote params={value} />
+        )}
       </div>
     </div>
   );
@@ -131,7 +120,7 @@ export default styled(AddressMini)`
     &.withName {
       font-family: monospace;
       max-width: 9rem;
-      min-width: 4em;
+      min-width: 9em;
       overflow: hidden;
       text-align: right;
       text-overflow: ellipsis;
@@ -145,7 +134,8 @@ export default styled(AddressMini)`
   .ui--AddressMini-balances {
     display: grid;
 
-    .ui--Bonded {
+    .ui--Bonded,
+    .ui--LockedVote {
       font-size: 0.75rem;
       margin-right: 2.25rem;
       margin-top: -0.5rem;

+ 34 - 0
packages/react-components/src/LockedVote.tsx

@@ -0,0 +1,34 @@
+// Copyright 2017-2019 @polkadot/react-components 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 { AccountId, AccountIndex, Address } from '@polkadot/types/interfaces';
+import { BareProps } from './types';
+
+import React from 'react';
+import { LockedVote } from '@polkadot/react-query';
+
+import { classes } from './util';
+
+export interface Props extends BareProps {
+  label?: React.ReactNode;
+  params?: AccountId | AccountIndex | Address | string | Uint8Array | null;
+  withLabel?: boolean;
+}
+
+export default function LockedVoteDisplay (props: Props): React.ReactElement<Props> | null {
+  const { params, className, label, style } = props;
+
+  if (!params) {
+    return null;
+  }
+
+  return (
+    <LockedVote
+      className={classes('ui--LockedVote', className)}
+      label={label}
+      params={params}
+      style={style}
+    />
+  );
+}

+ 1 - 0
packages/react-components/src/index.tsx

@@ -52,6 +52,7 @@ export { default as Label } from './Label';
 export { default as LabelHelp } from './LabelHelp';
 export { default as Labelled } from './Labelled';
 export { default as LinkPolkascan } from './LinkPolkascan';
+export { default as LockedVote } from './LockedVote';
 export { default as Menu } from './Menu';
 export { default as Messages } from './Messages';
 export { default as MessageSignature } from './MessageSignature';

+ 35 - 0
packages/react-query/src/LockedVote.tsx

@@ -0,0 +1,35 @@
+/* eslint-disable @typescript-eslint/camelcase */
+// Copyright 2017-2019 @polkadot/react-query 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 { BareProps, CallProps } from '@polkadot/react-api/types';
+import { AccountId, AccountIndex, Address, BalanceOf } from '@polkadot/types/interfaces';
+
+import React from 'react';
+
+import { withCalls } from '@polkadot/react-api';
+import { formatBalance } from '@polkadot/util';
+
+interface Props extends BareProps, CallProps {
+  children?: React.ReactNode;
+  label?: React.ReactNode;
+  params?: AccountId | AccountIndex | Address | string | Uint8Array | null;
+  electionsPhragmen_stakeOf?: BalanceOf;
+}
+
+export function LockedVote ({ children, className, electionsPhragmen_stakeOf, label = '' }: Props): React.ReactElement<Props> {
+  return (
+    <div className={className}>
+      {label}{
+        electionsPhragmen_stakeOf
+          ? formatBalance(electionsPhragmen_stakeOf)
+          : '0'
+      }{children}
+    </div>
+  );
+}
+
+export default withCalls<Props>(
+  ['query.electionsPhragmen.stakeOf', { paramName: 'params' }]
+)(LockedVote);

+ 1 - 0
packages/react-query/src/index.ts

@@ -10,6 +10,7 @@ export { default as BestFinalized } from './BestFinalized';
 export { default as BestNumber } from './BestNumber';
 export { default as Chain } from './Chain';
 export { default as Elapsed } from './Elapsed';
+export { default as LockedVote } from './LockedVote';
 export { default as NodeName } from './NodeName';
 export { default as NodeVersion } from './NodeVersion';
 export { default as Nonce } from './Nonce';