Some more functional component conversions (#1648)

This commit is contained in:
Jaco Greeff 2019-09-23 17:46:15 +02:00 committed by GitHub
parent aef86d373a
commit 00048285d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 493 additions and 720 deletions

View File

@ -21,86 +21,35 @@ interface Props extends I18nProps {
votes: Votes | null;
}
interface State {
votedTotal: number;
votedNay: number;
votedAye: number;
}
class Motion extends React.PureComponent<Props, State> {
public state: State = {
votedTotal: 0,
votedAye: 0,
votedNay: 0
};
public static getDerivedStateFromProps ({ votes }: Props): State | null {
if (!votes) {
return null;
}
const { ayes, nays } = votes;
const newState: State = {
votedNay: nays.length,
votedAye: ayes.length,
votedTotal: ayes.length + nays.length
};
return newState;
function Motion ({ className, hash, proposal, t, votes }: Props): React.ReactElement<Props> | null {
if (!proposal || !votes) {
return null;
}
public render (): React.ReactNode {
const { className, hash, proposal, votes } = this.props;
const { ayes, index, nays, threshold } = votes;
if (!proposal || !votes) {
return null;
}
const { index } = votes;
return (
<ActionItem
className={className}
accessory={
<Voting
hash={hash}
isCouncil
idNumber={index}
proposal={proposal}
/>
}
expandNested
idNumber={index}
proposal={proposal}
>
{this.renderInfo()}
</ActionItem>
);
}
private renderInfo (): React.ReactNode {
const { votes, t } = this.props;
if (!votes) {
return null;
}
const { ayes, nays, threshold } = votes;
return (
return (
<ActionItem
className={className}
accessory={
<Voting
hash={hash}
isCouncil
idNumber={index}
proposal={proposal}
/>
}
expandNested
idNumber={index}
proposal={proposal}
>
<div>
<h4>
{t(
'ayes ({{ayes}}/{{threshold}} to approve)',
{
replace: {
ayes: ayes.length,
threshold: threshold.toString()
}
}
)}
</h4>
<h4>{t('ayes ({{ayes}}/{{threshold}} to approve)', {
replace: {
ayes: ayes.length,
threshold: threshold.toString()
}
})}</h4>
{ayes.map((address, index): React.ReactNode => (
<Labelled
key={`${index}:${address}`}
@ -113,16 +62,11 @@ class Motion extends React.PureComponent<Props, State> {
/>
</Labelled>
))}
<h4>
{t(
'nays ({{nays}})',
{
replace: {
nays: nays.length
}
}
)}
</h4>
<h4>{t('nays ({{nays}})', {
replace: {
nays: nays.length
}
})}</h4>
{nays.map((address, index): React.ReactNode => (
<Labelled
key={`${index}:${address}`}
@ -136,31 +80,25 @@ class Motion extends React.PureComponent<Props, State> {
</Labelled>
))}
</div>
);
}
</ActionItem>
);
}
export default withMulti(
Motion,
translate,
withCalls<Props>(
[
'query.council.proposalOf',
{
paramName: 'hash',
propName: 'proposal',
transform: (value: Option<ProposalType>): ProposalType | null =>
value.unwrapOr(null)
}
],
[
'query.council.voting',
{
paramName: 'hash',
propName: 'votes',
transform: (value: Option<Votes>): Votes | null =>
value.unwrapOr(null)
}
]
['query.council.proposalOf', {
paramName: 'hash',
propName: 'proposal',
transform: (value: Option<ProposalType>): ProposalType | null =>
value.unwrapOr(null)
}],
['query.council.voting', {
paramName: 'hash',
propName: 'votes',
transform: (value: Option<Votes>): Votes | null =>
value.unwrapOr(null)
}]
)
);

View File

@ -8,7 +8,7 @@ import { I18nProps } from '@polkadot/react-components/types';
import { ReferendumInfoExtended } from '@polkadot/api-derive/type';
import BN from 'bn.js';
import React from 'react';
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';
@ -37,91 +37,90 @@ interface State {
votedTotal: BN;
}
class Referendum extends React.PureComponent<Props, State> {
public state: 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>({
voteCount: 0,
voteCountAye: 0,
voteCountNay: 0,
votedAye: new BN(0),
votedNay: new BN(0),
votedTotal: new BN(0)
};
});
public static getDerivedStateFromProps ({ democracy_referendumVotesFor }: Props, prevState: State): State | null {
if (!democracy_referendumVotesFor) {
return null;
}
useEffect((): void => {
if (democracy_referendumVotesFor) {
const newState: State = democracy_referendumVotesFor.reduce((state, { balance, vote }): State => {
if (vote.isAye) {
state.voteCountAye++;
state.votedAye = state.votedAye.add(balance);
} else {
state.voteCountNay++;
state.votedNay = state.votedNay.add(balance);
}
const newState: State = democracy_referendumVotesFor.reduce((state, { balance, vote }): State => {
if (vote.isAye) {
state.voteCountAye++;
state.votedAye = state.votedAye.add(balance);
} else {
state.voteCountNay++;
state.votedNay = state.votedNay.add(balance);
state.voteCount++;
state.votedTotal = state.votedTotal.add(balance);
return state;
}, {
voteCount: 0,
voteCountAye: 0,
voteCountNay: 0,
votedAye: new BN(0),
votedNay: new BN(0),
votedTotal: new BN(0)
});
if (newState.votedAye.eq(votedNay) && newState.votedNay.eq(votedNay)) {
return;
}
state.voteCount++;
state.votedTotal = state.votedTotal.add(balance);
return state;
}, {
voteCount: 0,
voteCountAye: 0,
voteCountNay: 0,
votedAye: new BN(0),
votedNay: new BN(0),
votedTotal: new BN(0)
});
if (newState.votedAye.eq(prevState.votedNay) && newState.votedNay.eq(prevState.votedNay)) {
return null;
setState(newState);
}
}, [democracy_referendumVotesFor]);
return newState;
if (!chain_bestNumber || value.end.sub(chain_bestNumber).lten(0)) {
return null;
}
public render (): React.ReactNode {
const { chain_bestNumber, className, value } = this.props;
const enactBlock = (democracy_enactmentPeriod || new BN(0)).add(value.end);
if (!chain_bestNumber || value.end.sub(chain_bestNumber).lten(0)) {
return null;
}
return (
<ActionItem
className={className}
idNumber={value.index}
proposal={value.proposal}
accessory={
<Voting
idNumber={value.index}
proposal={value.proposal}
/>
}
>
{this.renderInfo()}
</ActionItem>
);
}
private renderInfo (): React.ReactNode {
const { chain_bestNumber, democracy_enactmentPeriod, t, value: { end, threshold } } = this.props;
if (!chain_bestNumber) {
return null;
}
const enactBlock = (democracy_enactmentPeriod || new BN(0)).add(end);
return (
return (
<ActionItem
className={className}
idNumber={value.index}
proposal={value.proposal}
accessory={
<Voting
idNumber={value.index}
proposal={value.proposal}
/>
}
>
<div>
{this.renderResults()}
{voteCount !== 0 && votedTotal.gtn(0) && (
<div className='democracy--Referendum-results chart'>
<Chart.HorizBar
values={[
{
colors: COLORS_AYE,
label: `Aye, ${formatBalance(votedAye)} (${formatNumber(voteCountAye)})`,
value: votedAye.muln(10000).div(votedTotal).toNumber() / 100
},
{
colors: COLORS_NAY,
label: `Nay, ${formatBalance(votedNay)} (${formatNumber(voteCountNay)})`,
value: votedNay.muln(10000).div(votedTotal).toNumber() / 100
}
]}
/>
</div>
)}
<Static label={t('ending at')}>
{t('block #{{blockNumber}}, {{remaining}} blocks remaining', {
replace: {
blockNumber: formatNumber(end),
remaining: formatNumber(end.sub(chain_bestNumber).subn(1))
blockNumber: formatNumber(value.end),
remaining: formatNumber(value.end.sub(chain_bestNumber).subn(1))
}
})}
</Static>
@ -135,7 +134,7 @@ class Referendum extends React.PureComponent<Props, State> {
<VoteThreshold
isDisabled
isOptional={false}
defaultValue={{ isValid: true, value: threshold }}
defaultValue={{ isValid: true, value: value.threshold }}
label={t('vote threshold')}
name='voteThreshold'
type={{
@ -144,39 +143,12 @@ class Referendum extends React.PureComponent<Props, State> {
}}
/>
</div>
);
}
private renderResults (): React.ReactNode {
const { voteCount, voteCountAye, voteCountNay, votedAye, votedNay, votedTotal } = this.state;
if (voteCount === 0 || votedTotal.eqn(0)) {
return null;
}
return (
<div className='democracy--Referendum-results chart'>
<Chart.HorizBar
values={[
{
colors: COLORS_AYE,
label: `Aye, ${formatBalance(votedAye)} (${formatNumber(voteCountAye)})`,
value: votedAye.muln(10000).div(votedTotal).toNumber() / 100
},
{
colors: COLORS_NAY,
label: `Nay, ${formatBalance(votedNay)} (${formatNumber(voteCountNay)})`,
value: votedNay.muln(10000).div(votedTotal).toNumber() / 100
}
]}
/>
</div>
);
}
</ActionItem>
);
}
export default withMulti(
styled(Referendum as React.ComponentClass<Props>)`
styled(Referendum)`
.democracy--Referendum-results {
margin-bottom: 1em;

View File

@ -7,7 +7,7 @@ import { I18nProps } from '@polkadot/react-components/types';
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import BN from 'bn.js';
import React from 'react';
import React, { useState } from 'react';
import { Button, InputAddress, Modal, TxButton } from '@polkadot/react-components';
import accountObservable from '@polkadot/ui-keyring/observable/accounts';
import { withMulti, withObservable } from '@polkadot/react-api';
@ -20,99 +20,69 @@ interface Props extends I18nProps {
proposalId: BN | number;
}
interface State {
accountId?: string;
isSecondingOpen: boolean;
}
function Seconding ({ allAccounts, depositors, proposalId, t }: Props): React.ReactElement<Props> | null {
const [accountId, setAccountId] = useState<string | null>(null);
const [isSecondingOpen, setIsSecondingOpen] = useState(false);
const hasAccounts = allAccounts && Object.keys(allAccounts).length !== 0;
class Seconding extends React.PureComponent<Props, State> {
public state: State = {
isSecondingOpen: false
};
public render (): React.ReactNode {
const { allAccounts, t } = this.props;
const hasAccounts = allAccounts && Object.keys(allAccounts).length !== 0;
if (!hasAccounts) {
return null;
}
return (
<>
{this.renderModal()}
<div className='ui--Row-buttons'>
<Button
isPrimary
label={t('Second proposal')}
labelIcon='toggle off'
onClick={this.toggleSeconding}
/>
</div>
</>
);
if (!hasAccounts) {
return null;
}
private renderModal (): React.ReactNode {
const { depositors, proposalId, t } = this.props;
const { accountId, isSecondingOpen } = this.state;
const isDepositor = depositors.some((depositor): boolean => depositor.eq(accountId));
const _toggleSeconding = (): void => setIsSecondingOpen(!isSecondingOpen);
if (!isSecondingOpen) {
return null;
}
const isDepositor = !!depositors.find((depositor): boolean => depositor.eq(accountId));
return (
<Modal
dimmer='inverted'
open
size='small'
>
<Modal.Header>{t('Second proposal')}</Modal.Header>
<Modal.Content>
<InputAddress
help={t('Select the account you wish to second with. This will lock your funds until the proposal is either approved or rejected')}
label={t('second with account')}
onChange={this.onChangeAccount}
type='account'
withLabel
/>
</Modal.Content>
<Modal.Actions>
<Button.Group>
<Button
isNegative
onClick={this.toggleSeconding}
label={t('Cancel')}
labelIcon='cancel'
return (
<>
{isSecondingOpen && (
<Modal
dimmer='inverted'
open
size='small'
>
<Modal.Header>{t('Second proposal')}</Modal.Header>
<Modal.Content>
<InputAddress
help={t('Select the account you wish to second with. This will lock your funds until the proposal is either approved or rejected')}
label={t('second with account')}
onChange={setAccountId}
type='account'
withLabel
/>
<Button.Or />
<TxButton
accountId={accountId}
isDisabled={!accountId || isDepositor}
isPrimary
label={t('Second')}
labelIcon='sign-in'
onClick={this.toggleSeconding}
params={[proposalId]}
tx='democracy.second'
/>
</Button.Group>
</Modal.Actions>
</Modal>
);
}
private onChangeAccount = (accountId?: string): void => {
this.setState({ accountId });
}
private toggleSeconding = (): void => {
this.setState(({ isSecondingOpen }): State => ({
isSecondingOpen: !isSecondingOpen
}));
}
</Modal.Content>
<Modal.Actions>
<Button.Group>
<Button
isNegative
onClick={_toggleSeconding}
label={t('Cancel')}
labelIcon='cancel'
/>
<Button.Or />
<TxButton
accountId={accountId}
isDisabled={!accountId || isDepositor}
isPrimary
label={t('Second')}
labelIcon='sign-in'
onClick={_toggleSeconding}
params={[proposalId]}
tx='democracy.second'
/>
</Button.Group>
</Modal.Actions>
</Modal>
)}
<div className='ui--Row-buttons'>
<Button
isPrimary
label={t('Second proposal')}
labelIcon='toggle off'
onClick={_toggleSeconding}
/>
</div>
</>
);
}
export default withMulti(

View File

@ -6,7 +6,7 @@ import { I18nProps } from '@polkadot/react-components/types';
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import BN from 'bn.js';
import React from 'react';
import React, { useState } from 'react';
import { Button, Dropdown, InputAddress, Modal, TxButton } from '@polkadot/react-components';
import accountObservable from '@polkadot/ui-keyring/observable/accounts';
import { withMulti, withObservable } from '@polkadot/react-api';
@ -18,123 +18,79 @@ interface Props extends I18nProps {
referendumId: BN | number;
}
interface State {
accountId?: string;
isVotingOpen: boolean;
voteOptions: { text: React.ReactNode; value: boolean }[];
voteValue: boolean;
}
function Voting ({ allAccounts, referendumId, t }: Props): React.ReactElement<Props> | null {
const [accountId, setAccountId] = useState<string | null>(null);
const [isVotingOpen, setIsVotingOpen] = useState(false);
const [voteValue, setVoteValue] = useState(true);
const hasAccounts = allAccounts && Object.keys(allAccounts).length !== 0;
class Voting extends React.PureComponent<Props, State> {
public state: State;
public constructor (props: Props) {
super(props);
const { t } = props;
this.state = {
isVotingOpen: false,
voteOptions: [
{ text: t('Aye, I approve'), value: true },
{ text: t('Nay, I do not approve'), value: false }
],
voteValue: true
};
if (!hasAccounts) {
return null;
}
public render (): React.ReactNode {
const { allAccounts, t } = this.props;
const hasAccounts = allAccounts && Object.keys(allAccounts).length !== 0;
const _toggleVoting = (): void => setIsVotingOpen(!isVotingOpen);
if (!hasAccounts) {
return null;
}
return (
<>
{this.renderModal()}
<div className='ui--Row-buttons'>
<Button
isPrimary
label={t('Vote')}
labelIcon='check'
onClick={this.toggleVoting}
/>
</div>
</>
);
}
private renderModal (): React.ReactNode {
const { referendumId, t } = this.props;
const { accountId, isVotingOpen, voteOptions, voteValue } = this.state;
if (!isVotingOpen) {
return null;
}
return (
<Modal
dimmer='inverted'
open
size='small'
>
<Modal.Header>{t('Vote on proposal')}</Modal.Header>
<Modal.Content>
<InputAddress
help={t('Select the account you wish to vote with. You can approve "aye" or deny "nay" the proposal.')}
label={t('vote with account')}
onChange={this.onChangeAccount}
type='account'
withLabel
/>
<Dropdown
help={t('Select your vote preferences for this proposal, either to approve or disapprove')}
label={t('record my vote as')}
options={voteOptions}
onChange={this.onChangeVote}
value={voteValue}
/>
</Modal.Content>
<Modal.Actions>
<Button.Group>
<Button
isNegative
onClick={this.toggleVoting}
label={t('Cancel')}
labelIcon='cancel'
return (
<>
{isVotingOpen && (
<Modal
dimmer='inverted'
open
size='small'
>
<Modal.Header>{t('Vote on proposal')}</Modal.Header>
<Modal.Content>
<InputAddress
help={t('Select the account you wish to vote with. You can approve "aye" or deny "nay" the proposal.')}
label={t('vote with account')}
onChange={setAccountId}
type='account'
withLabel
/>
<Button.Or />
<TxButton
accountId={accountId}
isDisabled={!accountId}
isPrimary
label={t('Vote')}
labelIcon='check'
onClick={this.toggleVoting}
params={[referendumId, voteValue]}
tx='democracy.vote'
<Dropdown
help={t('Select your vote preferences for this proposal, either to approve or disapprove')}
label={t('record my vote as')}
options={[
{ text: t('Aye, I approve'), value: true },
{ text: t('Nay, I do not approve'), value: false }
]}
onChange={setVoteValue}
value={voteValue}
/>
</Button.Group>
</Modal.Actions>
</Modal>
);
}
private onChangeAccount = (accountId?: string): void => {
this.setState({ accountId });
}
private onChangeVote = (voteValue: boolean): void => {
this.setState({ voteValue });
}
private toggleVoting = (): void => {
this.setState(({ isVotingOpen }): Pick<State, never> => ({
isVotingOpen: !isVotingOpen
}));
}
</Modal.Content>
<Modal.Actions>
<Button.Group>
<Button
isNegative
onClick={_toggleVoting}
label={t('Cancel')}
labelIcon='cancel'
/>
<Button.Or />
<TxButton
accountId={accountId}
isDisabled={!accountId}
isPrimary
label={t('Vote')}
labelIcon='check'
onClick={_toggleVoting}
params={[referendumId, voteValue]}
tx='democracy.vote'
/>
</Button.Group>
</Modal.Actions>
</Modal>
)}
<div className='ui--Row-buttons'>
<Button
isPrimary
label={t('Vote')}
labelIcon='check'
onClick={_toggleVoting}
/>
</div>
</>
);
}
export default withMulti(

View File

@ -61,6 +61,155 @@ interface Row {
cols: Col[];
}
// adjust the number of columns in a cell based on the children and tree depth
function calcWidth (children: LinkArray): number {
return Math.max(1, children.reduce((total, { hdr: { width } }): number => {
return total + width;
}, 0));
}
// counts the height of a specific node
function calcHeight (children: LinkArray): number {
return children.reduce((max, { hdr, arr }): number => {
hdr.height = hdr.isEmpty
? 0
: 1 + calcHeight(arr);
return Math.max(max, hdr.height);
}, 0);
}
// a single column in a row, it just has the details for the entry
function createCol ({ hdr: { author, hash, isEmpty, isFinalized, parent, width } }: Link): Col {
return { author, hash, isEmpty, isFinalized, parent, width };
}
// create a simplified structure that allows for easy rendering
function createRows (arr: LinkArray): Row[] {
if (!arr.length) {
return [];
}
return createRows(
arr.reduce((children: LinkArray, { arr }: Link): LinkArray =>
children.concat(...arr), [])
).concat({
bn: arr.reduce((result, { hdr: { bn } }): string =>
result || bn, ''),
cols: arr.map(createCol)
});
}
// fills in a header based on the supplied data
function createHdr (bn: string, hash: string, parent: string, author: string | null, isEmpty = false): LinkHeader {
return { author, bn, hash, height: 0, isEmpty, isFinalized: false, parent, width: 0 };
}
// empty link helper
function createLink (): Link {
return {
arr: [],
hdr: createHdr('', ' ', ' ', null, true)
};
}
// even out the columns, i.e. add empty spacers as applicable to get tree rendering right
function addColumnSpacers (arr: LinkArray): void {
// check is any of the children has a non-empty set
const hasChildren = arr.some(({ arr }): boolean => arr.length !== 0);
if (hasChildren) {
// ok, non-empty found - iterate through an add at least an empty cell to all
arr
.filter(({ arr }): boolean => arr.length === 0)
.forEach(({ arr }): number => arr.push(createLink()));
const newArr = arr.reduce((flat: LinkArray, { arr }): LinkArray => flat.concat(...arr), []);
// go one level deeper, ensure that the full tree has empty spacers
addColumnSpacers(newArr);
}
}
// checks to see if a row has a single non-empty entry, i.e. it is a candidate for collapsing
function isSingleRow (cols: Col[]): boolean {
if (!cols[0] || cols[0].isEmpty) {
return false;
}
return cols.reduce((result: boolean, col, index): boolean => {
return index === 0
? result
: (!col.isEmpty ? false : result);
}, true);
}
function renderCol ({ author, hash, isEmpty, isFinalized, parent, width }: Col, index: number): React.ReactNode {
return (
<td
className={`header ${isEmpty && 'isEmpty'} ${isFinalized && 'isFinalized'}`}
colSpan={width}
key={`${hash}:${index}:${width}`}
>
{
isEmpty
? <div className='empty' />
: (
<>
{author && (
<IdentityIcon
className='author'
size={28}
value={author}
/>
)}
<div className='contents'>
<div className='hash'>{hash}</div>
<div className='parent'>{parent}</div>
</div>
</>
)
}
</td>
);
}
// render the rows created by createRows to React nodes
function renderRows (rows: Row[]): React.ReactNode[] {
const lastIndex = rows.length - 1;
let isPrevShort = false;
return rows.map(({ bn, cols }, index): React.ReactNode => {
// if not first, not last and single only, see if we can collapse
if (index !== 0 && index !== lastIndex && isSingleRow(cols)) {
if (isPrevShort) {
// previous one was already a link, this one as well - skip it
return null;
} else if (isSingleRow(rows[index - 1].cols)) {
isPrevShort = true;
return (
<tr key={bn}>
<td key='blockNumber' />
<td className='header isLink' colSpan={cols[0].width}>
<div className='link'>&#8942;</div>
</td>
</tr>
);
}
}
isPrevShort = false;
return (
<tr key={bn}>
<td key='blockNumber'>{`#${bn}`}</td>
{cols.map(renderCol)}
</tr>
);
});
}
class Forks extends React.PureComponent<Props, State> {
public state: State = {
numBlocks: 0,
@ -116,7 +265,7 @@ class Forks extends React.PureComponent<Props, State> {
// add to the header map
// also for HeaderExtended header.author ? header.author.toString() : null
this._headers.set(hash, this.createHdr(bn, hash, parent, null));
this._headers.set(hash, createHdr(bn, hash, parent, null));
// check to see if the children already has a entry
if (this._children.has(parent)) {
@ -186,146 +335,13 @@ class Forks extends React.PureComponent<Props, State> {
</SummaryBox>
<table>
<tbody>
{this.renderRows(this.createRows(tree.arr))}
{renderRows(createRows(tree.arr))}
</tbody>
</table>
</div>
);
}
// create a simplified structure that allows for easy rendering
private createRows (arr: LinkArray): Row[] {
if (!arr.length) {
return [];
}
return this
.createRows(
arr.reduce((children: LinkArray, { arr }: Link): LinkArray =>
children.concat(...arr), [])
)
.concat({
bn: arr.reduce((result, { hdr: { bn } }): string =>
result || bn, ''),
cols: arr.map(this.createCol)
});
}
// a single column in a row, it just has the details for the entry
private createCol = ({ hdr: { author, hash, isEmpty, isFinalized, parent, width } }: Link): Col => {
return { author, hash, isEmpty, isFinalized, parent, width };
}
// checks to see if a row has a single non-empty entry, i.e. it is a candidate for collapsing
private isSingleRow (cols: Col[]): boolean {
if (!cols[0] || cols[0].isEmpty) {
return false;
}
return cols.reduce((result: boolean, col, index): boolean => {
return index === 0
? result
: (!col.isEmpty ? false : result);
}, true);
}
// render the rows created by createRows to React nodes
private renderRows (rows: Row[]): React.ReactNode[] {
const lastIndex = rows.length - 1;
let isPrevShort = false;
return rows.map(({ bn, cols }, index): React.ReactNode => {
// if not first, not last and single only, see if we can collapse
if (index !== 0 && index !== lastIndex && this.isSingleRow(cols)) {
if (isPrevShort) {
// previous one was already a link, this one as well - skip it
return null;
} else if (this.isSingleRow(rows[index - 1].cols)) {
isPrevShort = true;
return (
<tr key={bn}>
<td key='blockNumber' />
<td className='header isLink' colSpan={cols[0].width}>
<div className='link'>&#8942;</div>
</td>
</tr>
);
}
}
isPrevShort = false;
return (
<tr key={bn}>
<td key='blockNumber'>{`#${bn}`}</td>
{cols.map(this.renderCol)}
</tr>
);
});
}
private renderCol = ({ author, hash, isEmpty, isFinalized, parent, width }: Col, index: number): React.ReactNode => {
return (
<td
className={`header ${isEmpty && 'isEmpty'} ${isFinalized && 'isFinalized'}`}
colSpan={width}
key={`${hash}:${index}:${width}`}
>
{
isEmpty
? <div className='empty' />
: (
<>
{author && (
<IdentityIcon
className='author'
size={28}
value={author}
/>
)}
<div className='contents'>
<div className='hash'>{hash}</div>
<div className='parent'>{parent}</div>
</div>
</>
)
}
</td>
);
}
// adjust the number of columns in a cell based on the children and tree depth
private calcWidth (children: LinkArray): number {
return Math.max(1, children.reduce((total, { hdr: { width } }): number => {
return total + width;
}, 0));
}
// counts the height of a specific node
private calcHeight (children: LinkArray): number {
return children.reduce((max, { hdr, arr }): number => {
hdr.height = hdr.isEmpty
? 0
: 1 + this.calcHeight(arr);
return Math.max(max, hdr.height);
}, 0);
}
// fills in a header based on the supplied data
private createHdr (bn: string, hash: string, parent: string, author: string | null, isEmpty = false): LinkHeader {
return { author, bn, hash, height: 0, isEmpty, isFinalized: false, parent, width: 0 };
}
// empty link helper
private createLink (): Link {
return {
arr: [],
hdr: this.createHdr('', ' ', ' ', null, true)
};
}
// adds children for a specific header, retrieving based on matching parent
private addChildren (base: LinkHeader, children: LinkArray): LinkArray {
const hdrs = (this._children.get(base.hash) || [])
@ -337,8 +353,8 @@ class Forks extends React.PureComponent<Props, State> {
});
// caclulate the max height/width for this entry
base.height = this.calcHeight(children);
base.width = this.calcWidth(children);
base.height = calcHeight(children);
base.width = calcWidth(children);
// place the active (larger, finalized) columns first for the pyramid display
children.sort((a, b): number => {
@ -354,27 +370,9 @@ class Forks extends React.PureComponent<Props, State> {
return children;
}
// even out the columns, i.e. add empty spacers as applicable to get tree rendering right
private addColumnSpacers (arr: LinkArray): void {
// check is any of the children has a non-empty set
const hasChildren = arr.some(({ arr }): boolean => arr.length !== 0);
if (hasChildren) {
// ok, non-empty found - iterate through an add at least an empty cell to all
arr
.filter(({ arr }): boolean => arr.length === 0)
.forEach(({ arr }): number => arr.push(this.createLink()));
const newArr = arr.reduce((flat: LinkArray, { arr }): LinkArray => flat.concat(...arr), []);
// go one level deeper, ensure that the full tree has empty spacers
this.addColumnSpacers(newArr);
}
}
// create a tree list from the available headers
private generateTree (): Link {
const root = this.createLink();
const root = createLink();
// add all the root entries first, we iterate from these
// We add the root entry explicitly, it exists as per init
@ -393,10 +391,10 @@ class Forks extends React.PureComponent<Props, State> {
});
// align the columns with empty spacers - this aids in display
this.addColumnSpacers(root.arr);
addColumnSpacers(root.arr);
root.hdr.height = this.calcHeight(root.arr);
root.hdr.width = this.calcWidth(root.arr);
root.hdr.height = calcHeight(root.arr);
root.hdr.width = calcWidth(root.arr);
return root;
}

View File

@ -5,7 +5,7 @@
import { I18nProps } from '@polkadot/react-components/types';
import BN from 'bn.js';
import React from 'react';
import React, { useState } from 'react';
import styled from 'styled-components';
import { InputAddress, Labelled } from '@polkadot/react-components';
import { Nonce } from '@polkadot/react-query';
@ -18,84 +18,50 @@ interface Props extends I18nProps {
onChange: (accountId: string | undefined | null, accountNonce: BN) => void;
}
interface State {
accountNonce: BN;
accountId?: string | null;
}
function Account ({ className, defaultValue, isError, onChange, t }: Props): React.ReactElement<Props> {
const [accountId, setAccountId] = useState<string | null | undefined>(defaultValue);
const [accountNonce, setAccountNonce] = useState(new BN(0));
class Account extends React.PureComponent<Props, State> {
public state: State;
const _onChangeAccountId = (accountId: string): void => {
setAccountId(accountId);
onChange(accountId, accountNonce);
};
const _onChangeAccountNonce = (accountNonce: BN): void => {
setAccountNonce(accountNonce);
onChange(accountId, accountNonce);
};
public constructor (props: Props) {
super(props);
this.state = {
accountId: props.defaultValue,
accountNonce: new BN(0)
};
}
public render (): React.ReactNode {
const { className, defaultValue, isError, t } = this.props;
return (
<div className={`ui--row ${className}`}>
<div className='large'>
<InputAddress
defaultValue={defaultValue}
isError={isError}
label={t('sign data from account')}
onChange={this.onChangeAccount}
placeholder='0x...'
type='account'
/>
</div>
{this.renderNonce()}
</div>
);
}
public renderNonce (): React.ReactNode {
const { t } = this.props;
const { accountId } = this.state;
if (!accountId) {
return null;
}
return (
<Labelled
className='small'
label={t('with an index of')}
>
<Nonce
className='ui disabled dropdown selection'
callOnResult={this.onChangeNonce}
params={accountId}
return (
<div className={`ui--row ${className}`}>
<div className='large'>
<InputAddress
defaultValue={defaultValue}
isError={isError}
label={t('sign data from account')}
onChange={_onChangeAccountId}
placeholder='0x...'
type='account'
/>
</Labelled>
);
}
private onChangeAccount = (accountId: string): void => {
const { onChange } = this.props;
this.setState({ accountId }, (): void =>
onChange(accountId, this.state.accountNonce)
);
}
private onChangeNonce = (_accountNonce: BN): void => {
const { onChange } = this.props;
const accountNonce = _accountNonce || new BN(0);
this.setState({ accountNonce }, (): void =>
onChange(this.state.accountId, accountNonce)
);
}
</div>
{accountId && (
<Labelled
className='small'
label={t('with an index of')}
>
<Nonce
className='ui disabled dropdown selection'
callOnResult={_onChangeAccountNonce}
params={accountId}
/>
</Labelled>
)}
</div>
);
}
export default translate(styled(Account)`
box-sizing: border-box;
padding-left: 2em;
`);
export default translate(
styled(Account)`
box-sizing: border-box;
padding-left: 2em;
`
);

View File

@ -7,7 +7,7 @@ import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import React from 'react';
import { Route, Switch } from 'react-router';
import Tabs, { TabItem } from '@polkadot/react-components/Tabs';
import Tabs from '@polkadot/react-components/Tabs';
import accountObservable from '@polkadot/ui-keyring/observable/accounts';
import { withMulti, withObservable } from '@polkadot/react-api';
@ -21,64 +21,47 @@ interface Props extends AppProps, I18nProps {
allAccounts?: SubjectInfo;
}
interface State {
tabs: TabItem[];
}
function ToolboxApp ({ allAccounts, basePath, t }: Props): React.ReactElement<Props> {
const hasAccounts = allAccounts && Object.keys(allAccounts).length !== 0;
const tabs = [
{
isRoot: true,
name: 'rpc',
text: t('RPC calls')
},
{
name: 'hash',
text: t('Hash data')
},
{
name: 'sign',
text: t('Sign message')
},
{
name: 'verify',
text: t('Verify signature')
}
];
const filteredTabs = hasAccounts
? tabs
: tabs.filter(({ name }): boolean => !['sign', 'verify'].includes(name));
class ToolboxApp extends React.PureComponent<Props, State> {
public constructor (props: Props) {
super(props);
const { t } = this.props;
this.state = {
tabs: [
{
isRoot: true,
name: 'rpc',
text: t('RPC calls')
},
{
name: 'hash',
text: t('Hash data')
},
{
name: 'sign',
text: t('Sign message')
},
{
name: 'verify',
text: t('Verify signature')
}
]
};
}
public render (): React.ReactNode {
const { allAccounts, basePath } = this.props;
const { tabs } = this.state;
const hasAccounts = allAccounts && Object.keys(allAccounts).length !== 0;
const filteredTabs = hasAccounts
? tabs
: tabs.filter(({ name }): boolean => !['sign', 'verify'].includes(name));
return (
<main className='toolbox--App'>
<header>
<Tabs
basePath={basePath}
items={filteredTabs}
/>
</header>
<Switch>
<Route path={`${basePath}/hash`} component={Hash} />
<Route path={`${basePath}/sign`} component={Sign} />
<Route path={`${basePath}/verify`} component={Verify} />
<Route component={Rpc} />
</Switch>
</main>
);
}
return (
<main className='toolbox--App'>
<header>
<Tabs
basePath={basePath}
items={filteredTabs}
/>
</header>
<Switch>
<Route path={`${basePath}/hash`} component={Hash} />
<Route path={`${basePath}/sign`} component={Sign} />
<Route path={`${basePath}/verify`} component={Verify} />
<Route component={Rpc} />
</Switch>
</main>
);
}
export default withMulti(

View File

@ -13,25 +13,15 @@ import Propose from './Propose';
interface Props extends AppProps, BareProps, I18nProps {}
interface State {
isProposeOpen: boolean;
}
export default class Overview extends React.PureComponent<Props, State> {
public state: State = {
isProposeOpen: false
};
public render (): React.ReactNode {
return (
<>
<Summary />
<Propose />
<Columar>
<Proposals />
<Approvals />
</Columar>
</>
);
}
export default function Overview (): React.ReactElement<Props> {
return (
<>
<Summary />
<Propose />
<Columar>
<Proposals />
<Approvals />
</Columar>
</>
);
}