Democracy display updates (#1974)

* Democracy updates

* Democracy UI cleanups

* Cleanup labels

* Move seconding, again...
This commit is contained in:
Jaco Greeff 2019-12-02 13:51:29 +01:00 committed by GitHub
parent ba1aca535b
commit ffbea9788c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 221 additions and 205 deletions

View File

@ -6,44 +6,63 @@ import { DeriveProposal } from '@polkadot/api-derive/types';
import { I18nProps } from '@polkadot/react-components/types';
import React from 'react';
import { ActionItem, InputAddress, Static } from '@polkadot/react-components';
import styled from 'styled-components';
import { AddressMini, AddressSmall } from '@polkadot/react-components';
import { FormatBalance } from '@polkadot/react-query';
import { formatNumber } from '@polkadot/util';
import translate from '../translate';
import ProposalCell from './ProposalCell';
import Seconding from './Seconding';
interface Props extends I18nProps {
value: DeriveProposal;
}
function Proposal ({ className, t, value: { balance, index, proposal, seconds } }: Props): React.ReactElement<Props> {
function Proposal ({ className, t, value: { balance, index, proposal, proposer, seconds } }: Props): React.ReactElement<Props> {
return (
<ActionItem
className={className}
idNumber={index}
proposal={proposal}
>
<Seconding
depositors={seconds || []}
proposalId={index}
/>
{balance && seconds && (
<div>
{seconds.map((address, count): React.ReactNode => (
<InputAddress
isDisabled
label={count === 0 ? t('proposer') : t('depositor {{count}}', { replace: { count } })}
<tr className={className}>
<td className='number toppad'>{formatNumber(index)}</td>
<td className='top'>
<AddressSmall value={proposer} />
</td>
<td className='number together top'>
<FormatBalance label={<label>{t('locked')}</label>} value={balance} />
</td>
<ProposalCell className='top' proposal={proposal} />
<td className='top'>
{seconds
.filter((_address, index): boolean => index !== 0)
.map((address, count): React.ReactNode => (
<AddressMini
className='identityIcon'
key={`${count}:${address}`}
defaultValue={address}
label={count ? undefined : t('seconds')}
value={address}
withBalance={false}
/>
))}
<Static label={t('balance')}>
<FormatBalance value={balance} />
</Static>
</div>
)}
</ActionItem>
</td>
<td className='together number top'>
<Seconding
depositors={seconds || []}
proposalId={index}
/>
</td>
</tr>
);
}
export default translate(Proposal);
export default translate(
styled(Proposal)`
.identityIcon {
&:first-child {
padding-top: 0;
}
&:last-child {
margin-bottom: 4px;
}
}
`
);

View File

@ -0,0 +1,39 @@
// 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 { Proposal } from '@polkadot/types/interfaces';
import { I18nProps } from '@polkadot/react-components/types';
import React from 'react';
import { registry } from '@polkadot/react-api';
import { Call } from '@polkadot/react-components';
import translate from '../translate';
interface Props extends I18nProps {
proposal: Proposal;
}
function ProposalCell ({ className, proposal, t }: Props): React.ReactElement<Props> {
const { meta, method, section } = registry.findMetaCall(proposal.callIndex);
return (
<td className={`${className} all`}>
<div>{section}.{method}</div>
<details>
<summary>{
meta && meta.documentation
? meta.documentation.join(' ')
: t('Details')
}</summary>
<Call
value={proposal}
withHash
/>
</details>
</td>
);
}
export default translate(ProposalCell);

View File

@ -6,6 +6,7 @@ import { DeriveProposal } from '@polkadot/api-derive/types';
import { I18nProps as Props } from '@polkadot/react-components/types';
import React from 'react';
import { Table } from '@polkadot/react-components';
import { useApi, trackStream } from '@polkadot/react-hooks';
import ProposalDisplay from './Proposal';
@ -18,15 +19,20 @@ function Proposals ({ className, t }: Props): React.ReactElement<Props> {
return (
<div className={`proposalSection ${className}`}>
<h1>{t('proposals')}</h1>
{
proposals?.length
? proposals.map((proposal): React.ReactNode => (
<ProposalDisplay
key={proposal.index.toString()}
value={proposal}
/>
))
: t('No active proposals')
{proposals?.length
? (
<Table>
<Table.Body>
{proposals.map((proposal): React.ReactNode => (
<ProposalDisplay
key={proposal.index.toString()}
value={proposal}
/>
))}
</Table.Body>
</Table>
)
: t('No active proposals')
}
</div>
);

View File

@ -9,15 +9,13 @@ import { I18nProps } from '@polkadot/react-components/types';
import BN from 'bn.js';
import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
import { ActionItem, Chart, Static, Voting } from '@polkadot/react-components';
import { formatBalance, formatNumber } from '@polkadot/util';
import VoteThreshold from '@polkadot/react-params/Param/VoteThreshold';
import { formatNumber } from '@polkadot/util';
import { withCalls, withMulti } from '@polkadot/react-api';
import { FormatBalance } from '@polkadot/react-query';
import translate from '../translate';
const COLORS_AYE = ['#64bebe', '#5badad'];
const COLORS_NAY = ['#d75ea1', '#e189ba'];
import ProposalCell from './ProposalCell';
import Voting from './Voting';
interface Props extends I18nProps {
idNumber: BN;
@ -37,7 +35,7 @@ interface State {
}
function Referendum ({ chain_bestNumber, className, democracy_enactmentPeriod, democracy_referendumVotesFor, t, value }: Props): React.ReactElement<Props> | null {
const [{ voteCount, voteCountAye, voteCountNay, votedAye, votedNay, votedTotal }, setState] = useState<State>({
const [{ voteCountAye, voteCountNay, votedAye, votedNay }, setState] = useState<State>({
voteCount: 0,
voteCountAye: 0,
voteCountNay: 0,
@ -85,63 +83,29 @@ function Referendum ({ chain_bestNumber, className, democracy_enactmentPeriod, d
const enactBlock = (democracy_enactmentPeriod || new BN(0)).add(value.info.end);
return (
<ActionItem
className={className}
idNumber={value.index}
proposal={value.proposal}
accessory={
<Voting
idNumber={value.index}
proposal={value.proposal}
/>
}
>
<div>
<Static label={t('ending at')}>
{t('block #{{blockNumber}}, {{remaining}} blocks remaining', {
replace: {
blockNumber: formatNumber(value.info.end),
remaining: formatNumber(value.info.end.sub(chain_bestNumber).subn(1))
}
})}
</Static>
<Static label={t('activate at (if passed)')}>
{t('block #{{blockNumber}}', {
replace: {
blockNumber: formatNumber(enactBlock)
}
})}
</Static>
<VoteThreshold
isDisabled
defaultValue={{ isValid: true, value: value.info.threshold }}
label={t('vote threshold')}
name='voteThreshold'
type={{
info: 0,
type: 'VoteThreshold'
}}
/>
{voteCount !== 0 && votedTotal.gtn(0) && (
<div className='democracy--Referendum-results chart'>
<Chart.HorizBar
values={[
{
colors: COLORS_AYE,
label: `Aye, ${formatBalance(votedAye, { forceUnit: '-' })} (${formatNumber(voteCountAye)})`,
value: votedAye.muln(10000).div(votedTotal).toNumber() / 100
},
{
colors: COLORS_NAY,
label: `Nay, ${formatBalance(votedNay, { forceUnit: '-' })} (${formatNumber(voteCountNay)})`,
value: votedNay.muln(10000).div(votedTotal).toNumber() / 100
}
]}
/>
</div>
)}
</div>
</ActionItem>
<tr className={className}>
<td className='number toppad'>{formatNumber(value.index)}</td>
<ProposalCell className='top' proposal={value.proposal} />
<td className='number together top'>
<label>{t('remaining')}</label>
{formatNumber(value.info.end.sub(chain_bestNumber).subn(1))} blocks
</td>
<td className='number together top'>
<label>{t('activate at')}</label>
{formatNumber(enactBlock)}
</td>
<td className='number together top'>
<label>{t('Aye ({{count}})', { replace: { count: formatNumber(voteCountAye) } })}</label>
<FormatBalance value={votedAye} />
</td>
<td className='number together top'>
<label>{t('Nay ({{count}})', { replace: { count: formatNumber(voteCountNay) } })}</label>
<FormatBalance value={votedNay} />
</td>
<td className='number together top'>
<Voting referendumId={value.index} />
</td>
</tr>
);
}

View File

@ -6,6 +6,7 @@ import { DerivedReferendum } from '@polkadot/api-derive/types';
import { I18nProps as Props } from '@polkadot/react-components/types';
import React from 'react';
import { Table } from '@polkadot/react-components';
import { useApi, trackStream } from '@polkadot/react-hooks';
import Referendum from './Referendum';
@ -18,16 +19,21 @@ function Referendums ({ className, t }: Props): React.ReactElement<Props> {
return (
<div className={`proposalSection ${className}`}>
<h1>{t('referenda')}</h1>
{
referendums?.length
? referendums.map((referendum): React.ReactNode => (
<Referendum
idNumber={referendum.index}
key={referendum.index.toString()}
value={referendum}
/>
))
: t('No active referendums')
{referendums?.length
? (
<Table>
<Table.Body>
{referendums.map((referendum): React.ReactNode => (
<Referendum
idNumber={referendum.index}
key={referendum.index.toString()}
value={referendum}
/>
))}
</Table.Body>
</Table>
)
: t('No active referendums')
}
</div>
);

View File

@ -62,7 +62,7 @@ function Seconding ({ depositors, proposalId, t }: Props): React.ReactElement<Pr
isPrimary
label={t('Second')}
icon='sign-in'
onClick={_toggleSeconding}
onStart={_toggleSeconding}
params={[proposalId]}
tx='democracy.second'
/>
@ -70,14 +70,12 @@ function Seconding ({ depositors, proposalId, t }: Props): React.ReactElement<Pr
</Modal.Actions>
</Modal>
)}
<Button.Group>
<Button
isPrimary
label={t('Second proposal')}
icon='toggle off'
onClick={_toggleSeconding}
/>
</Button.Group>
<Button
isPrimary
label={t('Second')}
icon='toggle off'
onClick={_toggleSeconding}
/>
</>
);
}

View File

@ -573,7 +573,7 @@ export default withMulti(
}
.staking--label {
margin: 0 1.75rem -0.75rem 0;
margin: 0 0 -0.75rem 1.75rem;
}
}
@ -602,10 +602,10 @@ export default withMulti(
}
.staking--Account-Nominee {
text-align: right;
text-align: left;
.staking--label {
margin: 0 2.25rem -.75rem 0;
margin: 0 0 -.75rem 2.25rem;
}
}

View File

@ -10,12 +10,11 @@ import { ValidatorFilter } from '../types';
import BN from 'bn.js';
import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
import { AddressMini, Badge, Icon } from '@polkadot/react-components';
import { AddressMini, AddressSmall, Badge, Icon } from '@polkadot/react-components';
import { trackStream, useApi } from '@polkadot/react-hooks';
import { FormatBalance } from '@polkadot/react-query';
import { formatNumber } from '@polkadot/util';
import AddressSmall from '../AddressSmall';
import translate from '../translate';
interface Props extends I18nProps {

View File

@ -8,10 +8,10 @@ import { AccountId, EraPoints, Points } from '@polkadot/types/interfaces';
import { ValidatorFilter } from '../types';
import React, { useEffect, useState } from 'react';
import { Dropdown, FilterOverlay, Table } from '@polkadot/react-components';
import { useApi, useFavorites } from '@polkadot/react-hooks';
import keyring from '@polkadot/ui-keyring';
import Table from '../Table';
import { STORE_FAVS_BASE } from '../constants';
import translate from '../translate';
import Address from './Address';
@ -60,7 +60,7 @@ function accountsToString (accounts: AccountId[]): string[] {
function CurrentList ({ authorsMap, hasQueries, isIntentions, lastAuthors, next, recentlyOnline, stakingOverview, t }: Props): React.ReactElement<Props> {
const { isSubstrateV2 } = useApi();
const [favorites, toggleFavorite] = useFavorites(STORE_FAVS_BASE);
const [filter] = useState<ValidatorFilter>('all');
const [filter, setFilter] = useState<ValidatorFilter>('all');
const [myAccounts] = useState(keyring.getAccounts().map(({ address }): string => address));
const [{ elected, validators, waiting }, setFiltered] = useState<{ elected: AccountExtend[]; validators: AccountExtend[]; waiting: AccountExtend[] }>({ elected: [], validators: [], waiting: [] });
@ -102,26 +102,9 @@ function CurrentList ({ authorsMap, hasQueries, isIntentions, lastAuthors, next,
/>
));
const _renderTable = (className: string, rows: React.ReactNode): React.ReactNode =>
<Table className={className}>
{/* <Table.Head>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th className='number'>{t('own stake')}</th>
<th className='number'>{t('other stake')}</th>
<th className='number'>{t('nominators')}</th>
<th className='number'>{t('commission')}</th>
<th className='number'>{t('points')}</th>
<th className='number'>{t('last #')}</th>
<th>&nbsp;</th>
</Table.Head> */}
<Table.Body>{rows}</Table.Body>
</Table>;
return (
<div>
{/* <FilterOverlay>
<FilterOverlay>
<Dropdown
onChange={setFilter}
options={[
@ -136,18 +119,18 @@ function CurrentList ({ authorsMap, hasQueries, isIntentions, lastAuthors, next,
value={filter}
withLabel={false}
/>
</FilterOverlay> */}
{_renderTable(isIntentions ? 'staking--hidden' : '', (
<>
</FilterOverlay>
<Table className={isIntentions ? 'staking--hidden' : ''}>
<Table.Body>
{_renderRows(validators, t('validators'), true)}
</>
))}
{_renderTable(isIntentions ? '' : 'staking--hidden', (
<>
</Table.Body>
</Table>
<Table className={isIntentions ? '' : 'staking--hidden'}>
<Table.Body>
{_renderRows(elected, t('intention'), false)}
{_renderRows(waiting, t('intention'), false)}
</>
))}
</Table.Body>
</Table>
</div>
);
}

View File

@ -6,11 +6,10 @@ import { I18nProps } from '@polkadot/react-components/types';
import { ValidatorInfo } from './types';
import React from 'react';
import { Icon } from '@polkadot/react-components';
import { AddressSmall, Icon } from '@polkadot/react-components';
import { FormatBalance } from '@polkadot/react-query';
import { formatNumber } from '@polkadot/util';
import AddressSmall from '../AddressSmall';
import translate from '../translate';
interface Props extends I18nProps {

View File

@ -11,10 +11,9 @@ import { ValidatorInfo } from './types';
import BN from 'bn.js';
import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
import { InputBalance } from '@polkadot/react-components';
import { InputBalance, Table } from '@polkadot/react-components';
import { useAccounts, useApi, useFavorites, trackStream } from '@polkadot/react-hooks';
import Table from '../Table';
import { STORE_FAVS_BASE } from '../constants';
import translate from '../translate';
import Summary from './Summary';
@ -174,28 +173,6 @@ function Targets ({ className, sessionRewards, t }: Props): React.ReactElement<P
value={amount}
/>
<Table>
{/* <Table.Head>
<th>&nbsp;</th>
<th className='number'>
<Icon
name='info circle'
data-tip
data-for='ranking-trigger'
/>
<Tooltip
text={t('Ranking is done of the estimated best return, taking the commission and total bonded amount into account. It does not incorporate validator liveliness according to length of operation nor number of blocks produced.')}
trigger='ranking-trigger'
/>
</th>
<th className='number'>&nbsp;</th>
<th className='number'>{t('commission')}</th>
<th className='number'>{t('nominators')}</th>
<th className='number'>{t('total stake')}</th>
<th className='number'>{t('own stake')}</th>
<th className='number'>{t('other stake')}</th>
<th className='number'>{t('payout (est.)')}</th>
<th>&nbsp;</th>
</Table.Head> */}
<Table.Body>
{validators.map((info): React.ReactNode =>
<Validator

View File

@ -24,6 +24,7 @@ interface Props extends BareProps {
iconInfo?: React.ReactNode;
isPadded?: boolean;
isShort?: boolean;
label?: React.ReactNode;
type?: KeyringItemType;
value?: AccountId | AccountIndex | Address | string;
withAddress?: boolean;
@ -33,7 +34,7 @@ interface Props extends BareProps {
withName?: boolean;
}
function AddressMini ({ balance, bonded, children, className, iconInfo, isPadded = true, style, value, withAddress = true, withBalance = false, withBonded = false, withLockedVote = false, withName = true }: Props): React.ReactElement<Props> | null {
function AddressMini ({ balance, bonded, children, className, iconInfo, isPadded = true, label, style, value, withAddress = true, withBalance = false, withBonded = false, withLockedVote = false, withName = true }: Props): React.ReactElement<Props> | null {
if (!value) {
return null;
}
@ -43,17 +44,9 @@ function AddressMini ({ balance, bonded, children, className, iconInfo, isPadded
className={classes('ui--AddressMini', isPadded ? 'padded' : '', className)}
style={style}
>
<div className='ui--AddressMini-info'>
{withAddress && (
<div className='ui--AddressMini-address'>
{withName
? <AccountName params={value} />
: toShortAddress(value)
}
</div>
)}
{children}
</div>
{label && (
<label className='ui--AddressMini-label'>{label}</label>
)}
<div className='ui--AddressMini-icon'>
<IdentityIcon
size={24}
@ -65,6 +58,17 @@ function AddressMini ({ balance, bonded, children, className, iconInfo, isPadded
</div>
)}
</div>
<div className='ui--AddressMini-info'>
{withAddress && (
<div className='ui--AddressMini-address'>
{withName
? <AccountName params={value} />
: toShortAddress(value)
}
</div>
)}
{children}
</div>
<div className='ui--AddressMini-balances'>
{withBalance && (
<BalanceDisplay
@ -90,11 +94,12 @@ function AddressMini ({ balance, bonded, children, className, iconInfo, isPadded
export default styled(AddressMini)`
display: inline-block;
padding: 0 0.25rem 0 1rem;
text-align: left;
white-space: nowrap;
&.padded {
display: inline-block;
padding: 0.25rem 0 0 1rem;
padding: 0.25rem 1rem 0 0;
}
&.summary {
@ -105,12 +110,15 @@ export default styled(AddressMini)`
.ui--AddressMini-address {
font-family: monospace;
max-width: 9rem;
min-width: 9em;
overflow: hidden;
text-align: right;
text-align: left;
text-overflow: ellipsis;
}
.ui--AddressMini-label {
margin: 0 0 -0.5rem 2.25rem;
}
.ui--AddressMini-balances {
display: grid;
@ -118,14 +126,14 @@ export default styled(AddressMini)`
.ui--Bonded,
.ui--LockedVote {
font-size: 0.75rem;
margin-right: 2.25rem;
margin-left: 2.25rem;
margin-top: -0.5rem;
text-align: right;
text-align: left;
}
}
.ui--AddressMini-icon {
margin: 0 0 0 0.5rem;
margin: 0 0.5rem 0 0;
.ui--AddressMini-icon-info {
position: absolute;

View File

@ -1,4 +1,4 @@
// Copyright 2017-2019 @polkadot/app-staking authors & contributors
// 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.
@ -6,9 +6,10 @@ import { Address, AccountId } from '@polkadot/types/interfaces';
import React from 'react';
import styled from 'styled-components';
import { IdentityIcon } from '@polkadot/react-components';
import { AccountIndex, AccountName } from '@polkadot/react-query';
import IdentityIcon from './IdentityIcon';
interface Props {
className?: string;
defaultName?: string;

View File

@ -19,6 +19,7 @@ interface Value {
interface Props extends BareProps {
aspectRatio?: number;
max?: number;
showLabels?: boolean;
values: Value[];
withColors?: boolean;
}
@ -41,7 +42,7 @@ interface Config {
const alphaColor = (hexColor: string): string =>
ChartJs.helpers.color(hexColor).alpha(0.65).rgbString();
function calculateOptions (aspectRatio: number, values: Value[], jsonValues: string, max: number): State {
function calculateOptions (aspectRatio: number, values: Value[], jsonValues: string, max: number, showLabels: boolean): State {
const chartData = values.reduce((data, { colors: [normalColor = '#00f', hoverColor], label, value }): Config => {
const dataset = data.datasets[0];
@ -71,10 +72,9 @@ function calculateOptions (aspectRatio: number, values: Value[], jsonValues: str
},
scales: {
xAxes: [{
ticks: {
beginAtZero: true,
max
}
ticks: showLabels
? { beginAtZero: true, max }
: { display: false }
}]
}
},
@ -82,14 +82,14 @@ function calculateOptions (aspectRatio: number, values: Value[], jsonValues: str
};
}
export default function ChartHorizBar ({ aspectRatio = 8, className, max = 100, style, values }: Props): React.ReactElement<Props> | null {
export default function ChartHorizBar ({ aspectRatio = 8, className, max = 100, showLabels = false, style, values }: Props): React.ReactElement<Props> | null {
const [{ chartData, chartOptions, jsonValues }, setState] = useState<State>({});
useEffect((): void => {
const newJsonValues = JSON.stringify(values);
if (newJsonValues !== jsonValues) {
setState(calculateOptions(aspectRatio, values, newJsonValues, max));
setState(calculateOptions(aspectRatio, values, newJsonValues, max, showLabels));
}
}, [values]);

View File

@ -1,4 +1,4 @@
// Copyright 2017-2019 @polkadot/app-staking authors & contributors
// 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.
@ -63,6 +63,8 @@ export default styled(Table)`
width: 100%;
tr {
width: 100%;
&.isHighlight {
td {
background: #ffffed;
@ -86,6 +88,10 @@ export default styled(Table)`
td, th {
text-align: left;
&.all {
width: 100%;
}
&.number {
text-align: right;
}
@ -97,6 +103,15 @@ export default styled(Table)`
&.toggle {
cursor: pointer;
}
&.top {
vertical-align: top;
}
&.toppad {
padding-top: 1.25rem;
vertical-align: top;
}
}
td {

View File

@ -7,6 +7,7 @@ export { default as AddressCard } from './AddressCard';
export { default as AddressInfo } from './AddressInfo';
export { default as AddressMini } from './AddressMini';
export { default as AddressRow } from './AddressRow';
export { default as AddressSmall } from './AddressSmall';
export { default as Available } from './Available';
export { default as Badge } from './Badge';
export { default as Balance } from './Balance';
@ -71,6 +72,7 @@ export { default as Row } from './Row';
export { default as Static } from './Static';
export { default as Status, StatusContext } from './Status';
export { default as SummaryBox } from './SummaryBox';
export { default as Table } from './Table';
export { default as Tabs } from './Tabs';
export { default as Toggle } from './Toggle';
export { default as Tooltip } from './Tooltip';