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:
parent
127ba36c9d
commit
9703811f8b
@ -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>
|
||||
|
||||
@ -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
|
||||
/>
|
||||
</>
|
||||
|
||||
@ -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 {
|
||||
|
||||
575
packages/page-accounts/src/Sidebar/Sidebar.tsx
Normal file
575
packages/page-accounts/src/Sidebar/Sidebar.tsx
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
43
packages/page-accounts/src/Sidebar/index.tsx
Normal file
43
packages/page-accounts/src/Sidebar/index.tsx
Normal 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);
|
||||
@ -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} />}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -48,6 +48,7 @@ function AccountNameJudgement ({ address, registrars, toggleJudgement }: Props):
|
||||
return (
|
||||
<Modal
|
||||
header={t('Provide judgement')}
|
||||
onClose={toggleJudgement}
|
||||
size='small'
|
||||
>
|
||||
<Modal.Content>
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
@ -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'>
|
||||
|
||||
100
packages/react-components/src/AvatarItem.tsx
Normal file
100
packages/react-components/src/AvatarItem.tsx
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
@ -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),
|
||||
|
||||
19
packages/react-components/src/Button/Content.tsx
Normal file
19
packages/react-components/src/Button/Content.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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>;
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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}
|
||||
|
||||
128
packages/react-components/src/Transfer.tsx
Normal file
128
packages/react-components/src/Transfer.tsx
Normal 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;
|
||||
}
|
||||
`);
|
||||
@ -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';
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
206
packages/react-hooks/src/useAccountInfo.ts
Normal file
206
packages/react-hooks/src/useAccountInfo.ts
Normal 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
|
||||
};
|
||||
}
|
||||
@ -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 });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -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
110
yarn.lock
@ -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
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user