AddressMenu popup when clicking on address name (#2563)

* AddressMenu

* use menu

* refactor

* draft

* cleanup

* Sidebar

* Sidebar

* improvements

* redesign

* bump

* Small adjustments

* Layout adjustments, useCallbacks (not perfect...)

* Remove _setName with extra effect, re-add badges (edit missing)

* Missing useCallback in Address

* no withSidebar on AddressToggle

* useAccountInfo CPU spikes (split effects, no useCallback deps)

* Alway have underline (even on address)

* secondary button color

* Add name callback

* Make identity appear with displayname

* Adjust judgement button position

* UI adjustments

Co-authored-by: Jaco Greeff <jacogr@gmail.com>
This commit is contained in:
Keith Ingram 2020-04-23 05:43:20 +12:00 committed by GitHub
parent 127ba36c9d
commit 9703811f8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 1407 additions and 524 deletions

View File

@ -11,6 +11,7 @@ import React, { Suspense } from 'react';
import ReactDOM from 'react-dom';
import { HashRouter } from 'react-router-dom';
import { ThemeProvider } from 'styled-components';
import AccountSidebar from '@polkadot/app-accounts/Sidebar';
import { Api } from '@polkadot/react-api';
import Queue from '@polkadot/react-components/Status/Queue';
import { BlockAuthors, Events } from '@polkadot/react-query';
@ -33,9 +34,11 @@ ReactDOM.render(
<Api url={settings.apiUrl}>
<BlockAuthors>
<Events>
<HashRouter>
<Apps />
</HashRouter>
<AccountSidebar>
<HashRouter>
<Apps />
</HashRouter>
</AccountSidebar>
</Events>
</BlockAuthors>
</Api>

View File

@ -2,7 +2,7 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { DeriveAccountInfo, DeriveBalancesAll } from '@polkadot/api-derive/types';
import { DeriveBalancesAll } from '@polkadot/api-derive/types';
import { ActionStatus } from '@polkadot/react-components/Status/types';
import { RecoveryConfig } from '@polkadot/types/interfaces';
import { SortedAccount } from './types';
@ -10,8 +10,8 @@ import { SortedAccount } from './types';
import BN from 'bn.js';
import React, { useCallback, useState, useEffect } from 'react';
import styled from 'styled-components';
import { AddressInfo, AddressMini, AddressSmall, Badge, Button, ChainLock, CryptoType, Forget, Icon, IdentityIcon, Input, InputTags, LinkExternal, Menu, Popup, Tag } from '@polkadot/react-components';
import { useApi, useCall, useToggle } from '@polkadot/react-hooks';
import { AddressInfo, AddressMini, AddressSmall, Badge, Button, ChainLock, CryptoType, Forget, Icon, IdentityIcon, LinkExternal, Menu, Popup, Tag } from '@polkadot/react-components';
import { useAccountInfo, useApi, useCall, useToggle } from '@polkadot/react-hooks';
import { Option } from '@polkadot/types';
import keyring from '@polkadot/ui-keyring';
import { formatBalance, formatNumber } from '@polkadot/util';
@ -47,18 +47,12 @@ function calcVisible (filter: string, name: string, tags: string[]): boolean {
function Account ({ account: { address, meta }, className, filter, isFavorite, setBalance, toggleFavorite }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const api = useApi();
const info = useCall<DeriveAccountInfo>(api.api.derive.accounts.info, [address]);
const balancesAll = useCall<DeriveBalancesAll>(api.api.derive.balances.all, [address]);
const recoveryInfo = useCall<RecoveryConfig | null>(api.api.query.recovery?.recoverable, [address], {
transform: (opt: Option<RecoveryConfig>) => opt.unwrapOr(null)
});
const [tags, setTags] = useState<string[]>([]);
const [accName, setAccName] = useState('');
const [genesisHash, setGenesisHash] = useState<string | null>(null);
const [{ isDevelopment, isEditable, isExternal }, setFlags] = useState({ isDevelopment: false, isEditable: false, isExternal: false });
const { genesisHash, isDevelopment, isEditable, isExternal, name: accName, onSaveGenesisHash, tags } = useAccountInfo(address);
const [isVisible, setIsVisible] = useState(true);
const [isEditingName, toggleEditName] = useToggle();
const [isEditingTags, toggleEditTags] = useToggle();
const [isBackupOpen, toggleBackup] = useToggle();
const [isDeriveOpen, toggleDerive] = useToggle();
const [isForgetOpen, toggleForget] = useToggle();
@ -69,40 +63,10 @@ function Account ({ account: { address, meta }, className, filter, isFavorite, s
const [isSettingsOpen, toggleSettings] = useToggle();
const [isTransferOpen, toggleTransfer] = useToggle();
const _setTags = useCallback(
(tags: string[]): void => setTags(tags.sort()),
[]
);
useEffect((): void => {
balancesAll && setBalance(address, balancesAll.freeBalance);
}, [address, balancesAll, setBalance]);
useEffect((): void => {
const { identity, nickname } = info || {};
if (api.api.query.identity && api.api.query.identity.identityOf) {
if (identity?.display) {
setAccName(identity.display);
}
} else if (nickname) {
setAccName(nickname);
}
}, [api, info]);
useEffect((): void => {
const account = keyring.getAccount(address);
setGenesisHash(account?.meta.genesisHash || null);
setFlags({
isDevelopment: account?.meta.isTesting || false,
isEditable: (account && !(account.meta.isInjected || account.meta.isHardware)) || false,
isExternal: account?.meta.isExternal || false
});
_setTags(account?.meta.tags || []);
setAccName(account?.meta.name || '');
}, [address, _setTags]);
useEffect((): void => {
setIsVisible(
calcVisible(filter, accName, tags)
@ -114,55 +78,6 @@ function Account ({ account: { address, meta }, className, filter, isFavorite, s
[address, toggleFavorite]
);
const _onGenesisChange = useCallback(
(genesisHash: string | null): void => {
const account = keyring.getPair(address);
account && keyring.saveAccountMeta(account, { ...account.meta, genesisHash });
setGenesisHash(genesisHash);
},
[address]
);
const _saveName = useCallback(
(): void => {
toggleEditName();
const meta = { name: accName, whenEdited: Date.now() };
if (address) {
try {
const currentKeyring = keyring.getPair(address);
currentKeyring && keyring.saveAccountMeta(currentKeyring, meta);
} catch (error) {
keyring.saveAddress(address, meta);
}
}
},
[accName, address, toggleEditName]
);
const _saveTags = useCallback(
(): void => {
toggleEditTags();
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);
}
}
},
[address, tags, toggleEditTags]
);
const _onForget = useCallback(
(): void => {
if (!address) {
@ -240,26 +155,7 @@ function Account ({ account: { address, meta }, className, filter, isFavorite, s
)}
</td>
<td className='address'>
<AddressSmall
onClickName={toggleEditName}
overrideName={
isEditingName
? (
<Input
autoFocus
className='name--input'
defaultValue={accName}
onBlur={_saveName}
onChange={setAccName}
onEnter={_saveName}
withLabel={false}
/>
)
: undefined
}
toggle={isEditingName}
value={address}
/>
<AddressSmall value={address} />
{isBackupOpen && (
<Backup
address={address}
@ -327,36 +223,17 @@ function Account ({ account: { address, meta }, className, filter, isFavorite, s
<CryptoType accountId={address} />
</td>
<td className='all'>
{isEditingTags
? (
<InputTags
defaultValue={tags}
onBlur={_saveTags}
onChange={_setTags}
onClose={_saveTags}
openOnFocus
searchInput={{ autoFocus: true }}
value={tags}
withLabel={false}
/>
)
: (
<div
className='tags--toggle'
onClick={toggleEditTags}
>
{tags.length
? tags.map((tag): React.ReactNode => (
<Tag
key={tag}
label={tag}
/>
))
: <label>{t('no tags')}</label>
}
</div>
)
}
<div className='tags--toggle'>
{tags.length
? tags.map((tag): React.ReactNode => (
<Tag
key={tag}
label={tag}
/>
))
: <label>{t('no tags')}</label>
}
</div>
</td>
<td className='number'>
{balancesAll && formatNumber(balancesAll.accountNonce)}
@ -443,7 +320,7 @@ function Account ({ account: { address, meta }, className, filter, isFavorite, s
<ChainLock
className='accounts--network-toggle'
genesisHash={genesisHash}
onChange={_onGenesisChange}
onChange={onSaveGenesisHash}
preventDefault
/>
</>

View File

@ -8,12 +8,11 @@ import { ActionStatus } from '@polkadot/react-components/Status/types';
import React, { useCallback, useEffect, useState } from 'react';
import styled from 'styled-components';
import { AddressSmall, AddressInfo, Button, ChainLock, Icon, InputTags, Input, LinkExternal, Forget, Menu, Popup, Tag } from '@polkadot/react-components';
import { useApi, useCall, useToggle } from '@polkadot/react-hooks';
import { AddressSmall, AddressInfo, Button, ChainLock, Icon, LinkExternal, Forget, Menu, Popup, Tag, Transfer } from '@polkadot/react-components';
import { useApi, useCall } from '@polkadot/react-hooks';
import keyring from '@polkadot/ui-keyring';
import { formatNumber } from '@polkadot/util';
import Transfer from '../Accounts/modals/Transfer';
import { useTranslation } from '../translate';
interface Props {
@ -37,11 +36,9 @@ function Address ({ address, className, filter, isFavorite, toggleFavorite }: Pr
const [accName, setAccName] = useState('');
const [current, setCurrent] = useState<KeyringAddress | null>(null);
const [genesisHash, setGenesisHash] = useState<string | null>(null);
const [isEditingName, toggleEditName] = useToggle();
const [isEditingTags, toggleEditTags] = useToggle();
const [isForgetOpen, toggleForget] = useToggle();
const [isSettingPopupOpen, toggleTransfer] = useToggle();
const [isTransferOpen, toggleSettingPopup] = useToggle();
const [isForgetOpen, setIsForgetOpen] = useState(false);
const [isSettingPopupOpen, setIsSettingPopupOpen] = useState(false);
const [isTransferOpen, setIsTransferOpen] = useState(false);
const [isVisible, setIsVisible] = useState(true);
const _setTags = useCallback(
@ -90,6 +87,39 @@ function Address ({ address, className, filter, isFavorite, toggleFavorite }: Pr
}
}, [accName, filter, tags]);
const _onGenesisChange = useCallback(
(genesisHash: string | null): void => {
setGenesisHash(genesisHash);
const account = keyring.getAddress(address);
account && keyring.saveAddress(address, { ...account.meta, genesisHash });
setGenesisHash(genesisHash);
},
[address]
);
const _onFavorite = useCallback(
(): void => toggleFavorite(address),
[address, toggleFavorite]
);
const _toggleForget = useCallback(
(): void => setIsForgetOpen(!isForgetOpen),
[isForgetOpen]
);
const _toggleSettingPopup = useCallback(
(): void => setIsSettingPopupOpen(!isSettingPopupOpen),
[isSettingPopupOpen]
);
const _toggleTransfer = useCallback(
(): void => setIsTransferOpen(!isTransferOpen),
[isTransferOpen]
);
const _onForget = useCallback(
(): void => {
if (address) {
@ -111,62 +141,6 @@ function Address ({ address, className, filter, isFavorite, toggleFavorite }: Pr
[address, t]
);
const _onGenesisChange = useCallback(
(genesisHash: string | null): void => {
setGenesisHash(genesisHash);
const account = keyring.getAddress(address);
account && keyring.saveAddress(address, { ...account.meta, genesisHash });
setGenesisHash(genesisHash);
},
[address]
);
const _onFavorite = useCallback(
(): void => toggleFavorite(address),
[address, toggleFavorite]
);
const _saveName = useCallback(
(): void => {
toggleEditName();
const meta = { name: accName, whenEdited: Date.now() };
if (address) {
try {
const currentKeyring = keyring.getPair(address);
currentKeyring && keyring.saveAccountMeta(currentKeyring, meta);
} catch (error) {
keyring.saveAddress(address, meta);
}
}
},
[accName, address, toggleEditName]
);
const _saveTags = useCallback(
(): void => {
toggleEditTags();
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);
}
}
},
[address, tags, toggleEditTags]
);
if (!isVisible) {
return null;
}
@ -181,26 +155,7 @@ function Address ({ address, className, filter, isFavorite, toggleFavorite }: Pr
/>
</td>
<td className='address'>
<AddressSmall
onClickName={toggleEditName}
overrideName={
isEditingName
? (
<Input
autoFocus
className='name--input'
defaultValue={accName}
onBlur={_saveName}
onChange={setAccName}
onEnter={_saveName}
withLabel={false}
/>
)
: undefined
}
toggle={isEditingName}
value={address}
/>
<AddressSmall value={address} />
{address && current && (
<>
{isForgetOpen && (
@ -208,14 +163,14 @@ function Address ({ address, className, filter, isFavorite, toggleFavorite }: Pr
address={current.address}
key='modal-forget-account'
mode='address'
onClose={toggleForget}
onClose={_toggleForget}
onForget={_onForget}
/>
)}
{isTransferOpen && (
<Transfer
key='modal-transfer'
onClose={toggleTransfer}
onClose={_toggleTransfer}
recipientId={address}
/>
)}
@ -223,36 +178,17 @@ function Address ({ address, className, filter, isFavorite, toggleFavorite }: Pr
)}
</td>
<td className='all'>
{isEditingTags
? (
<InputTags
defaultValue={tags}
onBlur={_saveTags}
onChange={_setTags}
onClose={_saveTags}
openOnFocus
searchInput={{ autoFocus: true }}
value={tags}
withLabel={false}
/>
)
: (
<div
className='tags--toggle'
onClick={toggleEditTags}
>
{tags.length
? tags.map((tag): React.ReactNode => (
<Tag
key={tag}
label={tag}
/>
))
: <label>{t('no tags')}</label>
}
</div>
)
}
<div className='tags'>
{tags.length
? tags.map((tag): React.ReactNode => (
<Tag
key={tag}
label={tag}
/>
))
: <label>{t('no tags')}</label>
}
</div>
</td>
<td className='number'>
{balancesAll && formatNumber(balancesAll.accountNonce)}
@ -270,30 +206,30 @@ function Address ({ address, className, filter, isFavorite, toggleFavorite }: Pr
icon='paper plane'
key='deposit'
label={t('deposit')}
onClick={toggleTransfer}
onClick={_toggleTransfer}
size='small'
tooltip={t('Send funds to this address')}
/>
<Popup
className='theme--default'
isOpen={isSettingPopupOpen}
onClose={toggleSettingPopup}
onClose={_toggleSettingPopup}
trigger={
<Button
icon='setting'
onClick={toggleSettingPopup}
onClick={_toggleSettingPopup}
size='small'
/>
}
>
<Menu
onClick={toggleSettingPopup}
onClick={_toggleSettingPopup}
text
vertical
>
<Menu.Item
disabled={!isEditable}
onClick={toggleForget}
onClick={_toggleForget}
>
{t('Forget this address')}
</Menu.Item>
@ -329,14 +265,9 @@ export default React.memo(styled(Address)`
text-align: right;
}
.tags--toggle {
cursor: pointer;
.tags {
width: 100%;
min-height: 1.5rem;
label {
cursor: pointer;
}
}
.name--input {

View File

@ -0,0 +1,575 @@
// Copyright 2017-2020 @polkadot/app-accounts authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { BareProps } from '@polkadot/react-components/types';
import { AccountId, Address } from '@polkadot/types/interfaces';
import React, { useCallback } from 'react';
import { Label } from 'semantic-ui-react';
import styled from 'styled-components';
import { useAccountInfo, useApi, useRegistrars, useToggle } from '@polkadot/react-hooks';
import { classes } from '@polkadot/react-components/util';
import { colorLink } from '@polkadot/react-components/styles/theme';
import { AccountNameJudgement, AccountName, AddressMini, AvatarItem, Button, Icon, IconLink, IdentityIcon, Input, InputTags, LinkExternal, Transfer } from '@polkadot/react-components';
import { useTranslation } from '../translate';
interface Props extends BareProps {
address: AccountId | Address | string | Uint8Array;
onClose: () => void;
onUpdateName: () => void;
}
function Sidebar ({ address, className, onClose, onUpdateName, style }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const { isRegistrar, registrars } = useRegistrars();
const { identity, isCouncil, isDevelopment, isEditable, isEditingName, isEditingTags, isExternal, isInContacts, isOwned, isSociety, isSudo, isTechCommittee, name, onForgetAddress, onSaveName, onSaveTags, setName, setTags, tags, toggleIsEditingName, toggleIsEditingTags } = useAccountInfo(address);
const [isHoveringButton, toggleIsHoveringButton] = useToggle();
const [isTransferOpen, toggleIsTransferOpen] = useToggle();
const [isJudgementOpen, toggleIsJudgementOpen] = useToggle();
const _onForgetAddress = useCallback(
(): void => {
onForgetAddress();
onUpdateName && onUpdateName();
},
[onForgetAddress, onUpdateName]
);
const _onUpdateName = useCallback(
(): void => {
onSaveName();
onUpdateName && onUpdateName();
},
[onSaveName, onUpdateName]
);
const useIdentity = !!api.query.identity?.identityOf;
const hasFlags = isDevelopment || isExternal || isSociety || isCouncil || isTechCommittee || isSudo;
return (
<>
<div
className={classes('ui--AddressMenu', className)}
style={style}
>
<Button
className='ui--AddressMenu-close'
icon='close'
isBasic
isCircular
onClick={onClose}
/>
<div className='ui--AddressMenu-header'>
<IdentityIcon
size={80}
value={address.toString()}
/>
<div className='ui--AddressMenu-addr'>
{address.toString()}
</div>
<AccountName
onClick={(isEditable && !isEditingName) ? toggleIsEditingName : undefined}
override={
isEditingName
? (
<Input
autoFocus
className='name--input'
defaultValue={name}
onBlur={(isInContacts || isOwned) ? _onUpdateName : undefined}
onChange={setName}
withLabel={false}
/>
)
: isEditable
? (name.toUpperCase() || t('<unknown>'))
: undefined
}
value={address}
withSidebar={false}
>
{(!isEditingName && isEditable) && (
<Icon
className='inline-icon'
name='edit'
/>
)}
</AccountName>
<div className='ui--AddressMenu-tags'>
{isEditingTags
? (
<InputTags
defaultValue={tags}
onBlur={onSaveTags}
onChange={setTags}
onClose={onSaveTags}
openOnFocus
searchInput={{ autoFocus: true }}
value={tags}
withLabel={false}
/>
)
: (
<div
className='tags--toggle'
onClick={toggleIsEditingTags}
>
{tags.length
? tags.map((tag): React.ReactNode => (
<Label
color='grey'
key={tag}
size='tiny'
>
{tag}
</Label>
))
: <label>{t('no tags')}</label>
}
</div>
)
}
{(!isEditingTags && (isInContacts || isOwned)) && (
<Icon
className='inline-icon'
name='edit'
onClick={toggleIsEditingTags}
/>
)}
</div>
{hasFlags && (
<div className='ui--AddressMenu-flags'>
{isExternal && (
<Label
color='grey'
size='tiny'
tag
>
{t('Injected')}
</Label>
)}
{isDevelopment && (
<Label
color='grey'
size='tiny'
tag
>
{t('Test account')}
</Label>
)}
{isCouncil && (
<Label
color='blue'
size='tiny'
tag
>
{t('Council')}
</Label>
)}
{isSociety && (
<Label
color='green'
size='tiny'
tag
>
{t('Society')}
</Label>
)}
{isTechCommittee && (
<Label
color='orange'
size='tiny'
tag
>
{t('Technical committee')}
</Label>
)}
{isSudo && (
<Label
color='pink'
size='tiny'
tag
>
{t('Sudo key')}
</Label>
)}
</div>
)}
<div className='ui-AddressMenu--button'>
<Button.Group>
<Button
icon='send'
label={t('Deposit')}
onClick={toggleIsTransferOpen}
/>
{isOwned && (
<Button
className='basic'
icon='check'
isPrimary
label={t('Owned')}
onMouseEnter={toggleIsHoveringButton}
onMouseLeave={toggleIsHoveringButton}
size='tiny'
/>
)}
{!isOwned && !isInContacts && (
<Button
icon='add'
isPositive
label={t('Save')}
onClick={_onUpdateName}
onMouseEnter={toggleIsHoveringButton}
onMouseLeave={toggleIsHoveringButton}
size='tiny'
/>
)}
{!isOwned && isInContacts && (
<Button
className={`ui--AddressMenu-button icon ${isHoveringButton ? '' : 'basic'}`}
isAnimated
isNegative={isHoveringButton}
isPositive={!isHoveringButton}
onClick={_onForgetAddress}
onMouseEnter={toggleIsHoveringButton}
onMouseLeave={toggleIsHoveringButton}
size='tiny'
>
<Button.Content visible>
<Icon name='check' />
{' '}
{t('Saved')}
</Button.Content>
<Button.Content hidden>
<Icon name='ban' />
{' '}
{t('Remove')}
</Button.Content>
</Button>
)}
</Button.Group>
</div>
</div>
{useIdentity && identity?.isExistent && (
<div className='ui--AddressMenu-section ui--AddressMenu-identity'>
<div className='ui--AddressMenu-sectionHeader'>
<div>
<Icon name='address card' />
{' '}
{t('identity')}
</div>
<Label
color={
identity.isGood
? 'green'
: identity.isBad
? 'red'
: 'yellow'
}
size='tiny'
>
<b>{identity.judgements.length}</b>
<Label.Detail>
{
identity.judgements.length
? (identity.isGood
? (identity.isKnownGood ? t('Known good') : t('Reasonable'))
: (identity.isErroneous ? t('Erroneous') : t('Low quality'))
)
: t('No judgments')
}
</Label.Detail>
</Label>
</div>
<div>
<AvatarItem
icon={
// This won't work - images are IPFS hashes
// identity.image
// ? <img src={identity.image} />
// : <i className='icon user ui--AddressMenu-identityIcon' />
//
<i className='icon user ui--AddressMenu-identityIcon' />
}
subtitle={identity.legal}
title={identity.display}
/>
<div className='ui--AddressMenu-identityTable'>
{identity.parent && (
<div className='tr parent'>
<div className='th'>{t('parent')}</div>
<div className='td'>
<AddressMini
className='parent'
isPadded={false}
value={identity.parent}
/>
</div>
</div>
)}
{identity.email && (
<div className='tr'>
<div className='th'>{t('email')}</div>
<div className='td'>
<a
href={`mailto:${identity.email}`}
rel='noopener noreferrer'
target='_blank'
>
{identity.email}
</a>
</div>
</div>
)}
{identity.web && (
<div className='tr'>
<div className='th'>{t('website')}</div>
<div className='td'>
<a
href={identity.web.replace(/^(https?:\/\/)?/g, 'https://')}
rel='noopener noreferrer'
target='_blank'
>
{identity.web}
</a>
</div>
</div>
)}
{identity.twitter && (
<div className='tr'>
<div className='th'>{t('twitter')}</div>
<div className='td'>
<a
href={`https://twitter.com/${identity.twitter}`}
rel='noopener noreferrer'
target='_blank'
>
{identity.twitter}
</a>
</div>
</div>
)}
{identity.riot && (
<div className='tr'>
<div className='th'>{t('riot')}</div>
<div className='td'>
{identity.riot}
</div>
</div>
)}
</div>
</div>
</div>
)}
{address && identity?.isExistent && isRegistrar && (
<div className='ui--AddressMenu-section'>
<div className='ui--AddressMenu-actions'>
<ul>
<li>
<IconLink
icon='address card'
label={t('Add identity judgment')}
onClick={toggleIsJudgementOpen}
/>
</li>
</ul>
</div>
</div>
)}
<div className='ui--AddressMenu-section'>
<LinkExternal
data={address}
type='address'
/>
</div>
</div>
{isTransferOpen && (
<Transfer
key='modal-transfer'
onClose={toggleIsTransferOpen}
recipientId={address}
/>
)}
{(!!address && isJudgementOpen && isRegistrar && useIdentity) && (
<AccountNameJudgement
address={address.toString()}
key='modal-judgement'
registrars={registrars}
toggleJudgement={toggleIsJudgementOpen}
/>
)}
</>
);
}
export default React.memo(styled(Sidebar)`
bottom: 0;
position: fixed;
top: 0;
right: 0;
bottom: 0;
max-width: 24rem;
background: #f5f5f5;
padding: 1rem;
box-shadow: -6px 0px 20px 0px rgba(0,0,0,0.2);
z-index: 999;
input {
width: auto !important;
}
.ui--AddressMenu-close {
position: absolute;
right: 0.5rem;
top: 0.5rem;
font-size: 1.2rem;
padding: 0.6rem !important;
}
.ui--AddressMenu-header {
align-items: center;
background: white;
border-bottom: 1px solid #e6e6e6;
display: flex;
flex-direction: column;
justify-content: center;
margin: -1rem -1rem 1rem -1rem;
padding: 1rem;
.ui.button {
transition: 0.5s all;
&.secondary {
background-color: #666;
}
}
.ui.button+.ui.button {
margin-left: 0.25rem !important;
}
}
.ui--AddressMenu-addr {
font-family: monospace;
margin: 0.5rem 0;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
}
.ui--AddressMenu-section {
&:not(:last-child) {
margin-bottom: 1.4rem;
}
.ui--AddressMenu-sectionHeader {
display: inline-flex;
color: #aaa;
margin-bottom: 0.4rem;
width: 100%;
& > :first-child {
flex: 1;
}
}
}
.ui--AddressMenu-identity {
.ui--AddressMenu-identityTable {
font-size: 13px;
margin-top: 0.3rem;
.tr {
display: inline-flex;
align-items: center;
width: 100%;
.th {
font-weight: bold;
text-align: right;
flex-basis: 20%;
}
.td {
padding-left: 0.6rem;
flex: 1;
}
}
}
.parent {
padding: 0 !important;
}
}
.ui--AddressMenu-tags,
.ui--AddressMenu-flags {
margin-bottom: 0.75rem;
}
.ui--AddressMenu-flags {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
> * {
margin-bottom: 0.4rem;
&:not(:first-child) {
margin-left: 1rem;
margin-right: 0;
}
}
}
.ui--AddressMenu-identityIcon {
background: ${colorLink}66;
}
.ui--AddressMenu-actions {
ul {
list-style-type: none;
margin-block-start: 0;
margin-block-end: 0;
padding-inline-start: 1rem;
li {
margin: 0.2rem 0;
}
}
}
.tags--toggle {
display: inline-block;
}
.inline-icon {
cursor: pointer;
margin: 0 0 0 0.6rem;
color: rgba(200, 200, 200, 0.8);
}
&:hover {
.inline-icon {
color: ${colorLink}
}
}
.name--input {
.ui.input {
margin: 0 !important;
> input {
padding: 0 !important;
background: rgba(230, 230, 230, 0.8) !important;
border: 0 !important;
border-radius: 0 !important;
box-shadow: 0 3px 3px rgba(0,0,0,.2);
}
}
}
`);

View File

@ -0,0 +1,43 @@
// Copyright 2017-2020 @polkadot/app-accounts 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 { StringOrNull, VoidFn } from '@polkadot/react-components/types';
import React, { useCallback, useState } from 'react';
import Sidebar from './Sidebar';
type ToggleContext = undefined | (([address, onUpdateName]: [StringOrNull, VoidFn | null]) => void);
interface Props {
children: React.ReactNode;
}
const AccountSidebarToggle: React.Context<ToggleContext> = React.createContext<ToggleContext>(undefined);
function AccountSidebar ({ children }: Props): React.ReactElement<Props> {
const [[address, onUpdateName], setAddress] = useState<[StringOrNull, VoidFn | null]>([null, null]);
const onClose = useCallback(
() => setAddress([null, null]),
[]
);
return (
<AccountSidebarToggle.Provider value={setAddress}>
{children}
{address && (
<Sidebar
address={address}
onClose={onClose}
onUpdateName={onUpdateName}
/>
)}
</AccountSidebarToggle.Provider>
);
}
export { AccountSidebarToggle };
export default React.memo(AccountSidebar);

View File

@ -25,7 +25,9 @@ function External ({ className, value: { image, imageHash, threshold } }: Props)
proposal={image?.proposal}
/>
<td className='address'>
{image && <AddressMini value={image.proposer} />}
{image && (
<AddressMini value={image.proposer} />
)}
</td>
<td className='number'>
{image && <FormatBalance value={image.balance} />}

View File

@ -29,7 +29,9 @@ function BlockHeader ({ value }: Props): React.ReactElement<Props> | null {
</td>
<td className='all hash overflow'>{hashHex}</td>
<td className='address'>
{value.author && <AddressMini value={value.author} />}
{value.author && (
<AddressMini value={value.author} />
)}
</td>
</tr>
);

View File

@ -50,7 +50,11 @@ function BlockByHash ({ className, value }: Props): React.ReactElement<Props> {
>
{getBlock && !getBlock.isEmpty && getHeader && !getHeader.isEmpty && (
<tr>
<td className='address'>{getHeader.author && <AddressMini value={getHeader.author} />}</td>
<td className='address'>
{getHeader.author && (
<AddressMini value={getHeader.author} />
)}
</td>
<td className='hash overflow'>{getHeader.hash.toHex()}</td>
<td className='hash overflow'><Link to={`/explorer/query/${parentHash}`}>{parentHash}</Link></td>
<td className='hash overflow'>{getHeader.extrinsicsRoot.toHex()}</td>

View File

@ -6,6 +6,7 @@ import { DeriveParachainInfo } from '@polkadot/api-derive/types';
import React from 'react';
import styled from 'styled-components';
import { AvatarItem } from '@polkadot/react-components';
import { useTranslation } from './translate';
import { parachainName, parachainOwner } from './util';
@ -21,91 +22,30 @@ function ParachainInfo ({ children, className, info, isBig }: Props): React.Reac
const { t } = useTranslation();
return (
<div className={[className, isBig ? 'big' : ''].join(' ')}>
<div className='chain-icon'>
{
info?.icon
? (
<img src={info.icon} />
)
: (
<i className='icon chain fitted' />
)
}
</div>
<div className='details'>
<div className='name'>
{parachainName(t, info)}
</div>
<div className='owner'>
{parachainOwner(t, info)}
</div>
</div>
<AvatarItem
className={className}
icon={
info?.icon
? (
<img src={info.icon} />
)
: (
<i className='icon chain' />
)
}
isBig={isBig}
subtitle={parachainOwner(t, info)}
title={parachainName(t, info)}
>
{children}
</div>
</AvatarItem>
);
}
export default React.memo(styled(ParachainInfo)`
& {
display: flex;
align-items: center;
.chain-icon {
width: 2.4rem;
height: 2.4rem;
.icon.chain {
background: #e03997;
border-radius: 50%;
color: white;
margin-right: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
> * {
width:100%;
}
> i.icon {
height: auto !important;
}
> img {
height: 100%;
}
}
.details {
flex: 1;
.name {
font-weight: bold;
font-size: 1rem;
}
.owner {
color: rgba(100, 100, 100, 0.6);
font-size: 1rem;
}
}
&.big {
.chain-icon {
width: 3.4rem;
height: 3.4rem;
margin-right: 0.6rem;
> i.icon {
font-size: 1.6rem;
}
}
.details {
.name {
font-size: 1.4rem;
line-height: 1.4rem;
}
}
}
}
`);

View File

@ -6,16 +6,14 @@ import { DeriveAccountInfo, DeriveAccountRegistration } from '@polkadot/api-deri
import { BareProps } from '@polkadot/react-api/types';
import { AccountId, AccountIndex, Address } from '@polkadot/types/interfaces';
import React, { useEffect, useState } from 'react';
import React, { useCallback, useContext, useEffect, useState } from 'react';
import styled from 'styled-components';
import registry from '@polkadot/react-api/typeRegistry';
import { useCall, useApi, useRegistrars, useToggle } from '@polkadot/react-hooks';
import { AccountSidebarToggle } from '@polkadot/app-accounts/Sidebar';
import { useCall, useApi } from '@polkadot/react-hooks';
import { stringToU8a } from '@polkadot/util';
import { useTranslation } from './translate';
import { getAddressName } from './util';
import AccountNameJudgement from './AccountNameJudgement';
import AddressMini from './AddressMini';
import Badge from './Badge';
import Icon from './Icon';
@ -29,9 +27,9 @@ interface Props extends BareProps {
// this is used by app-account/addresses to toggle editing
toggle?: boolean;
value: AccountId | AccountIndex | Address | string | Uint8Array | null | undefined;
withSidebar?: boolean;
}
const DISPLAY_KEYS = ['display', 'legal', 'email', 'web', 'twitter', 'riot'];
const KNOWN: [AccountId, string][] = [
[registry.createType('AccountId', stringToU8a('modlpy/socie'.padEnd(32, '\0'))), 'Society'],
[registry.createType('AccountId', stringToU8a('modlpy/trsry'.padEnd(32, '\0'))), 'Treasury']
@ -98,16 +96,14 @@ function extractName (address: string, accountIndex?: AccountIndex, defaultName?
);
}
function createIdElem (badgeType: 'green' | 'brown' | 'gray', nameElem: React.ReactNode, infoElem: React.ReactNode, hoverElem?: React.ReactNode, onJudge?: undefined | (() => void)): React.ReactNode {
function createIdElem (badgeType: 'green' | 'brown' | 'gray', nameElem: React.ReactNode, infoElem: React.ReactNode): React.ReactNode {
return (
<div className='via-identity'>
<Badge
hover={hoverElem}
info={infoElem}
isInline
isSmall
isTooltip
onClick={onJudge}
type={badgeType}
/>
{nameElem}
@ -115,11 +111,10 @@ function createIdElem (badgeType: 'green' | 'brown' | 'gray', nameElem: React.Re
);
}
function extractIdentity (address: string, identity: DeriveAccountRegistration, onJudge: undefined | (() => void), t: (key: string, opts?: object) => string): React.ReactNode {
function extractIdentity (address: string, identity: DeriveAccountRegistration): React.ReactNode {
const judgements = identity.judgements.filter(([, judgement]): boolean => !judgement.isFeePaid);
const isGood = judgements.some(([, judgement]): boolean => judgement.isKnownGood || judgement.isReasonable);
const isBad = judgements.some(([, judgement]): boolean => judgement.isErroneous || judgement.isLowQuality);
const waitCount = identity.judgements.length - judgements.length;
const displayName = isGood
? identity.display
: (identity.display || '').replace(/[^\x20-\x7E]/g, '');
@ -130,43 +125,6 @@ function extractIdentity (address: string, identity: DeriveAccountRegistration,
: identity.displayParent.replace(/[^\x20-\x7E]/g, '')
)
: undefined;
const hoverElem = (
<div>
<div>
{
judgements.length
? (judgements.length === 1
? t('1 judgement')
: t('{{count}} judgements', { replace: { count: judgements.length } })
)
: t('no judgements')
}{judgements.length ? ': ' : ''}{judgements.map(([, judgement]): string => judgement.toString()).join(', ')}{
waitCount
? t(' ({{count}} waiting)', { replace: { count: waitCount } })
: ''
}
</div>
<table>
<tbody>
{identity.parent && (
<tr>
<td>{t('parent')}</td>
<td><AddressMini value={identity.parent} /></td>
</tr>
)}
{DISPLAY_KEYS
.filter((key): boolean => !!identity[key as 'web'])
.map((key): React.ReactNode => (
<tr key={key}>
<td>{t(key)}</td>
<td>{identity[key as 'web']}</td>
</tr>
))
}
</tbody>
</table>
</div>
);
const nameElem = displayParent
? <span className={`name ${isGood && 'isGood'}`}><span className='top'>{displayParent}</span><span className='sub'>/{displayName}</span></span>
: <span className={`name ${isGood && 'isGood'}`}>{displayName}</span>;
@ -176,16 +134,14 @@ function extractIdentity (address: string, identity: DeriveAccountRegistration,
nameCache.set(address, [false, displayParent ? [displayParent, displayName] : [displayName, null]]);
displayCache.set(address, createIdElem(badgeType, nameElem, infoElem));
return createIdElem(badgeType, nameElem, infoElem, hoverElem, onJudge);
return createIdElem(badgeType, nameElem, infoElem);
}
function AccountName ({ children, className, defaultName, label, noLookup, onClick, override, toggle, value }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
function AccountName ({ children, className, defaultName, label, noLookup, onClick, override, toggle, value, withSidebar }: Props): React.ReactElement<Props> {
const { api } = useApi();
const { isRegistrar, registrars } = useRegistrars(noLookup);
const [isJudgementOpen, toggleJudgement] = useToggle();
const info = useCall<DeriveAccountInfo>(!noLookup && api.derive.accounts.info, [value]);
const [name, setName] = useState<React.ReactNode>(() => extractName((value || '').toString(), undefined, defaultName));
const toggleSidebar = useContext(AccountSidebarToggle);
// set the actual nickname, local name, accountIndex, accountId
useEffect((): void => {
@ -195,7 +151,7 @@ function AccountName ({ children, className, defaultName, label, noLookup, onCli
if (api.query.identity?.identityOf) {
setName(() =>
identity?.display
? extractIdentity(cacheAddr, identity, isRegistrar ? toggleJudgement : undefined, t)
? extractIdentity(cacheAddr, identity)
: extractName(cacheAddr, accountIndex)
);
} else if (nickname) {
@ -205,32 +161,40 @@ function AccountName ({ children, className, defaultName, label, noLookup, onCli
} else {
setName(defaultOrAddr(defaultName, cacheAddr, accountIndex));
}
}, [api, defaultName, info, isRegistrar, t, toggle, toggleJudgement, value]);
}, [api, defaultName, info, toggle, value]);
const _onNameEdit = useCallback(
() => setName(defaultOrAddr(defaultName, (value || '').toString())),
[defaultName, value]
);
const _onToggleSidebar = useCallback(
() => toggleSidebar && value && toggleSidebar([value.toString(), _onNameEdit]),
[_onNameEdit, toggleSidebar, value]
);
return (
<>
{isJudgementOpen && (
<AccountNameJudgement
address={(value || '').toString()}
registrars={registrars}
toggleJudgement={toggleJudgement}
/>
)}
<div
className={`ui--AccountName ${className}`}
onClick={
override
? undefined
: onClick
}
>
{label || ''}{override || name}{children}
</div>
</>
<div
className={`ui--AccountName ${withSidebar && 'withSidebar'} ${className}`}
onClick={
withSidebar
? _onToggleSidebar
: onClick
}
>
{label || ''}{override || name}{children}
</div>
);
}
export default React.memo(styled(AccountName)`
border: 1px dotted transparent;
&.withSidebar:hover {
border-bottom-color: #333;
cursor: help !important;
}
.via-identity {
display: inline-block;
overflow: hidden;

View File

@ -48,6 +48,7 @@ function AccountNameJudgement ({ address, registrars, toggleJudgement }: Props):
return (
<Modal
header={t('Provide judgement')}
onClose={toggleJudgement}
size='small'
>
<Modal.Content>

View File

@ -34,11 +34,12 @@ interface Props extends BareProps {
withBalance?: boolean;
withBonded?: boolean;
withLockedVote?: boolean;
withSidebar?: boolean;
withName?: boolean;
withShrink?: boolean;
}
function AddressMini ({ balance, bonded, children, className, iconInfo, isPadded = true, label, labelBalance, noLookup, summary, value, withAddress = true, withBalance = false, withBonded = false, withLockedVote = false, withName = true, withShrink = false }: Props): React.ReactElement<Props> | null {
function AddressMini ({ balance, bonded, children, className, iconInfo, isPadded = true, label, labelBalance, noLookup, summary, value, withAddress = true, withBalance = false, withBonded = false, withLockedVote = false, withName = true, withShrink = false, withSidebar = true }: Props): React.ReactElement<Props> | null {
if (!value) {
return null;
}
@ -67,6 +68,7 @@ function AddressMini ({ balance, bonded, children, className, iconInfo, isPadded
<AccountName
noLookup={noLookup}
value={value}
withSidebar={withSidebar}
/>
)
: toShortAddress(value)

View File

@ -7,26 +7,31 @@ import { Address, AccountId } from '@polkadot/types/interfaces';
import React from 'react';
import styled from 'styled-components';
import { classes } from './util';
import AccountIndex from './AccountIndex';
import AccountName from './AccountName';
import IdentityIcon from './IdentityIcon';
interface Props {
children?: React.ReactNode;
className?: string;
defaultName?: string;
onClickName?: () => void;
overrideName?: React.ReactNode;
toggle?: boolean;
withIndex?: boolean;
withSidebar?: boolean;
toggle?: any;
value?: string | Address | AccountId | null | Uint8Array;
}
function AddressSmall ({ className, defaultName, onClickName, overrideName, toggle, value }: Props): React.ReactElement<Props> {
function AddressSmall ({ children, className, defaultName, onClickName, overrideName, toggle, value, withIndex, withSidebar = true }: Props): React.ReactElement<Props> {
return (
<div className={`ui--AddressSmall ${className}`}>
<IdentityIcon
size={32}
value={value as Uint8Array}
/>
<div className='nameInfo'>
<div className={classes('nameInfo', withSidebar && 'withSidebar')}>
<AccountName
className={(overrideName || !onClickName) ? '' : 'name--clickable'}
defaultName={defaultName}
@ -34,24 +39,27 @@ function AddressSmall ({ className, defaultName, onClickName, overrideName, togg
override={overrideName}
toggle={toggle}
value={value}
/>
withSidebar={withSidebar}
>
{children}
</AccountName>
{withIndex && (
<AccountIndex value={value} />
)}
</div>
</div>
);
}
export default React.memo(styled(AddressSmall)`
vertical-align: middle;
white-space: nowrap;
.name--clickable {
cursor: pointer;
}
display: flex;
align-items: center;
.ui--IdentityIcon,
.nameInfo {
display: inline-block;
vertical-align: middle;
white-space: nowrap;
}
.ui--IdentityIcon {
@ -59,10 +67,12 @@ export default React.memo(styled(AddressSmall)`
}
.nameInfo {
&.withSidebar {
cursor: help;
}
> div {
max-width: 16rem;
overflow: hidden;
text-overflow: ellipsis;
max-width: 12rem;
}
}
`);

View File

@ -69,6 +69,7 @@ function AddressToggle ({ address, className, filter, isHidden, noLookup, noTogg
className='ui--AddressToggle-address'
noLookup={noLookup}
value={address}
withSidebar={false}
/>
{!noToggle && (
<div className='ui--AddressToggle-toggle'>

View File

@ -0,0 +1,100 @@
// Copyright 2017-2020 @polkadot/app-parachains 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';
interface Props {
children?: React.ReactNode;
className?: string;
color?: string;
icon: React.ReactNode;
isBig?: boolean;
title: React.ReactNode;
subtitle: React.ReactNode;
}
function AvatarItem ({ children, className, icon, isBig, subtitle, title }: Props): React.ReactElement<Props> {
return (
<div className={['ui--AvatarItem', className, isBig ? 'big' : ''].join(' ')}>
<div className='ui--AvatarItem-icon'>
{icon}
</div>
<div className='ui--AvatarItem-details'>
<div className='ui--AvatarItem-title'>
{title}
</div>
<div className='ui--AvatarItem-subtitle'>
{subtitle}
</div>
</div>
{children}
</div>
);
}
export default React.memo(styled(AvatarItem)`
& {
display: flex;
align-items: center;
.ui--AvatarItem-icon {
width: 2.4rem;
height: 2.4rem;
margin-right: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
> * {
border-radius: 50%;
width: 100%;
height: 100%;
}
> i.icon {
color: white;
line-height: 2.4rem;
margin-right: 0 !important;
}
> img {
}
}
.ui--AvatarItem-details {
flex: 1;
.ui--AvatarItem-title {
font-weight: bold;
font-size: 1rem;
}
.ui--AvatarItem-subtitle {
color: rgba(100, 100, 100, 0.6);
font-size: 1rem;
}
}
&.big {
.ui--AvatarItem-icon {
width: 3.4rem;
height: 3.4rem;
margin-right: 0.6rem;
> i.icon {
font-size: 1.6rem;
line-height: 3.4rem;
}
}
.ui--AvatarItem-details {
.ui--AvatarItem-name {
font-size: 1.4rem;
line-height: 1.4rem;
}
}
}
}
`);

View File

@ -14,9 +14,11 @@ import Tooltip from '../Tooltip';
let idCounter = 0;
function Button ({ children, className, floated, icon, isBasic = false, isCircular = false, isDisabled = false, isFluid = false, isIcon, isLoading = false, isNegative = false, isPositive = false, isPrimary = false, label, labelPosition, onClick, size, style, tabIndex, tooltip }: ButtonProps): React.ReactElement<ButtonProps> {
function Button ({ children, className, floated, icon, isAnimated, isBasic = false, isCircular = false, isDisabled = false, isFluid = false, isIcon, isLoading = false, isNegative = false, isPositive = false, isPrimary = false, label, labelPosition, onClick, onMouseEnter, onMouseLeave, size, style, tabIndex, tooltip }: ButtonProps): React.ReactElement<ButtonProps> {
const [triggerId] = useState(`button-${++idCounter}`);
const props = {
animate: 'fade',
animated: isAnimated,
basic: isBasic,
circular: isCircular,
className: `${className} ${isIcon && 'isIcon'}`,
@ -29,6 +31,8 @@ function Button ({ children, className, floated, icon, isBasic = false, isCircul
loading: isLoading,
negative: isNegative,
onClick,
onMouseEnter,
onMouseLeave,
positive: isPositive,
primary: isPrimary,
secondary: !isBasic && !(isPositive || isPrimary || isNegative),

View File

@ -0,0 +1,19 @@
// Copyright 2017-2020 @polkadot/react-components authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import React from 'react';
import SUIButton from 'semantic-ui-react/dist/commonjs/elements/Button/Button';
import { ContentProps as Props } from './types';
export default function ButtonContent ({ children, hidden, visible }: Props): React.ReactElement<Props> {
return (
<SUIButton.Content
hidden={hidden}
visible={visible}
>
{children}
</SUIButton.Content>
);
}

View File

@ -5,12 +5,14 @@
import { ButtonType } from './types';
import IButton from './Button';
import Content from './Content';
import Divider from './Divider';
import Group from './Group';
import Or from './Or';
const Button = IButton as unknown as ButtonType;
Button.Content = Content;
Button.Divider = Divider;
Button.Group = Group;
Button.Or = Or;

View File

@ -2,16 +2,16 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { ButtonProps as SUIButtonProps } from 'semantic-ui-react/dist/commonjs/elements/Button/Button';
import { BareProps } from '../types';
export type Button$Sizes = 'mini' | 'tiny' | 'small' | 'medium' | 'large' | 'big' | 'huge' | 'massive';
export type Button$OnClick = () => void | Promise<void>;
export type Button$Callback = () => void | Promise<void>;
export interface ButtonProps extends BareProps {
children?: React.ReactNode;
floated?: 'left' | 'right';
icon: string;
icon?: string;
isAnimated?: SUIButtonProps['animated'];
isBasic?: boolean;
isCircular?: boolean;
isDisabled?: boolean;
@ -23,15 +23,23 @@ export interface ButtonProps extends BareProps {
isPrimary?: boolean;
label?: React.ReactNode;
labelPosition?: 'left' | 'right';
onClick?: Button$OnClick;
onClick?: Button$Callback;
onMouseEnter?: Button$Callback;
onMouseLeave?: Button$Callback;
ref?: any;
size?: Button$Sizes;
size?: SUIButtonProps['size'];
tabIndex?: number;
tooltip?: React.ReactNode;
}
export type DividerProps = BareProps;
export interface ContentProps extends BareProps {
children?: React.ReactNode;
hidden?: boolean;
visible?: boolean;
}
export interface GroupProps extends BareProps {
children?: React.ReactNode;
isBasic?: boolean;
@ -43,6 +51,7 @@ export type GroupType = React.ComponentType<GroupProps> & {
};
export type ButtonType = React.ComponentType<ButtonProps> & {
Content: React.ComponentType<ContentProps>;
Divider: React.ComponentType<DividerProps>;
Group: GroupType;
Or: React.ComponentType<BareProps>;

View File

@ -10,16 +10,22 @@ import styled from 'styled-components';
import Icon from './Icon';
interface Props extends BareProps {
href?: string;
icon?: string;
label?: React.ReactNode;
rel?: string;
target?: string;
onClick: () => void;
}
function IconLink ({ className, icon, label, onClick }: Props): React.ReactElement<Props> {
function IconLink ({ className, href, icon, label, onClick, rel, target }: Props): React.ReactElement<Props> {
return (
<a
className={className}
href={href}
onClick={onClick}
rel={rel}
target={target}
>
{icon && <Icon className={icon} />}
{label}

View File

@ -25,13 +25,14 @@ function IdentityIcon ({ className, onCopy, prefix, size, theme, value }: Props)
const { queueAction } = useContext(StatusContext);
const validators = useContext(ValidatorsContext);
const [isValidator, setIsValidator] = useState(false);
const [address] = useState(value?.toString());
const [address, setAddress] = useState(value?.toString());
const thisTheme = theme || getIdentityTheme(systemName);
useEffect((): void => {
value && setIsValidator(
validators.includes(value.toString())
);
value && setAddress(value.toString());
}, [value, validators]);
const _onCopy = useCallback(

View File

@ -2,11 +2,11 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { BareProps } from './types';
import { BareProps, VoidFn } from './types';
import React, { useCallback, useState } from 'react';
import SUIInput from 'semantic-ui-react/dist/commonjs/elements/Input/Input';
import { isUndefined } from '@polkadot/util';
import { isFunction, isUndefined } from '@polkadot/util';
import Labelled from './Labelled';
@ -33,7 +33,7 @@ interface Props extends BareProps {
maxLength?: number;
min?: any;
name?: string;
onEnter?: () => void;
onEnter?: boolean | VoidFn;
onEscape?: () => void;
onChange?: (value: string) => void;
onBlur?: () => void;
@ -116,7 +116,7 @@ function Input ({ autoFocus = false, children, className, defaultValue, help, ic
if (onEnter && event.keyCode === 13) {
(event.target as any).blur();
onEnter();
isFunction(onEnter) && onEnter();
}
if (onEscape && event.keyCode === 27) {

View File

@ -8,8 +8,8 @@ import BN from 'bn.js';
import React from 'react';
import styled from 'styled-components';
import { BitLengthOption } from '@polkadot/react-components/constants';
import { InputNumber } from '@polkadot/react-components';
import { formatBalance } from '@polkadot/util';
import InputNumber from './InputNumber';
interface Props extends BareProps {
autoFocus?: boolean;

View File

@ -3,23 +3,26 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import React from 'react';
import SUIPopup from 'semantic-ui-react/dist/commonjs/modules/Popup/Popup';
import SUIPopup, { PopupProps } from 'semantic-ui-react/dist/commonjs/modules/Popup/Popup';
interface Props {
children: React.ReactNode;
className?: string;
isOpen?: boolean;
on?: PopupProps['on'];
onClose?: () => void;
position?: PopupProps['position'];
trigger?: React.ReactNode;
}
function Popup ({ children, className, isOpen, onClose, trigger }: Props): React.ReactElement<Props> {
function Popup ({ children, className, isOpen, on = 'click', onClose, position = 'bottom right', trigger }: Props): React.ReactElement<Props> {
return (
<SUIPopup
className={className}
on={on}
onClose={onClose}
open={isOpen}
position='bottom right'
position={position}
trigger={trigger}
>
{children}

View File

@ -0,0 +1,128 @@
// Copyright 2017-2020 @polkadot/app-accounts 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 { SubmittableExtrinsic } from '@polkadot/api/promise/types';
import BN from 'bn.js';
import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
import { useApi } from '@polkadot/react-hooks';
import { Available } from '@polkadot/react-query';
import InputAddress from './InputAddress';
import InputBalance from './InputBalance';
import Modal from './Modal';
import TxButton from './TxButton';
import { useTranslation } from './translate';
interface Props {
className?: string;
onClose: () => void;
recipientId?: string;
senderId?: string;
}
const ZERO = new BN(0);
function Transfer ({ className, onClose, recipientId: propRecipientId, senderId: propSenderId }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const [amount, setAmount] = useState<BN | undefined>(new BN(0));
const [extrinsic, setExtrinsic] = useState<SubmittableExtrinsic | null>(null);
const [hasAvailable] = useState(true);
const [maxBalance] = useState(new BN(0));
const [recipientId, setRecipientId] = useState<string | null>(propRecipientId || null);
const [senderId, setSenderId] = useState<string | null>(propSenderId || null);
useEffect((): void => {
senderId && recipientId && setExtrinsic(
() => api.tx.balances.transfer(recipientId, amount || ZERO)
);
}, [api, amount, recipientId, senderId]);
const transferrable = <span className='label'>{t('transferrable')}</span>;
return (
<Modal
className='app--accounts-Modal'
header={t('Send funds')}
>
<Modal.Content>
<div className={className}>
<InputAddress
defaultValue={propSenderId}
help={t('The account you will send funds from.')}
isDisabled={!!propSenderId}
label={t('send from account')}
labelExtra={
<Available
label={transferrable}
params={senderId}
/>
}
onChange={setSenderId}
type='account'
/>
<InputAddress
defaultValue={propRecipientId}
help={t('Select a contact or paste the address you want to send funds to.')}
isDisabled={!!propRecipientId}
label={t('send to address')}
labelExtra={
<Available
label={transferrable}
params={recipientId}
/>
}
onChange={setRecipientId}
type='allPlus'
/>
<InputBalance
autoFocus
help={t('Type the amount you want to transfer. Note that you can select the unit on the right e.g sending 1 milli is equivalent to sending 0.001.')}
isError={!hasAvailable}
isZeroable
label={t('amount')}
maxValue={maxBalance}
onChange={setAmount}
withMax
/>
</div>
</Modal.Content>
<Modal.Actions onCancel={onClose}>
<TxButton
accountId={senderId}
extrinsic={extrinsic}
icon='send'
isDisabled={!hasAvailable}
isPrimary
label={t('Make Transfer')}
onStart={onClose}
/>
</Modal.Actions>
</Modal>
);
}
export default React.memo(styled(Transfer)`
article.padded {
box-shadow: none;
margin-left: 2rem;
}
.balance {
margin-bottom: 0.5rem;
text-align: right;
padding-right: 1rem;
.label {
opacity: 0.7;
}
}
label.with-help {
flex-basis: 10rem;
}
`);

View File

@ -4,6 +4,7 @@
export { default as AccountIndex } from './AccountIndex';
export { default as AccountName } from './AccountName';
export { default as AccountNameJudgement } from './AccountNameJudgement';
export { default as ActionItem } from './ActionItem';
export { default as AddressInfo } from './AddressInfo';
export { default as AddressMini } from './AddressMini';
@ -11,6 +12,7 @@ export { default as AddressRow } from './AddressRow';
export { default as AddressSmall } from './AddressSmall';
export { default as AddressToggle } from './AddressToggle';
export { default as Available } from './Available';
export { default as AvatarItem } from './AvatarItem';
export { default as Badge } from './Badge';
export { default as Balance } from './Balance';
export { default as Bonded } from './Bonded';
@ -83,6 +85,7 @@ export { default as Tabs } from './Tabs';
export { default as Tag } from './Tag';
export { default as Toggle } from './Toggle';
export { default as Tooltip } from './Tooltip';
export { default as Transfer } from './Transfer';
export { default as TxButton } from './TxButton';
export { default as TxComponent } from './TxComponent';
export { default as TxModal } from './TxModal';

View File

@ -5,18 +5,18 @@
import { css } from 'styled-components';
/* default buttons, dark gray */
const colorBtnDefault = '#666';
export const colorBtnDefault = '#666';
/* highlighted buttons, orange */
const colorBtnHighlight = '#f19135';
export const colorBtnHighlight = '#f19135';
/* primary buttons, blue */
const colorBtnPrimary = colorBtnDefault; // '#2e86ab';
export const colorBtnPrimary = colorBtnDefault; // '#2e86ab';
/* button text color */
const colorBtnText = '#f9f9f9';
export const colorBtnText = '#f9f9f9';
const colorLink = '#2e86ab';
export const colorLink = '#2e86ab';
export default css`
.theme--default {

View File

@ -3,13 +3,14 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { WithTranslation } from 'react-i18next';
import { ButtonProps as SUIButtonProps } from 'semantic-ui-react/dist/commonjs/elements/Button/Button';
import { SubmittableExtrinsic } from '@polkadot/api/promise/types';
import { Abi } from '@polkadot/api-contract';
import { ActionStatus } from '@polkadot/react-components/Status/types';
import { TxState } from '@polkadot/react-hooks/types';
import { IExtrinsic } from '@polkadot/types/types';
import { AccountId, Index } from '@polkadot/types/interfaces';
import { ButtonProps, Button$Sizes } from './Button/types';
import { ButtonProps } from './Button/types';
import { InputAddressProps } from './InputAddress/types';
import { TxCallback, TxFailedCallback } from './Status/types';
@ -55,7 +56,7 @@ export interface TxButtonProps extends TxProps {
accountNonce?: Index;
className?: string;
icon?: string;
iconSize?: Button$Sizes;
iconSize?: SUIButtonProps['size'];
isBasic?: boolean;
isDisabled?: boolean;
isIcon?: boolean;
@ -69,7 +70,7 @@ export interface TxButtonProps extends TxProps {
onStart?: VoidFn;
onSuccess?: TxCallback;
onUpdate?: TxCallback;
size?: Button$Sizes;
size?: SUIButtonProps['size'];
tooltip?: string;
withSpinner?: boolean;
}

View File

@ -3,6 +3,7 @@
// of the Apache-2.0 license. See the LICENSE file for details.
export { default as useAccountId } from './useAccountId';
export { default as useAccountInfo } from './useAccountInfo';
export { default as useAccounts } from './useAccounts';
export { default as useAddresses } from './useAddresses';
export { default as useApi } from './useApi';

View File

@ -2,7 +2,8 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { ConstructTxFn, StringOrNull } from '@polkadot/react-components/types';
import { DeriveAccountFlags, DeriveAccountRegistration } from '@polkadot/api-derive/types';
import { ConstructTxFn, StringOrNull, VoidFn } from '@polkadot/react-components/types';
import { AccountId, Balance, BlockNumber, Call, Hash, SessionIndex } from '@polkadot/types/interfaces';
import { IExtrinsic } from '@polkadot/types/types';
import { SubmittableExtrinsic } from '@polkadot/api/promise/types';
@ -72,3 +73,41 @@ export interface UseSudo {
sudoKey?: string;
isMine: boolean;
}
export interface AddressFlags extends DeriveAccountFlags {
isDevelopment: boolean;
isEditable: boolean;
isExternal: boolean;
isFavorite: boolean;
isInContacts: boolean;
isOwned: boolean;
}
export interface AddressIdentity extends DeriveAccountRegistration {
isGood: boolean;
isBad: boolean;
isKnownGood: boolean;
isReasonable: boolean;
isErroneous: boolean;
isLowQuality: boolean;
isExistent: boolean;
waitCount: number;
}
export interface UseAccountInfo extends AddressFlags {
name: string;
setName: React.Dispatch<string>;
tags: string[];
setTags: React.Dispatch<string[]>;
genesisHash: StringOrNull;
setGenesisHash: React.Dispatch<StringOrNull>;
identity?: AddressIdentity;
isEditingName: boolean;
toggleIsEditingName: VoidFn;
isEditingTags: boolean;
toggleIsEditingTags: VoidFn;
onSaveName: VoidFn;
onSaveTags: VoidFn;
onSaveGenesisHash: VoidFn;
onForgetAddress: VoidFn;
}

View File

@ -0,0 +1,206 @@
// Copyright 2017-2020 @polkadot/react-hooks 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 { DeriveAccountFlags, DeriveAccountInfo } from '@polkadot/api-derive/types';
import { StringOrNull } from '@polkadot/react-components/types';
import { Address, AccountId } from '@polkadot/types/interfaces';
import { AddressFlags, AddressIdentity, UseAccountInfo } from './types';
import { useCallback, useEffect, useState } from 'react';
import keyring from '@polkadot/ui-keyring';
import useAccounts from './useAccounts';
import useAddresses from './useAddresses';
import useApi from './useApi';
import useCall from './useCall';
import useToggle from './useToggle';
const IS_NONE = {
isCouncil: false,
isDevelopment: false,
isEditable: false,
isExternal: false,
isFavorite: false,
isInContacts: false,
isOwned: false,
isSociety: false,
isSudo: false,
isTechCommittee: false
};
export default function useAccountInfo (_value: AccountId | Address | string | Uint8Array): UseAccountInfo {
const value = _value.toString();
const { api } = useApi();
const accountInfo = useCall<DeriveAccountInfo>(api.derive.accounts.info as any, [value]);
const accountFlags = useCall<DeriveAccountFlags>(api.derive.accounts.flags as any, [value]);
const { isAccount } = useAccounts();
const { isAddress } = useAddresses();
const [tags, setSortedTags] = useState<string[]>([]);
const [name, setName] = useState('');
const [genesisHash, setGenesisHash] = useState<StringOrNull>(null);
const [identity, setIdentity] = useState<AddressIdentity | undefined>();
const [flags, setFlags] = useState<AddressFlags>(IS_NONE);
const [isEditingName, toggleIsEditingName] = useToggle(false);
const [isEditingTags, toggleIsEditingTags] = useToggle(false);
useEffect((): void => {
accountFlags && setFlags((flags) => ({
...flags,
...accountFlags
}));
}, [accountFlags]);
useEffect((): void => {
const { identity, nickname } = accountInfo || {};
if (api.query.identity && api.query.identity.identityOf) {
if (identity?.display) {
setName(identity.display);
}
} else if (nickname) {
setName(nickname);
} else {
setName('');
}
if (identity) {
const judgements = identity.judgements.filter(([, judgement]) => !judgement.isFeePaid);
const isKnownGood = judgements.some(([, judgement]) => judgement.isKnownGood);
const isReasonable = judgements.some(([, judgement]) => judgement.isReasonable);
const isErroneous = judgements.some(([, judgement]) => judgement.isErroneous);
const isLowQuality = judgements.some(([, judgement]) => judgement.isLowQuality);
setIdentity({
...identity,
isBad: isErroneous || isLowQuality,
isErroneous,
isExistent: !!identity.display,
isGood: isKnownGood || isReasonable,
isKnownGood,
isLowQuality,
isReasonable,
judgements,
waitCount: identity.judgements.length - judgements.length
});
} else {
setIdentity(undefined);
}
}, [accountInfo, api]);
useEffect((): void => {
const accountOrAddress = keyring.getAccount(value) || keyring.getAddress(value);
const isOwned = isAccount(value);
const isInContacts = isAddress(value);
setGenesisHash(accountOrAddress?.meta.genesisHash || null);
setFlags((flags) => ({
...flags,
isDevelopment: accountOrAddress?.meta.isTesting || false,
isEditable: (!identity?.display && (isInContacts || (accountOrAddress && !(accountOrAddress.meta.isInjected || accountOrAddress.meta.isHardware)))) || false,
isExternal: accountOrAddress?.meta.isExternal || false,
isInContacts,
isOwned
}));
setName(accountOrAddress?.meta.name || '');
setSortedTags(accountOrAddress?.meta.tags ? accountOrAddress.meta.tags.sort() : []);
}, [identity, isAccount, isAddress, value]);
const onSaveName = useCallback(
(): void => {
if (isEditingName) {
toggleIsEditingName();
}
const meta = { name, whenEdited: Date.now() };
if (value) {
try {
const currentKeyring = keyring.getPair(value);
currentKeyring && keyring.saveAccountMeta(currentKeyring, meta);
} catch (error) {
keyring.saveAddress(value, meta);
}
}
},
[isEditingName, name, toggleIsEditingName, value]
);
const onSaveTags = useCallback(
(): void => {
if (isEditingTags) {
toggleIsEditingTags();
}
const meta = { tags, whenEdited: Date.now() };
if (value) {
try {
const currentKeyring = keyring.getPair(value);
currentKeyring && keyring.saveAccountMeta(currentKeyring, meta);
} catch (error) {
keyring.saveAddress(value, meta);
}
}
},
[isEditingTags, tags, toggleIsEditingTags, value]
);
const onForgetAddress = useCallback(
(): void => {
if (isEditingName) {
toggleIsEditingName();
}
if (isEditingTags) {
toggleIsEditingTags();
}
try {
keyring.forgetAddress(value);
} catch (e) {
console.error(e);
}
},
[isEditingName, isEditingTags, toggleIsEditingName, toggleIsEditingTags, value]
);
const onSaveGenesisHash = useCallback(
(): void => {
const account = keyring.getPair(value);
account && keyring.saveAccountMeta(account, { ...account.meta, genesisHash });
setGenesisHash(genesisHash);
},
[genesisHash, value]
);
const setTags = useCallback(
(tags: string[]): void => setSortedTags(tags.sort()),
[]
);
return {
genesisHash,
identity,
isEditingName,
isEditingTags,
name,
onForgetAddress,
onSaveGenesisHash,
onSaveName,
onSaveTags,
setGenesisHash,
setName,
setTags,
tags,
toggleIsEditingName,
toggleIsEditingTags,
...flags
};
}

View File

@ -3,6 +3,7 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { useEffect, useState } from 'react';
import { AccountId, Address } from '@polkadot/types/interfaces';
import accountObservable from '@polkadot/ui-keyring/observable/accounts';
import useIsMountedRef from './useIsMountedRef';
@ -10,19 +11,21 @@ import useIsMountedRef from './useIsMountedRef';
interface UseAccounts {
allAccounts: string[];
hasAccounts: boolean;
isAccount: (_: string | AccountId | Address) => boolean;
}
export default function useAccounts (): UseAccounts {
const mountedRef = useIsMountedRef();
const [state, setState] = useState<UseAccounts>({ allAccounts: [], hasAccounts: false });
const [state, setState] = useState<UseAccounts>({ allAccounts: [], hasAccounts: false, isAccount: () => false });
useEffect((): () => void => {
const subscription = accountObservable.subject.subscribe((accounts): void => {
if (mountedRef.current) {
const allAccounts = accounts ? Object.keys(accounts) : [];
const hasAccounts = allAccounts.length !== 0;
const isAccount = (accountId: string | AccountId | Address): boolean => allAccounts.includes(accountId.toString());
setState({ allAccounts, hasAccounts });
setState({ allAccounts, hasAccounts, isAccount });
}
});

View File

@ -3,6 +3,7 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { useEffect, useState } from 'react';
import { AccountId, Address } from '@polkadot/types/interfaces';
import addressObservable from '@polkadot/ui-keyring/observable/addresses';
import useIsMountedRef from './useIsMountedRef';
@ -10,19 +11,21 @@ import useIsMountedRef from './useIsMountedRef';
interface UseAccounts {
allAddresses: string[];
hasAddresses: boolean;
isAddress: (_: string | AccountId | Address) => boolean;
}
export default function useAccounts (): UseAccounts {
const mountedRef = useIsMountedRef();
const [state, setState] = useState<UseAccounts>({ allAddresses: [], hasAddresses: false });
const [state, setState] = useState<UseAccounts>({ allAddresses: [], hasAddresses: false, isAddress: () => false });
useEffect((): () => void => {
const subscription = addressObservable.subject.subscribe((addresses): void => {
if (mountedRef.current) {
const allAddresses = addresses ? Object.keys(addresses) : [];
const hasAddresses = allAddresses.length !== 0;
const isAddress = (address: string | AccountId | Address): boolean => allAddresses.includes(address.toString());
setState({ allAddresses, hasAddresses });
setState({ allAddresses, hasAddresses, isAddress });
}
});

110
yarn.lock
View File

@ -3893,9 +3893,9 @@ __metadata:
linkType: hard
"@types/node@npm:*, @types/node@npm:>= 8":
version: 13.13.0
resolution: "@types/node@npm:13.13.0"
checksum: 2/30bc5a1ade71f18de9a4324b273d140a131310e92245a449b39c3aaa1271ca03d6a59b79cb12154bbedd8960f99d184e0d757e53fe704d6d1736d9567696468e
version: 13.13.2
resolution: "@types/node@npm:13.13.2"
checksum: 2/de03e2f34b92655ae6f89abfed3362559751d0ae4f35905b6a931dca03e1e9cb4f51b104f509661b1df03baafdaab5b44d6a142620c67cb907af821f5481df32
languageName: node
linkType: hard
@ -4776,11 +4776,11 @@ __metadata:
linkType: hard
"acorn-import-meta@npm:^1.0.0":
version: 1.0.0
resolution: "acorn-import-meta@npm:1.0.0"
version: 1.1.0
resolution: "acorn-import-meta@npm:1.1.0"
peerDependencies:
acorn: ^6.0.0
checksum: 2/c39d4fc960653bdb5e61ed2d0982afd84e185b2f5ba2e4139a60e456305df558d10d3a969508bf4c1f621bdf064c7d82db4f33ac3eda2e19f39b28b9d728b532
acorn: ^6.0.0 || ^7.0.0
checksum: 2/16facde86452de81c367bc31dcc90778b7b762cd6f666b1ddd7fec375e0261aa7a758a5f118cad0bc94b1acfbf5036c4bf657788693832b2db4ad3bc5b49732f
languageName: node
linkType: hard
@ -4859,7 +4859,7 @@ __metadata:
languageName: node
linkType: hard
"acorn@npm:^6.0.0, acorn@npm:^6.0.1, acorn@npm:^6.2.1":
"acorn@npm:^6.0.0, acorn@npm:^6.0.1, acorn@npm:^6.4.1":
version: 6.4.1
resolution: "acorn@npm:6.4.1"
bin:
@ -5511,11 +5511,11 @@ __metadata:
linkType: hard
"babel-plugin-dynamic-import-node@npm:^2.3.0":
version: 2.3.0
resolution: "babel-plugin-dynamic-import-node@npm:2.3.0"
version: 2.3.3
resolution: "babel-plugin-dynamic-import-node@npm:2.3.3"
dependencies:
object.assign: ^4.1.0
checksum: 2/2987dc15b8d07b2527aa1019d96e56d65eeb2fa596f67231fc39cc739d68ccd7f477dc3f9c40245de25a90ea76e8619e6ecd1569b65bcfcf15db097d81aa6cd2
checksum: 2/6745b8edca96f6c8bc34ab65935b5676358d2e55323e8e823b8de7aa353e3e6398a495ce434c9c36ad5fb1609467a1b1a0028946e1490bf7de8f97df3ae7f3b1
languageName: node
linkType: hard
@ -5987,16 +5987,16 @@ __metadata:
linkType: hard
"browserslist@npm:^4.0.0, browserslist@npm:^4.11.1, browserslist@npm:^4.8.5, browserslist@npm:^4.9.1":
version: 4.11.1
resolution: "browserslist@npm:4.11.1"
version: 4.12.0
resolution: "browserslist@npm:4.12.0"
dependencies:
caniuse-lite: ^1.0.30001038
electron-to-chromium: ^1.3.390
caniuse-lite: ^1.0.30001043
electron-to-chromium: ^1.3.413
node-releases: ^1.1.53
pkg-up: ^2.0.0
bin:
browserslist: cli.js
checksum: 2/fdf7a5ab90fac4dd9ffbcc5c219afbe30a8deb16b2d6fe4277cf4271e12b59f1b464ace29fb4ff24896a11e14a02f4cc27a6a560575d2767b77a629d073deea4
checksum: 2/564af87b3300321d885c22b6fb010a4d702b1cc77591d684d8f79411d5df65b9290a043348bcb5e4ca96b423c703aeb83fa25979ee3b140b28c48fc965dea0ed
languageName: node
linkType: hard
@ -6351,10 +6351,10 @@ __metadata:
languageName: node
linkType: hard
"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001038, caniuse-lite@npm:^1.0.30001039":
version: 1.0.30001043
resolution: "caniuse-lite@npm:1.0.30001043"
checksum: 2/142c04989837e6332a1d1ac451df98ef9ab08097df4e1c54b4d8a57dd496d0014edbc749834921e7d1cd5306a1cd6264daec5d5743214ea3d28019b5e70fe74c
"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001039, caniuse-lite@npm:^1.0.30001043":
version: 1.0.30001045
resolution: "caniuse-lite@npm:1.0.30001045"
checksum: 2/b9b393d506d4c771dff786d040eb8b5e988311f691c59df2c24644604e2b86a06d00b4b0499453e67feee0e4a8d02ead6f115d47a548a075095ecb7c56c72ebb
languageName: node
linkType: hard
@ -7371,8 +7371,8 @@ __metadata:
linkType: hard
"coveralls@npm:^3.0.11":
version: 3.0.11
resolution: "coveralls@npm:3.0.11"
version: 3.0.13
resolution: "coveralls@npm:3.0.13"
dependencies:
js-yaml: ^3.13.1
lcov-parse: ^1.0.0
@ -7381,7 +7381,7 @@ __metadata:
request: ^2.88.0
bin:
coveralls: ./bin/coveralls.js
checksum: 2/8bf464950dda177a07f930109ddc8cc3779ddb1d0d3b666789883d6234f4094922dbd93e00ed5917c2535a1fea378c328e8fde4be336b5c5ac8b4509ef797e4e
checksum: 2/d573474ef85762d7859ffe494daef5fb32d9b92a27779e34adacb4ea60aa74b59df72b34b12a1d3a3805e9715407daa74e5f7c7f976960f7e3971c57aa000cbb
languageName: node
linkType: hard
@ -8693,10 +8693,10 @@ __metadata:
languageName: node
linkType: hard
"electron-to-chromium@npm:^1.3.390":
version: 1.3.413
resolution: "electron-to-chromium@npm:1.3.413"
checksum: 2/5647038890ac4dc6e657b2951e1cbbe0e1cd66ccfaa27ca7677f6eb2b5df46e838e7f38c70c947f19f96432924f4ba64f8bb92b986919e03494acd0986ea6827
"electron-to-chromium@npm:^1.3.413":
version: 1.3.414
resolution: "electron-to-chromium@npm:1.3.414"
checksum: 2/e5c162e58042617f4c333bd4e8defca84d07fd251589a4e8aeaa790ebcbeed1175da6b9bf4fc9544422333d85831698a26003c5cb34d72a3162e14fc493335c1
languageName: node
linkType: hard
@ -11540,11 +11540,11 @@ __metadata:
linkType: hard
"i18next@npm:*, i18next@npm:^19.4.2":
version: 19.4.2
resolution: "i18next@npm:19.4.2"
version: 19.4.3
resolution: "i18next@npm:19.4.3"
dependencies:
"@babel/runtime": ^7.3.1
checksum: 2/122cc93821947f427a66ef1a595bb278ad1b16210552772a903f64a5af3726dd190220ddda7bd1d9de05494058264a4df9244180a18f9c69f723a86c7cfe5412
checksum: 2/a9f2527abdbfcfa1b117d84b18653b6aee90fd6778676daf7b8c205630b4c06a9d18b83241b862b18f7f916f95883de58f00d9fbc18cfa1d83036a7a7ae132b8
languageName: node
linkType: hard
@ -15106,11 +15106,11 @@ __metadata:
linkType: hard
"nan@npm:^2.12.1, nan@npm:^2.14.0":
version: 2.14.0
resolution: "nan@npm:2.14.0"
version: 2.14.1
resolution: "nan@npm:2.14.1"
dependencies:
node-gyp: latest
checksum: 2/988248a5f141b9ff728d00927607af857564707fb480de7dca775126af3ea5d7fe231958139fb866931742525ef0c0ca9c8d161188df81e1fa5fd79de1e2adc6
checksum: 2/eeab7cf260362a578f0b8622716a76d19bc009722049c7274748644ce03b2aa38ca01b3ac730a0497fd2c1ec882a21a0592e800a903994ed4d32acd06bf7eba7
languageName: node
linkType: hard
@ -15943,8 +15943,8 @@ __metadata:
linkType: hard
"ora@npm:^4.0.2":
version: 4.0.3
resolution: "ora@npm:4.0.3"
version: 4.0.4
resolution: "ora@npm:4.0.4"
dependencies:
chalk: ^3.0.0
cli-cursor: ^3.1.0
@ -15954,7 +15954,7 @@ __metadata:
mute-stream: 0.0.8
strip-ansi: ^6.0.0
wcwidth: ^1.0.1
checksum: 2/cd84ed2c776b595af9abdef274e17d74ada10bfee88ba919219902a547a0dc853ba24f46f4ec30e589cbb92bae574e406c4169d6f197cf15c279e12a403763ff
checksum: 2/120595347066b1749b099015cbee3bda201904b5eff41f72ada2d06a0ebbd54bb2d765d3bfe8da09c61bc1894364e689cb085f65f71d95768acaf4f3ae274a91
languageName: node
linkType: hard
@ -19611,12 +19611,12 @@ __metadata:
linkType: hard
"source-map-support@npm:^0.5.16, source-map-support@npm:^0.5.6, source-map-support@npm:~0.5.12":
version: 0.5.17
resolution: "source-map-support@npm:0.5.17"
version: 0.5.18
resolution: "source-map-support@npm:0.5.18"
dependencies:
buffer-from: ^1.0.0
source-map: ^0.6.0
checksum: 2/d2fd6d9a9e924c992fe9c026c9a24fd6ea18adee82462075c09aeb9e78202f2c89a7d9f8b22772c19bffce0bfa1d21a95adee3607bb828b81b5439c98a4eb411
checksum: 2/f3146ec8ab3c099a875eece31f2c7144d1ac3d220af3d624c3a63c82d7273047fb21622225a5eaac5a6cc9be9cc8dba62104b40089450185de25743ae277e9d5
languageName: node
linkType: hard
@ -19675,9 +19675,9 @@ __metadata:
linkType: hard
"spdx-exceptions@npm:^2.1.0":
version: 2.2.0
resolution: "spdx-exceptions@npm:2.2.0"
checksum: 2/748c042fb1928b5ece6b5ae939ef091207e0c45066419d6c6a4944e21219c038a2a1a6df60d6b1adf3a600cd1da846d72adb246b09d9b8ef56c12b5d92bbfc01
version: 2.3.0
resolution: "spdx-exceptions@npm:2.3.0"
checksum: 2/3cbd2498897dc384158666a9dd7435e3b42ece5da42fd967b218b790e248381d001ec77a676d13d1f4e8da317d97b7bc0ebf4fff37bfbb95923d49b024030c96
languageName: node
linkType: hard
@ -20297,8 +20297,8 @@ __metadata:
linkType: hard
"stylelint@npm:^13.3.2":
version: 13.3.2
resolution: "stylelint@npm:13.3.2"
version: 13.3.3
resolution: "stylelint@npm:13.3.3"
dependencies:
"@stylelint/postcss-css-in-js": ^0.37.1
"@stylelint/postcss-markdown": ^0.36.1
@ -20350,7 +20350,7 @@ __metadata:
write-file-atomic: ^3.0.3
bin:
stylelint: bin/stylelint.js
checksum: 2/3afc4f2c252b69f5b2fabe805bbbba1c8da585ff20db040e77992c31a900887a87b06a4eeeb015bf2d33e70e07f25adeafda6ad5a8fb3004ce3d99ecb57bf8d7
checksum: 2/b523f716580cb4490597f659e254f1c178f8da9351b2d0cb8482d291a802e32f14097153d65fb3f1ddc3a70dfc32edba64c3dae5b5d82acf046b21ee89b8e532
languageName: node
linkType: hard
@ -22208,7 +22208,7 @@ __metadata:
languageName: node
linkType: hard
"watchpack@npm:^1.6.0":
"watchpack@npm:^1.6.1":
version: 1.6.1
resolution: "watchpack@npm:1.6.1"
dependencies:
@ -22427,15 +22427,15 @@ __metadata:
languageName: node
linkType: hard
"webpack@npm:4.42.1, webpack@npm:^4.42.1, webpack@npm:^4.8.1":
version: 4.42.1
resolution: "webpack@npm:4.42.1"
"webpack@npm:4.43.0, webpack@npm:^4.42.1, webpack@npm:^4.8.1":
version: 4.43.0
resolution: "webpack@npm:4.43.0"
dependencies:
"@webassemblyjs/ast": 1.9.0
"@webassemblyjs/helper-module-context": 1.9.0
"@webassemblyjs/wasm-edit": 1.9.0
"@webassemblyjs/wasm-parser": 1.9.0
acorn: ^6.2.1
acorn: ^6.4.1
ajv: ^6.10.2
ajv-keywords: ^3.4.1
chrome-trace-event: ^1.0.2
@ -22452,11 +22452,11 @@ __metadata:
schema-utils: ^1.0.0
tapable: ^1.1.3
terser-webpack-plugin: ^1.4.3
watchpack: ^1.6.0
watchpack: ^1.6.1
webpack-sources: ^1.4.1
bin:
webpack: bin/webpack.js
checksum: 2/c951f5b8728b1e7000b69322d41f643598aaab5ff0f5f9c30ec1eb7942b0454fa4ac80971c0cbfb3e5eeff6d50a88af5fc6632109b5a47aa666cc38684074fc8
checksum: 2/9f1e6375ba28368b4a0d02a0807aa4e148b101244d7ab698c99ac478e3043cdacf7d7c89aa7d4af9549509be7d23878ef863bd6a9540e89a18dd2c04e82e2b4f
languageName: node
linkType: hard
@ -22862,11 +22862,11 @@ __metadata:
linkType: hard
"yaml@npm:^1.7.2":
version: 1.9.1
resolution: "yaml@npm:1.9.1"
version: 1.9.2
resolution: "yaml@npm:1.9.2"
dependencies:
"@babel/runtime": ^7.9.2
checksum: 2/56973fe2f959d5e36952c5c676439fa399d42c5a169b9f001574198fd654d3a6038e6429549f20df3f422af5130fcc22c05979fb95824be3fcf92833dd483884
checksum: 2/098baa974d33f13c3a93efba4e83c8992f533e78463010afc41a9c2ae574890fa670d91e8d18ffaf9af616c3a0683a4e93b69057bd143f53306570a117ebcb7f
languageName: node
linkType: hard