Combine AddressSummary & Row (#1260)
* Combine AddressSummary & Row * Cleanup address rendering * Tiny tags cleanups * Remove unused ui--AddressSummary overrides * Column component * Columar * Cleanups * Remove hot client * Backport data padding
This commit is contained in:
parent
ba320b4d01
commit
f887f68cfb
@ -2,11 +2,6 @@
|
||||
/* This software may be modified and distributed under the terms
|
||||
/* of the Apache-2.0 license. See the LICENSE file for details. */
|
||||
|
||||
.app--accounts-Modal .ui--AddressSummary {
|
||||
display: inline-block;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.accounts--Creator-advanced {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,7 +149,7 @@ class Address extends React.PureComponent<Props, State> {
|
||||
icon='trash'
|
||||
key='forget'
|
||||
size='small'
|
||||
tooltip={t('Forget this account')}
|
||||
tooltip={t('Forget this address')}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -2,19 +2,12 @@
|
||||
/* This software may be modified and distributed under the terms
|
||||
/* of the Apache-2.0 license. See the LICENSE file for details. */
|
||||
|
||||
@keyframes fadein {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
header > .explorer--BlockHeader {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.explorer--BlockHeader {
|
||||
animation: fadein 1s;
|
||||
|
||||
.author {
|
||||
font-size: 1rem;
|
||||
text-align: right;
|
||||
@ -25,15 +18,6 @@ header > .explorer--BlockHeader {
|
||||
}
|
||||
}
|
||||
|
||||
.header-outer {
|
||||
padding: 0.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header-outer + details {
|
||||
margin-top: -0.5rem;
|
||||
}
|
||||
|
||||
.header {
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
@ -74,6 +58,7 @@ header > .explorer--BlockHeader {
|
||||
|
||||
.contains {
|
||||
border: 0;
|
||||
margin-top: 0.5rem;
|
||||
text-align: center;
|
||||
|
||||
> .info {
|
||||
@ -89,7 +74,7 @@ header > .explorer--BlockHeader {
|
||||
> label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
||||
> span,
|
||||
> label {
|
||||
vertical-align: middle;
|
||||
|
||||
@ -43,18 +43,14 @@ class BlockHeaders extends React.PureComponent<Props> {
|
||||
render () {
|
||||
const { headers = [] } = this.props;
|
||||
|
||||
return (
|
||||
<div className='explorer--BlockHeaders'>
|
||||
{headers.map((header, index) => (
|
||||
<BlockHeader
|
||||
isSummary={!!index}
|
||||
key={header.blockNumber.toString()}
|
||||
value={header}
|
||||
withLink={!header.blockNumber.isZero()}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
return headers.map((header, index) => (
|
||||
<BlockHeader
|
||||
isSummary={!!index}
|
||||
key={header.blockNumber.toString()}
|
||||
value={header}
|
||||
withLink={!header.blockNumber.isZero()}
|
||||
/>
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ import React from 'react';
|
||||
import { HeaderExtended } from '@polkadot/api-derive';
|
||||
import { EventRecord, SignedBlock } from '@polkadot/types';
|
||||
import { withCalls } from '@polkadot/ui-api';
|
||||
import { Columar } from '@polkadot/ui-app';
|
||||
|
||||
import BlockHeader from '../BlockHeader';
|
||||
import translate from '../translate';
|
||||
@ -39,9 +40,11 @@ class BlockByHash extends React.PureComponent<Props> {
|
||||
withExplorer
|
||||
/>
|
||||
</header>
|
||||
<Extrinsics value={chain_getBlock.block.extrinsics} />
|
||||
<Events value={system_events} />
|
||||
<Logs value={chain_getHeader.digest.logs} />
|
||||
<Columar>
|
||||
<Extrinsics value={chain_getBlock.block.extrinsics} />
|
||||
<Events value={system_events} />
|
||||
<Logs value={chain_getHeader.digest.logs} />
|
||||
</Columar>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import { I18nProps } from '@polkadot/ui-app/types';
|
||||
|
||||
import React from 'react';
|
||||
import { EventRecord } from '@polkadot/types';
|
||||
import { Column } from '@polkadot/ui-app';
|
||||
|
||||
import EventsDisplay from '../Events';
|
||||
import translate from '../translate';
|
||||
@ -27,15 +28,12 @@ class Events extends React.PureComponent<Props> {
|
||||
}));
|
||||
|
||||
return (
|
||||
<section>
|
||||
<h1>{t('events')}</h1>
|
||||
<div className='explorer--BlockByHash-flexable ui--flex-medium'>
|
||||
<EventsDisplay
|
||||
eventClassName='explorer--BlockByHash-block'
|
||||
events={events}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
<Column header={t('events')}>
|
||||
<EventsDisplay
|
||||
eventClassName='explorer--BlockByHash-block'
|
||||
events={events}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
import { I18nProps } from '@polkadot/ui-app/types';
|
||||
|
||||
import React from 'react';
|
||||
import { AddressMini, Call, LinkPolkascan } from '@polkadot/ui-app';
|
||||
import { AddressMini, Call, Column, LinkPolkascan } from '@polkadot/ui-app';
|
||||
import { formatNumber } from '@polkadot/util';
|
||||
import { Extrinsic, Method } from '@polkadot/types';
|
||||
|
||||
@ -21,10 +21,9 @@ class Extrinsics extends React.PureComponent<Props> {
|
||||
const { label, t } = this.props;
|
||||
|
||||
return (
|
||||
<section key='extrinsics'>
|
||||
<h1>{label || t('extrinsics')}</h1>
|
||||
<Column header={label || t('extrinsics')}>
|
||||
{this.renderContent()}
|
||||
</section>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
@ -33,17 +32,13 @@ class Extrinsics extends React.PureComponent<Props> {
|
||||
|
||||
if (!value || !value.length) {
|
||||
return (
|
||||
<div className='ui disabled'>
|
||||
<article>
|
||||
{t('no pending extrinsics are in the queue')}
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='explorer--BlockByHash-flexable ui--flex-medium'>
|
||||
{value.map(this.renderExtrinsic)}
|
||||
</div>
|
||||
);
|
||||
return value.map(this.renderExtrinsic);
|
||||
}
|
||||
|
||||
// FIXME This is _very_ similar to what we have in democracy/Item
|
||||
@ -51,36 +46,31 @@ class Extrinsics extends React.PureComponent<Props> {
|
||||
const { meta, method, section } = Method.findFunction(extrinsic.callIndex);
|
||||
|
||||
return (
|
||||
<div
|
||||
className='explorer--BlockByHash-block'
|
||||
key={`extrinsic:${index}`}
|
||||
>
|
||||
<article className='explorer--Container'>
|
||||
<div className='header'>
|
||||
<h3>
|
||||
{section}.{method} (#{formatNumber(index)})
|
||||
</h3>
|
||||
{this.renderSigner(extrinsic)}
|
||||
</div>
|
||||
<details>
|
||||
<summary>{
|
||||
meta && meta.documentation
|
||||
? meta.documentation.join(' ')
|
||||
: 'Details'
|
||||
}</summary>
|
||||
<Call
|
||||
className='details'
|
||||
value={extrinsic}
|
||||
withHash
|
||||
/>
|
||||
</details>
|
||||
{
|
||||
extrinsic.isSigned
|
||||
? <LinkPolkascan data={extrinsic.hash.toHex()} type='extrinsic' />
|
||||
: null
|
||||
}
|
||||
</article>
|
||||
</div>
|
||||
<article key={`extrinsic:${index}`}>
|
||||
<div className='header'>
|
||||
<h3>
|
||||
{section}.{method} (#{formatNumber(index)})
|
||||
</h3>
|
||||
{this.renderSigner(extrinsic)}
|
||||
</div>
|
||||
<details>
|
||||
<summary>{
|
||||
meta && meta.documentation
|
||||
? meta.documentation.join(' ')
|
||||
: 'Details'
|
||||
}</summary>
|
||||
<Call
|
||||
className='details'
|
||||
value={extrinsic}
|
||||
withHash
|
||||
/>
|
||||
</details>
|
||||
{
|
||||
extrinsic.isSigned
|
||||
? <LinkPolkascan data={extrinsic.hash.toHex()} type='extrinsic' />
|
||||
: null
|
||||
}
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ import { I18nProps } from '@polkadot/ui-app/types';
|
||||
|
||||
import React from 'react';
|
||||
import { DigestItem, Struct, Tuple, U8a, Vector, getTypeDef } from '@polkadot/types';
|
||||
import { Column } from '@polkadot/ui-app';
|
||||
import Params from '@polkadot/ui-params';
|
||||
|
||||
import translate from '../translate';
|
||||
@ -23,12 +24,9 @@ class Logs extends React.PureComponent<Props> {
|
||||
}
|
||||
|
||||
return (
|
||||
<section>
|
||||
<h1>{t('logs')}</h1>
|
||||
<div className='explorer--BlockByHash-flexable ui--flex-medium'>
|
||||
{value.map(this.renderItem)}
|
||||
</div>
|
||||
</section>
|
||||
<Column header={t('logs')}>
|
||||
{value.map(this.renderItem)}
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ class Events extends React.PureComponent<Props> {
|
||||
const { emptyLabel, events, t } = this.props;
|
||||
|
||||
if (!events || events.length === 0) {
|
||||
return emptyLabel || t('no events available');
|
||||
return <article>{emptyLabel || t('no events available')}</article>;
|
||||
}
|
||||
|
||||
return events.map(this.renderEvent);
|
||||
@ -40,35 +40,33 @@ class Events extends React.PureComponent<Props> {
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={eventClassName}
|
||||
<article
|
||||
className={`explorer--Container ${eventClassName}`}
|
||||
key={key}
|
||||
>
|
||||
<article className='explorer--Container'>
|
||||
<div className='header'>
|
||||
<h3>
|
||||
{event.section}.{event.method} {
|
||||
extIndex !== -1
|
||||
? `(#${formatNumber(extIndex)})`
|
||||
: ''
|
||||
}
|
||||
</h3>
|
||||
</div>
|
||||
<details>
|
||||
<summary>
|
||||
{
|
||||
event.meta && event.meta.documentation
|
||||
? event.meta.documentation.join(' ')
|
||||
: 'Details'
|
||||
}
|
||||
</summary>
|
||||
<EventDisplay
|
||||
className='details'
|
||||
value={event}
|
||||
/>
|
||||
</details>
|
||||
</article>
|
||||
</div>
|
||||
<div className='header'>
|
||||
<h3>
|
||||
{event.section}.{event.method} {
|
||||
extIndex !== -1
|
||||
? `(#${formatNumber(extIndex)})`
|
||||
: ''
|
||||
}
|
||||
</h3>
|
||||
</div>
|
||||
<details>
|
||||
<summary>
|
||||
{
|
||||
event.meta && event.meta.documentation
|
||||
? event.meta.documentation.join(' ')
|
||||
: 'Details'
|
||||
}
|
||||
</summary>
|
||||
<EventDisplay
|
||||
className='details'
|
||||
value={event}
|
||||
/>
|
||||
</details>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ import { I18nProps } from '@polkadot/ui-app/types';
|
||||
import { KeyedEvent } from './types';
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Columar, Column } from '@polkadot/ui-app';
|
||||
|
||||
import BlockHeaders from './BlockHeaders';
|
||||
import Events from './Events';
|
||||
@ -18,14 +18,6 @@ type Props = I18nProps & {
|
||||
events: Array<KeyedEvent>
|
||||
};
|
||||
|
||||
const Wrapper = styled.div`
|
||||
.column {
|
||||
flex: 0 0 50%;
|
||||
min-width: 0;
|
||||
padding: 0 1em 1em;
|
||||
}
|
||||
`;
|
||||
|
||||
class Main extends React.PureComponent<Props> {
|
||||
render () {
|
||||
const { events, t } = this.props;
|
||||
@ -34,16 +26,14 @@ class Main extends React.PureComponent<Props> {
|
||||
<>
|
||||
<Query />
|
||||
<Summary />
|
||||
<Wrapper className='ui--flex-medium'>
|
||||
<div className='column'>
|
||||
<h1>{t('recent blocks')}</h1>
|
||||
<Columar>
|
||||
<Column header={t('recent blocks')}>
|
||||
<BlockHeaders />
|
||||
</div>
|
||||
<div className='column'>
|
||||
<h1>{t('recent events')}</h1>
|
||||
</Column>
|
||||
<Column header={t('recent events')}>
|
||||
<Events events={events} />
|
||||
</div>
|
||||
</Wrapper>
|
||||
</Column>
|
||||
</Columar>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -54,8 +54,8 @@
|
||||
|
||||
.explorer--BlockByHash-header-right {
|
||||
position: absolute;
|
||||
top: -0.5rem;
|
||||
right: 0rem;
|
||||
top: 0.25rem;
|
||||
right: 0.5rem;
|
||||
}
|
||||
|
||||
.explorer--query {
|
||||
|
||||
@ -7,7 +7,7 @@ import { I18nProps } from '@polkadot/ui-app/types';
|
||||
import { ValidatorFilter, RecentlyOfflineMap } from '../types';
|
||||
|
||||
import React from 'react';
|
||||
import { Dropdown, FilterOverlay } from '@polkadot/ui-app';
|
||||
import { Columar, Column, Dropdown, FilterOverlay } from '@polkadot/ui-app';
|
||||
|
||||
import translate from '../translate';
|
||||
import Address from './Address';
|
||||
@ -48,7 +48,7 @@ class CurrentList extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { t } = this.props;
|
||||
const { current, next, t } = this.props;
|
||||
const { filter, filterOptions } = this.state;
|
||||
return (
|
||||
<div>
|
||||
@ -61,73 +61,42 @@ class CurrentList extends React.PureComponent<Props, State> {
|
||||
value={filter}
|
||||
/>
|
||||
</FilterOverlay>
|
||||
<div className='validator--ValidatorsList ui--flex-medium'>
|
||||
<div className='validator--current'>
|
||||
{this.renderCurrent()}
|
||||
</div>
|
||||
<div className='validator--next'>
|
||||
{this.renderNext()}
|
||||
</div>
|
||||
</div>
|
||||
<Columar className='validator--ValidatorsList'>
|
||||
<Column header={t('validators')}>
|
||||
{this.renderColumn(current, t('validator (stash)'))}
|
||||
</Column>
|
||||
<Column header={t('next up')}>
|
||||
{this.renderColumn(next, t('intention (stash)'))}
|
||||
</Column>
|
||||
</Columar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderCurrent () {
|
||||
const { current, t } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>
|
||||
{t('validators', {
|
||||
replace: {
|
||||
count: current.length
|
||||
}
|
||||
})}
|
||||
</h1>
|
||||
{this.renderColumn(current, t('validator (stash)'))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
private renderNext () {
|
||||
const { next, t } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>{t('next up')}</h1>
|
||||
{this.renderColumn(next, t('intention (stash)'))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
private renderColumn (addresses: Array<string>, defaultName: string) {
|
||||
const { balances, lastAuthor, lastBlock, recentlyOffline, t } = this.props;
|
||||
const { filter } = this.state;
|
||||
|
||||
if (addresses.length === 0) {
|
||||
return (
|
||||
<div>{t('no addresses found')}</div>
|
||||
<article>{t('no addresses found')}</article>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{addresses.map((address) => (
|
||||
<Address
|
||||
address={address}
|
||||
balances={balances}
|
||||
defaultName={defaultName}
|
||||
key={address}
|
||||
filter={filter}
|
||||
lastAuthor={lastAuthor}
|
||||
lastBlock={lastBlock}
|
||||
recentlyOffline={recentlyOffline}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
return addresses.map((address) => (
|
||||
<Address
|
||||
address={address}
|
||||
balances={balances}
|
||||
defaultName={defaultName}
|
||||
key={address}
|
||||
filter={filter}
|
||||
lastAuthor={lastAuthor}
|
||||
lastBlock={lastBlock}
|
||||
recentlyOffline={recentlyOffline}
|
||||
/>
|
||||
));
|
||||
}
|
||||
|
||||
private onChangeFilter = (filter: ValidatorFilter): void => {
|
||||
this.setState({ filter });
|
||||
}
|
||||
|
||||
@ -114,14 +114,3 @@
|
||||
.staking--accounts-info .staking--label {
|
||||
margin: 0 2.25rem -0.75rem 0;
|
||||
}
|
||||
|
||||
.ui--AddressSummary {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0;
|
||||
|
||||
.ui--AddressSummary-base,
|
||||
.ui--AddressSummary-children {
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ function createWebpack ({ alias = {}, context, name = 'index' }) {
|
||||
`./src/${name}.tsx`,
|
||||
isProd
|
||||
? null
|
||||
: 'webpack-plugin-serve/client'
|
||||
: null // 'webpack-plugin-serve/client'
|
||||
].filter((entry) => entry),
|
||||
mode: ENV,
|
||||
output: {
|
||||
|
||||
@ -90,7 +90,6 @@ export default class AddressMini extends React.PureComponent<Props> {
|
||||
return (
|
||||
<BalanceDisplay
|
||||
balance={balance}
|
||||
className='ui--AddressSummary-balance'
|
||||
params={value}
|
||||
/>
|
||||
);
|
||||
@ -106,7 +105,6 @@ export default class AddressMini extends React.PureComponent<Props> {
|
||||
return (
|
||||
<BondedDisplay
|
||||
bonded={bonded}
|
||||
className='ui--AddressSummary-balance'
|
||||
label=''
|
||||
params={value}
|
||||
/>
|
||||
|
||||
@ -2,32 +2,110 @@
|
||||
// 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';
|
||||
import { I18nProps } from './types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import { Label } from 'semantic-ui-react';
|
||||
import React from 'react';
|
||||
import { withCalls } from '@polkadot/ui-api';
|
||||
import styled from 'styled-components';
|
||||
import { withCalls, withMulti } from '@polkadot/ui-api';
|
||||
import { Button, Input, InputTags } from '@polkadot/ui-app';
|
||||
import BaseIdentityIcon from '@polkadot/ui-identicon';
|
||||
import keyring from '@polkadot/ui-keyring';
|
||||
|
||||
import { classes } from './util';
|
||||
import { AddressSummary, Props } from './AddressSummary';
|
||||
import AddressInfo, { BalanceActiveType } from './AddressInfo';
|
||||
import CopyButton from './CopyButton';
|
||||
import IdentityIcon from './IdentityIcon';
|
||||
import LinkPolkascan from './LinkPolkascan';
|
||||
import translate from './translate';
|
||||
import { classes, getAddrName, getAddrTags, toShortAddress } from './util';
|
||||
|
||||
const DEFAULT_ICON_SIZE = 64;
|
||||
export type Props = I18nProps & {
|
||||
accounts_idAndIndex?: [AccountId?, AccountIndex?],
|
||||
bonded?: BN | Array<BN>,
|
||||
buttons?: React.ReactNode,
|
||||
children?: React.ReactNode,
|
||||
defaultName?: string,
|
||||
extraInfo?: React.ReactNode,
|
||||
isEditable?: boolean,
|
||||
isInline?: boolean,
|
||||
value: AccountId | AccountIndex | Address | string | null,
|
||||
withBalance?: boolean | BalanceActiveType,
|
||||
withExplorer?: boolean,
|
||||
withIcon?: boolean,
|
||||
withIndex?: boolean,
|
||||
withTags?: boolean
|
||||
};
|
||||
|
||||
type State = {
|
||||
address: string,
|
||||
isEditingName: boolean,
|
||||
isEditingTags: boolean,
|
||||
name: string,
|
||||
tags: string[]
|
||||
};
|
||||
|
||||
const DEFAULT_ADDR = '5'.padEnd(16, 'x');
|
||||
const ICON_SIZE = 48;
|
||||
|
||||
class AddressRow extends React.PureComponent<Props, State> {
|
||||
state: State;
|
||||
|
||||
constructor (props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = this.createState();
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
defaultName: '<unknown>'
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps ({ accounts_idAndIndex = [], defaultName, value }: Props, prevState: State) {
|
||||
const [_accountId] = accounts_idAndIndex;
|
||||
const accountId = _accountId || value;
|
||||
const address = accountId
|
||||
? accountId.toString()
|
||||
: DEFAULT_ADDR;
|
||||
const name = getAddrName(address, false, defaultName) || '';
|
||||
const tags = getAddrTags(address);
|
||||
const state = { tags } as State;
|
||||
let hasChanged = false;
|
||||
|
||||
if (address !== prevState.address) {
|
||||
state.address = address;
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
if (!prevState.isEditingName && name !== prevState.name) {
|
||||
state.name = name;
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
return hasChanged
|
||||
? state
|
||||
: null;
|
||||
}
|
||||
|
||||
class AddressRow extends AddressSummary {
|
||||
render () {
|
||||
const { className, style, identIconSize = DEFAULT_ICON_SIZE, isInline, value, withIndex } = this.props;
|
||||
const { accounts_idAndIndex = [], className, isInline, style } = this.props;
|
||||
const [accountId, accountIndex] = accounts_idAndIndex;
|
||||
const isValid = accountId || accountIndex;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes('ui--AddressRow', !value && 'invalid', isInline && 'inline', className)}
|
||||
className={classes('ui--AddressRow', !isValid && 'invalid', isInline && 'inline', className)}
|
||||
style={style}
|
||||
>
|
||||
<div className='ui--AddressRow-base'>
|
||||
{this.renderIcon('ui--AddressRow-icon', identIconSize)}
|
||||
{this.renderIcon()}
|
||||
{this.renderButtons()}
|
||||
<div className='ui--AddressRow-details'>
|
||||
<div className='ui--AddressSummary-data'>
|
||||
<div className='ui--AddressRow-data'>
|
||||
{this.renderName()}
|
||||
{this.renderAddress()}
|
||||
{this.renderAccountIndex(withIndex)}
|
||||
{this.renderAccountIndex()}
|
||||
</div>
|
||||
{this.renderBalances()}
|
||||
{this.renderTags()}
|
||||
@ -38,11 +116,450 @@ class AddressRow extends AddressSummary {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private createState () {
|
||||
const { accounts_idAndIndex = [], defaultName, value } = this.props;
|
||||
const [_accountId] = accounts_idAndIndex;
|
||||
const accountId = _accountId || value;
|
||||
const address = accountId
|
||||
? accountId.toString()
|
||||
: DEFAULT_ADDR;
|
||||
const name = getAddrName(address, false, defaultName) || '';
|
||||
const tags = getAddrTags(address);
|
||||
|
||||
return {
|
||||
address,
|
||||
isEditingName: false,
|
||||
isEditingTags: false,
|
||||
name,
|
||||
tags
|
||||
};
|
||||
}
|
||||
|
||||
protected renderAddress () {
|
||||
const { address } = this.state;
|
||||
|
||||
return (
|
||||
<div className='ui--AddressRow-accountId'>
|
||||
<CopyButton
|
||||
isAddress
|
||||
value={address}
|
||||
>
|
||||
<span>{toShortAddress(address)}</span>
|
||||
</CopyButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
protected renderButtons () {
|
||||
const { buttons } = this.props;
|
||||
|
||||
return buttons
|
||||
? <div className='ui--AddressRow-buttons'>{buttons}</div>
|
||||
: null;
|
||||
}
|
||||
|
||||
protected renderExplorer () {
|
||||
const { value, withExplorer } = this.props;
|
||||
|
||||
if (!withExplorer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='ui--AddressRow-explorer'>
|
||||
<LinkPolkascan
|
||||
className='polkascan'
|
||||
data={value}
|
||||
type='address'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
protected renderName () {
|
||||
const { isEditable } = this.props;
|
||||
const { isEditingName, name } = this.state;
|
||||
|
||||
return isEditingName
|
||||
? (
|
||||
<Input
|
||||
autoFocus
|
||||
className='ui--AddressRow-name-input'
|
||||
defaultValue={name}
|
||||
onBlur={this.saveName}
|
||||
onChange={this.onChangeName}
|
||||
onEnter={this.saveName}
|
||||
withLabel={false}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<div
|
||||
className={classes('ui--AddressRow-name', isEditable && 'editable')}
|
||||
onClick={isEditable ? this.toggleNameEditor : undefined}
|
||||
>
|
||||
{name}
|
||||
{isEditable && this.renderEditIcon()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
protected onChangeName = (name: string) => {
|
||||
this.setState({ name });
|
||||
}
|
||||
|
||||
protected onChangeTags = (tags: string[]) => {
|
||||
this.setState({ tags });
|
||||
}
|
||||
|
||||
protected renderAccountIndex () {
|
||||
const { accounts_idAndIndex = [], withIndex } = this.props;
|
||||
const [, accountIndex] = accounts_idAndIndex;
|
||||
|
||||
if (!accountIndex || !withIndex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='ui--AddressRow-accountIndex'>
|
||||
{accountIndex.toString()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
protected renderBalances () {
|
||||
const { accounts_idAndIndex = [], withBalance } = this.props;
|
||||
const [accountId] = accounts_idAndIndex;
|
||||
|
||||
if (!withBalance || !accountId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='ui--AddressRow-balances'>
|
||||
<AddressInfo
|
||||
value={accountId}
|
||||
withBalance={withBalance}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
protected renderChildren () {
|
||||
const { children } = this.props;
|
||||
// we need children, or when an array, at least 1 non-empty value
|
||||
const hasChildren = !children
|
||||
? false
|
||||
: Array.isArray(children)
|
||||
? children.filter((child) => child).length !== 0
|
||||
: true;
|
||||
|
||||
if (!hasChildren) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='ui--AddressRow-children'>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
protected renderEditIcon () {
|
||||
return (
|
||||
<Button
|
||||
className='iconButton'
|
||||
icon='edit'
|
||||
size='mini'
|
||||
isPrimary
|
||||
key='unlock'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
protected renderIcon () {
|
||||
const { accounts_idAndIndex = [], withIcon = true } = this.props;
|
||||
const { address } = this.state;
|
||||
const [accountId] = accounts_idAndIndex;
|
||||
|
||||
if (!withIcon) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Since we do queries to storage in the wrapped example, we don't want
|
||||
// to follow that route if we don't have a valid address.
|
||||
const Component = accountId
|
||||
? IdentityIcon
|
||||
: BaseIdentityIcon;
|
||||
|
||||
return (
|
||||
<Component
|
||||
className='ui--AddressRow-icon'
|
||||
size={ICON_SIZE}
|
||||
value={address}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
protected renderSaveIcon (callback: () => void) {
|
||||
return (
|
||||
<Button
|
||||
className='saveButton'
|
||||
onClick={callback}
|
||||
icon='save'
|
||||
size='small'
|
||||
isPrimary
|
||||
key='save'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
protected renderTags () {
|
||||
const { isEditingTags, tags } = this.state;
|
||||
const { isEditable, withTags = false } = this.props;
|
||||
|
||||
if (!withTags) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return isEditingTags
|
||||
? (
|
||||
<InputTags
|
||||
className='ui--AddressRow-tags-input'
|
||||
onBlur={this.saveTags}
|
||||
onChange={this.onChangeTags}
|
||||
onClose={this.saveTags}
|
||||
openOnFocus
|
||||
defaultValue = {tags}
|
||||
searchInput={{ autoFocus: true }}
|
||||
value={tags}
|
||||
withLabel={false}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<div
|
||||
className={classes('ui--AddressRow-tags', isEditable && 'editable')}
|
||||
onClick={isEditable ? this.toggleTagsEditor : undefined}
|
||||
>
|
||||
{
|
||||
!tags.length
|
||||
? (isEditable ? <span className='addTags'>add tags</span> : undefined)
|
||||
: tags.map((tag) => (
|
||||
<Label key={tag} size='tiny' color='grey'>{tag}</Label>
|
||||
))
|
||||
}
|
||||
{isEditable && this.renderEditIcon()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
protected saveName = () => {
|
||||
const { address, name } = this.state;
|
||||
const trimmedName = name.trim();
|
||||
const meta = {
|
||||
name: trimmedName,
|
||||
whenEdited: Date.now()
|
||||
};
|
||||
|
||||
// Save only if the name was changed or if it's no empty.
|
||||
if (trimmedName && address) {
|
||||
try {
|
||||
const currentKeyring = keyring.getPair(address);
|
||||
|
||||
currentKeyring && keyring.saveAccountMeta(currentKeyring, meta);
|
||||
} catch (error) {
|
||||
keyring.saveAddress(address, meta);
|
||||
}
|
||||
|
||||
this.setState({ isEditingName: false });
|
||||
}
|
||||
}
|
||||
|
||||
protected saveTags = () => {
|
||||
const { address, tags } = this.state;
|
||||
const meta = {
|
||||
tags,
|
||||
whenEdited: Date.now()
|
||||
};
|
||||
|
||||
if (address) {
|
||||
try {
|
||||
const currentKeyring = keyring.getPair(address);
|
||||
|
||||
currentKeyring && keyring.saveAccountMeta(currentKeyring, meta);
|
||||
} catch (error) {
|
||||
keyring.saveAddress(address, meta);
|
||||
}
|
||||
|
||||
this.setState({ isEditingTags: false });
|
||||
}
|
||||
}
|
||||
|
||||
protected toggleNameEditor = () => {
|
||||
this.setState(({ isEditingName }) => ({
|
||||
isEditingName: !isEditingName
|
||||
}));
|
||||
}
|
||||
|
||||
protected toggleTagsEditor = () => {
|
||||
this.setState(({ isEditingTags }) => ({
|
||||
isEditingTags: !isEditingTags
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(
|
||||
export {
|
||||
DEFAULT_ADDR,
|
||||
AddressRow
|
||||
};
|
||||
|
||||
export default withMulti(
|
||||
styled(AddressRow as any)`
|
||||
text-align: left;
|
||||
|
||||
&.inline {
|
||||
display: flex;
|
||||
|
||||
.ui--AddressRow-children {
|
||||
padding: 0 0 0 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.invalid {
|
||||
filter: grayscale(100);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
button.ui.icon.editButton {
|
||||
padding: 0em .3em .3em .3em;
|
||||
color: #2e86ab;
|
||||
background: none;
|
||||
/*trick to let the button in the flow but keep the content centered regardless*/
|
||||
margin-left: -2em;
|
||||
position: relative;
|
||||
right: -2.3em;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.ui--AddressRow-accountId,
|
||||
.ui--AddressRow-accountIndex {
|
||||
font-family: monospace;
|
||||
font-size: 1.25em;
|
||||
padding: 0;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.ui--AddressRow-accountIndex {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.ui--AddressRow-balances {
|
||||
.column {
|
||||
display: block;
|
||||
|
||||
label,
|
||||
.result {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
> span {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.ui--AddressRow-base {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ui--AddressRow-buttons {
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 0.75rem;
|
||||
}
|
||||
|
||||
.ui--AddressRow-children {
|
||||
display: block;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.ui--AddressRow-data {
|
||||
margin: 0;
|
||||
padding: 0.25rem 0 0;
|
||||
white-space: nowrap;
|
||||
|
||||
* {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.ui--AddressRow-details {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ui--AddressRow-explorer {
|
||||
content: ' ';
|
||||
margin-top: 1rem;
|
||||
|
||||
.polkascan {
|
||||
position: absolute;
|
||||
bottom: 0.75rem;
|
||||
right: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.ui--AddressRow-icon {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.ui--AddressRow-name {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-transform: uppercase;
|
||||
white-space: normal;
|
||||
width: 17rem;
|
||||
}
|
||||
|
||||
.ui--AddressRow-name-input {
|
||||
input {
|
||||
height: 1em;
|
||||
text-transform: uppercase;
|
||||
margin-top: -0.3em;
|
||||
margin-bottom: -0.35em;
|
||||
}
|
||||
}
|
||||
|
||||
.ui--AddressRow-tags {
|
||||
&.editable {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: left;
|
||||
|
||||
> span {
|
||||
border: 1px #00000052 solid;
|
||||
border-radius: .5em;
|
||||
border-style: dashed;
|
||||
color: grey;
|
||||
font-size: x-small;
|
||||
padding: .1em 0.3em 0.1em 0.3em;
|
||||
margin-top: .2em;
|
||||
}
|
||||
|
||||
> div.label {
|
||||
margin-top:.3em
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ui--AddressRow-tags-input {
|
||||
margin-bottom: -1.4em;
|
||||
}
|
||||
`,
|
||||
translate,
|
||||
withCalls<Props>(
|
||||
['derive.accounts.idAndIndex', { paramName: 'value' }],
|
||||
'query.session.validators'
|
||||
)(AddressRow)
|
||||
['derive.accounts.idAndIndex', { paramName: 'value' }]
|
||||
)
|
||||
);
|
||||
|
||||
@ -1,435 +0,0 @@
|
||||
// Copyright 2017-2019 @polkadot/ui-app 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';
|
||||
import { I18nProps } from './types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import { Label } from 'semantic-ui-react';
|
||||
import React from 'react';
|
||||
import { withCalls } from '@polkadot/ui-api';
|
||||
import { Button, Input, InputTags } from '@polkadot/ui-app';
|
||||
import BaseIdentityIcon from '@polkadot/ui-identicon';
|
||||
import keyring from '@polkadot/ui-keyring';
|
||||
|
||||
import AddressInfo, { BalanceActiveType } from './AddressInfo';
|
||||
import CopyButton from './CopyButton';
|
||||
import IdentityIcon from './IdentityIcon';
|
||||
import LinkPolkascan from './LinkPolkascan';
|
||||
import translate from './translate';
|
||||
import { classes, getAddrName, getAddrTags, toShortAddress } from './util';
|
||||
|
||||
export type Props = I18nProps & {
|
||||
accounts_idAndIndex?: [AccountId?, AccountIndex?],
|
||||
bonded?: BN | Array<BN>,
|
||||
buttons?: React.ReactNode,
|
||||
children?: React.ReactNode,
|
||||
defaultName?: string,
|
||||
extraInfo?: React.ReactNode,
|
||||
identIconSize?: number,
|
||||
isEditable?: boolean,
|
||||
isInline?: boolean,
|
||||
isShort?: boolean,
|
||||
session_validators?: Array<AccountId>,
|
||||
value: AccountId | AccountIndex | Address | string | null,
|
||||
withBalance?: boolean | BalanceActiveType,
|
||||
withExplorer?: boolean,
|
||||
withIcon?: boolean,
|
||||
withIndex?: boolean,
|
||||
withTags?: boolean
|
||||
};
|
||||
|
||||
type State = {
|
||||
address: string,
|
||||
isEditingName: boolean,
|
||||
isEditingTags: boolean,
|
||||
name: string,
|
||||
tags: string[]
|
||||
};
|
||||
|
||||
const DEFAULT_ADDR = '5'.padEnd(16, 'x');
|
||||
const ICON_SIZE = 64;
|
||||
|
||||
class AddressSummary extends React.PureComponent<Props, State> {
|
||||
state: State;
|
||||
|
||||
constructor (props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = this.createState();
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
defaultName: '<unknown>'
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps ({ accounts_idAndIndex = [], defaultName, value }: Props, prevState: State): State | null {
|
||||
const [_accountId] = accounts_idAndIndex;
|
||||
const accountId = _accountId || value;
|
||||
const address = accountId
|
||||
? accountId.toString()
|
||||
: DEFAULT_ADDR;
|
||||
const name = getAddrName(address, false, defaultName) || '';
|
||||
const tags = getAddrTags(address);
|
||||
const state = { tags } as State;
|
||||
let hasChanged = false;
|
||||
|
||||
if (address !== prevState.address) {
|
||||
state.address = address;
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
if (!prevState.isEditingName && name !== prevState.name) {
|
||||
state.name = name;
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
return hasChanged
|
||||
? state
|
||||
: null;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { accounts_idAndIndex = [], className, isInline, style, withIndex } = this.props;
|
||||
const [accountId, accountIndex] = accounts_idAndIndex;
|
||||
const isValid = accountId || accountIndex;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes('ui--AddressSummary', !isValid && 'invalid', isInline && 'inline', className)}
|
||||
style={style}
|
||||
>
|
||||
<div className='ui--AddressSummary-base'>
|
||||
{this.renderIcon()}
|
||||
{this.renderButtons()}
|
||||
<div className='ui--AddressSummary-data'>
|
||||
{this.renderName()}
|
||||
{this.renderAddress()}
|
||||
{this.renderAccountIndex(withIndex)}
|
||||
</div>
|
||||
{this.renderBalances()}
|
||||
{this.renderTags()}
|
||||
</div>
|
||||
{this.renderChildren()}
|
||||
{this.renderExplorer()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private createState () {
|
||||
const { accounts_idAndIndex = [], defaultName, value } = this.props;
|
||||
const [_accountId] = accounts_idAndIndex;
|
||||
const accountId = _accountId || value;
|
||||
const address = accountId
|
||||
? accountId.toString()
|
||||
: DEFAULT_ADDR;
|
||||
const name = getAddrName(address, false, defaultName) || '';
|
||||
const tags = getAddrTags(address);
|
||||
|
||||
return {
|
||||
address,
|
||||
isEditingName: false,
|
||||
isEditingTags: false,
|
||||
name,
|
||||
tags
|
||||
};
|
||||
}
|
||||
|
||||
protected renderAddress () {
|
||||
const { isShort = true } = this.props;
|
||||
const { address } = this.state;
|
||||
const addrElem = isShort
|
||||
? (
|
||||
<CopyButton
|
||||
isAddress
|
||||
value={address}
|
||||
>
|
||||
<span>{toShortAddress(address)}</span>
|
||||
</CopyButton>
|
||||
)
|
||||
: address;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='ui--AddressSummary-name'>
|
||||
{name}
|
||||
</div>
|
||||
<div className='ui--AddressSummary-accountId'>
|
||||
{addrElem}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
protected renderButtons () {
|
||||
const { buttons } = this.props;
|
||||
|
||||
return buttons
|
||||
? <div className='ui--AddressSummary-buttons'>{buttons}</div>
|
||||
: null;
|
||||
}
|
||||
|
||||
protected renderExplorer () {
|
||||
const { value, withExplorer } = this.props;
|
||||
|
||||
if (!withExplorer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='ui--AddressSummary-explorer'>
|
||||
<LinkPolkascan
|
||||
className='polkascan'
|
||||
data={value}
|
||||
type='address'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
protected renderName () {
|
||||
const { isEditable } = this.props;
|
||||
const { isEditingName, name } = this.state;
|
||||
|
||||
return isEditingName
|
||||
? (
|
||||
<Input
|
||||
autoFocus
|
||||
defaultValue={name}
|
||||
onBlur={this.saveName}
|
||||
onChange={this.onChangeName}
|
||||
onEnter={this.saveName}
|
||||
withLabel={false}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<div
|
||||
className={classes('ui--AddressSummary-name', isEditable && 'editable')}
|
||||
onClick={isEditable ? this.toggleNameEditor : undefined}
|
||||
>
|
||||
{name}
|
||||
{isEditable && this.renderEditIcon()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
protected onChangeName = (name: string) => {
|
||||
this.setState({ name });
|
||||
}
|
||||
|
||||
protected onChangeTags = (tags: string[]) => {
|
||||
this.setState({ tags });
|
||||
}
|
||||
|
||||
protected renderAccountIndex (withIndex?: boolean) {
|
||||
const { accounts_idAndIndex = [] } = this.props;
|
||||
const [, accountIndex] = accounts_idAndIndex;
|
||||
|
||||
if (!accountIndex || !withIndex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='ui--AddressSummary-accountIndex'>
|
||||
{accountIndex.toString()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
protected renderBalances () {
|
||||
const { accounts_idAndIndex = [], withBalance } = this.props;
|
||||
const [accountId] = accounts_idAndIndex;
|
||||
|
||||
if (!withBalance || !accountId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='ui--AddressSummary-balances'>
|
||||
<AddressInfo
|
||||
value={accountId}
|
||||
withBalance={withBalance}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
protected renderChildren () {
|
||||
const { children } = this.props;
|
||||
// we need children, or when an array, at least 1 non-empty value
|
||||
const hasChildren = !children
|
||||
? false
|
||||
: Array.isArray(children)
|
||||
? children.filter((child) => child).length !== 0
|
||||
: true;
|
||||
|
||||
if (!hasChildren) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='ui--AddressSummary-children'>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
protected renderEditIcon () {
|
||||
return (
|
||||
<Button
|
||||
className='iconButton'
|
||||
icon='edit'
|
||||
size='mini'
|
||||
isPrimary
|
||||
key='unlock'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
protected renderIcon (className: string = 'ui--AddressSummary-icon', size?: number) {
|
||||
const { accounts_idAndIndex = [], identIconSize = ICON_SIZE, withIcon = true } = this.props;
|
||||
const { address } = this.state;
|
||||
const [accountId] = accounts_idAndIndex;
|
||||
|
||||
if (!withIcon) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Since we do queries to storage in the wrapped example, we don't want
|
||||
// to follow that route if we don't have a valid address.
|
||||
const Component = accountId
|
||||
? IdentityIcon
|
||||
: BaseIdentityIcon;
|
||||
|
||||
return (
|
||||
<Component
|
||||
className={className}
|
||||
size={size || identIconSize}
|
||||
value={address}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
protected renderSaveIcon (callback: () => void) {
|
||||
return (
|
||||
<Button
|
||||
className='saveButton'
|
||||
onClick={callback}
|
||||
icon='save'
|
||||
size='small'
|
||||
isPrimary
|
||||
key='save'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
protected renderTags () {
|
||||
const { isEditingTags, tags } = this.state;
|
||||
const { isEditable, withTags = false } = this.props;
|
||||
|
||||
if (!withTags) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const resultingDom = isEditingTags ?
|
||||
<>
|
||||
<InputTags
|
||||
className='ui--AddressSummary-tags-input'
|
||||
onBlur={this.saveTags}
|
||||
onChange={this.onChangeTags}
|
||||
onClose={this.saveTags}
|
||||
openOnFocus
|
||||
defaultValue = {tags}
|
||||
searchInput={{ autoFocus: true }}
|
||||
value={tags}
|
||||
withLabel={false}
|
||||
/>
|
||||
</>
|
||||
:
|
||||
<div
|
||||
className={classes('ui--AddressSummary-tags', isEditable && 'editable')}
|
||||
onClick={isEditable ? this.toggleTagsEditor : undefined}
|
||||
>
|
||||
{
|
||||
!tags.length
|
||||
? (isEditable ? <span className='addTags'>add tags</span> : undefined)
|
||||
: tags.map((tag) => {
|
||||
return (
|
||||
<Label key={tag} size='tiny' color='grey'>
|
||||
{tag}
|
||||
</Label>
|
||||
);
|
||||
})
|
||||
}
|
||||
{isEditable && this.renderEditIcon()}
|
||||
</div>;
|
||||
|
||||
return resultingDom;
|
||||
}
|
||||
|
||||
protected saveName = () => {
|
||||
const { address, name } = this.state;
|
||||
const trimmedName = name.trim();
|
||||
const meta = {
|
||||
name: trimmedName,
|
||||
whenEdited: Date.now()
|
||||
};
|
||||
|
||||
// Save only if the name was changed or if it's no empty.
|
||||
if (trimmedName && address) {
|
||||
try {
|
||||
const currentKeyring = keyring.getPair(address);
|
||||
|
||||
currentKeyring && keyring.saveAccountMeta(currentKeyring, meta);
|
||||
} catch (error) {
|
||||
keyring.saveAddress(address, meta);
|
||||
}
|
||||
|
||||
this.setState({ isEditingName: false });
|
||||
}
|
||||
}
|
||||
|
||||
protected saveTags = () => {
|
||||
const { address, tags } = this.state;
|
||||
const meta = {
|
||||
tags,
|
||||
whenEdited: Date.now()
|
||||
};
|
||||
|
||||
if (address) {
|
||||
try {
|
||||
const currentKeyring = keyring.getPair(address);
|
||||
|
||||
currentKeyring && keyring.saveAccountMeta(currentKeyring, meta);
|
||||
} catch (error) {
|
||||
keyring.saveAddress(address, meta);
|
||||
}
|
||||
|
||||
this.setState({ isEditingTags: false });
|
||||
}
|
||||
}
|
||||
|
||||
protected toggleNameEditor = () => {
|
||||
this.setState(({ isEditingName }) => ({
|
||||
isEditingName: !isEditingName
|
||||
}));
|
||||
}
|
||||
|
||||
protected toggleTagsEditor = () => {
|
||||
this.setState(({ isEditingTags }) => ({
|
||||
isEditingTags: !isEditingTags
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
DEFAULT_ADDR,
|
||||
AddressSummary
|
||||
};
|
||||
|
||||
export default translate(
|
||||
withCalls<Props>(
|
||||
['derive.accounts.idAndIndex', { paramName: 'value' }]
|
||||
)(AddressSummary)
|
||||
);
|
||||
@ -43,15 +43,6 @@ export default styled(Card)`
|
||||
}
|
||||
}
|
||||
|
||||
.ui--AddressSummary-buttons {
|
||||
text-align: right;
|
||||
margin-bottom: 2em;
|
||||
|
||||
button {
|
||||
margin-left: 0.2em;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 2060px) {
|
||||
min-width: 32%;
|
||||
max-width: 32%;
|
||||
|
||||
35
packages/ui-app/src/Columar.tsx
Normal file
35
packages/ui-app/src/Columar.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright 2017-2019 @polkadot/ui-app 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 React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode,
|
||||
className?: string
|
||||
};
|
||||
|
||||
class Columar extends React.PureComponent<Props> {
|
||||
render () {
|
||||
const { children, className } = this.props;
|
||||
|
||||
return (
|
||||
<div className={`ui--Columnar ${className}`}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default styled(Columar)`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.ui--Column {
|
||||
@media (min-width: 1025px) {
|
||||
max-width: 50%;
|
||||
min-width: 50%;
|
||||
}
|
||||
}
|
||||
`;
|
||||
53
packages/ui-app/src/Column.tsx
Normal file
53
packages/ui-app/src/Column.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
// Copyright 2017-2019 @polkadot/ui-app 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 React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode,
|
||||
className?: string,
|
||||
header: React.ReactNode
|
||||
};
|
||||
|
||||
class Column extends React.PureComponent<Props> {
|
||||
render () {
|
||||
const { children, className, header } = this.props;
|
||||
|
||||
return (
|
||||
<div className={`ui--Column ${className}`}>
|
||||
<h1>{header}</h1>
|
||||
<article className='container'>
|
||||
{children}
|
||||
</article>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default styled(Column)`
|
||||
box-sizing: border-box;
|
||||
flex: 1 1;
|
||||
margin: 0;
|
||||
padding: 0.5rem;
|
||||
|
||||
.container {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
article {
|
||||
border: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
article+article {
|
||||
border-top: 1px solid #f2f2f2;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1025px) {
|
||||
max-width: 50%;
|
||||
min-width: 50%;
|
||||
}
|
||||
`;
|
||||
@ -5,7 +5,6 @@
|
||||
export { default as AddressInfo } from './AddressInfo';
|
||||
export { default as AddressMini } from './AddressMini';
|
||||
export { default as AddressRow } from './AddressRow';
|
||||
export { default as AddressSummary } from './AddressSummary';
|
||||
export { default as Available } from './Available';
|
||||
export { default as Balance } from './Balance';
|
||||
export { default as Bonded } from './Bonded';
|
||||
@ -16,6 +15,8 @@ export { default as Card } from './Card';
|
||||
export { default as CardGrid } from './CardGrid';
|
||||
export { default as CardSummary } from './CardSummary';
|
||||
export { default as Chart } from './Chart';
|
||||
export { default as Columar } from './Columar';
|
||||
export { default as Column } from './Column';
|
||||
export { default as CopyButton } from './CopyButton';
|
||||
export { default as CryptoType } from './CryptoType';
|
||||
export { default as Dropdown } from './Dropdown';
|
||||
|
||||
@ -74,6 +74,7 @@ article {
|
||||
box-sizing: border-box;
|
||||
margin: 0.25rem;
|
||||
padding: 1.25rem 1.5rem;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
|
||||
&:hover {
|
||||
|
||||
@ -83,20 +83,6 @@ header .ui--Button-Group {
|
||||
}
|
||||
}
|
||||
|
||||
.ui--AddressRow-base {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ui--AddressRow-icon {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.ui--AddressSummary {
|
||||
position: relative;
|
||||
padding: 0 1em;
|
||||
white-space: nowrap
|
||||
}
|
||||
|
||||
button.ui.icon.iconButton {
|
||||
padding: 0em .3em .3em .3em !important;
|
||||
color: #2e86ab !important;
|
||||
@ -111,138 +97,6 @@ button.ui.icon.iconButton {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ui--AddressSummary-tags.editable {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
|
||||
> span {
|
||||
border: 1px #00000052 solid;
|
||||
border-radius: .5em;
|
||||
border-style: dashed;
|
||||
color: grey;
|
||||
font-size: x-small;
|
||||
padding: .1em 0.3em 0.1em 0.3em;
|
||||
margin-top: .2em;
|
||||
}
|
||||
|
||||
> div.label {
|
||||
margin-top:.3em
|
||||
}
|
||||
}
|
||||
|
||||
.ui--AddressSummary-name.editable > button.ui.icon.editButton,
|
||||
.ui--AddressSummary-tags.editable > button.ui.icon.editButton {
|
||||
padding: 0em .3em .3em .3em;
|
||||
color: #2e86ab;
|
||||
background: none;
|
||||
/*trick to let the button in the flow but keep the content centered regardless*/
|
||||
margin-left: -2em;
|
||||
position: relative;
|
||||
right: -2.3em;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.ui--AddressSummary-base {
|
||||
padding: 0 1em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ui--AddressSummary-base,
|
||||
.ui--AddressSummary-children {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.grow+.ui--AddressRow {
|
||||
padding: 1rem 0 0 1rem;
|
||||
}
|
||||
|
||||
.ui--AddressRow {
|
||||
&.inline {
|
||||
display: flex;
|
||||
|
||||
.ui--AddressSummary-children {
|
||||
padding: 0 0 0 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.ui--AddressSummary-children {
|
||||
display: block;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.ui--AddressRow-details {
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
|
||||
.ui--AddressSummary-data,
|
||||
.ui--AddressSummary-name {
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.ui--AddressSummary-name {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ui--AddressSummary-balances {
|
||||
.column {
|
||||
display: block;
|
||||
|
||||
label,
|
||||
.result {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
> span {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.ui--AddressSummary-tags.editable {
|
||||
justify-content: left;
|
||||
}
|
||||
}
|
||||
|
||||
.ui--AddressSummary-explorer {
|
||||
content: ' ';
|
||||
margin-top: 1rem;
|
||||
|
||||
.polkascan {
|
||||
position: absolute;
|
||||
bottom: 0.75rem;
|
||||
right: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.ui--AddressSummary-buttons {
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 0.75rem;
|
||||
}
|
||||
|
||||
.ui--AddressSummary .rx--updated {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.ui--AddressSummary.invalid {
|
||||
filter: grayscale(100);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.ui--AddressSummary-icon {
|
||||
display: block
|
||||
}
|
||||
|
||||
.ui--AddressSummary-data+.ui--AddressSummary-balance,
|
||||
.ui--AddressSummary-data+.ui--AddressSummary-bonded,
|
||||
.ui--AddressSummary-data+.ui--AddressSummary-nonce {
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
|
||||
.ui--AddressMini-balances {
|
||||
display: grid;
|
||||
}
|
||||
@ -255,60 +109,6 @@ button.ui.icon.iconButton {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.ui--AddressSummary-balance.rx--updated,
|
||||
.ui--AddressSummary-nonce.rx--updated {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.ui--AddressSummary .ui--AddressSummary-data {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.ui--AddressSummary-data {
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
|
||||
.ui--AddressSummary-name {
|
||||
box-sizing: border-box;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-transform: uppercase;
|
||||
white-space: normal;
|
||||
width: 17rem;
|
||||
}
|
||||
}
|
||||
|
||||
.ui--AddressSummary-data * {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.ui--AddressSummary-accountId,
|
||||
.ui--AddressSummary-accountIndex {
|
||||
font-family: monospace;
|
||||
font-size: 1.25em;
|
||||
padding: 0;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.ui--AddressSummary-accountIndex {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.ui--AddressSummary-data .ui--Input input {
|
||||
height: 1em;
|
||||
text-align: left;
|
||||
text-transform: uppercase;
|
||||
margin-top: -0.3em;
|
||||
margin-bottom: -0.35em;
|
||||
}
|
||||
|
||||
.ui--AddressSummary-tags-input {
|
||||
margin-bottom: -1.4em;
|
||||
}
|
||||
|
||||
.ui--Bubble.ui.label {
|
||||
font-weight: normal;
|
||||
margin: 0.25rem;
|
||||
|
||||
@ -30,6 +30,7 @@ th.ui--media-small {
|
||||
@media (min-width: 1281px) {
|
||||
.ui--flex-large {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ui--media-large {
|
||||
@ -47,6 +48,7 @@ th.ui--media-small {
|
||||
@media (min-width: 1025px) {
|
||||
.ui--flex-medium {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ui--media-medium {
|
||||
@ -64,6 +66,7 @@ th.ui--media-small {
|
||||
@media (min-width: 768px) {
|
||||
.ui--flex-small {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ui--media-small {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user