Rework address & accounts layout (#2008)
* Display accounts as rows * Tags display * Accounts seem ok (as a start) * Filter by tags * Don't add new tags via input * Convert addresses * Adjustments
This commit is contained in:
parent
c5a85676d5
commit
ee83086c4a
@ -6,8 +6,9 @@ import { ActionStatus } from '@polkadot/react-components/Status/types';
|
||||
import { I18nProps } from '@polkadot/react-components/types';
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Label } from 'semantic-ui-react';
|
||||
import styled from 'styled-components';
|
||||
import { AddressCard, AddressInfo, Button, ChainLock, Forget, Menu, Popup } from '@polkadot/react-components';
|
||||
import { AddressInfo, AddressSmall, Button, ChainLock, Forget, Icon, InputTags, LinkPolkascan, Menu, Popup, Input } from '@polkadot/react-components';
|
||||
import { useApi } from '@polkadot/react-hooks';
|
||||
import keyring from '@polkadot/ui-keyring';
|
||||
|
||||
@ -19,19 +20,27 @@ import translate from './translate';
|
||||
|
||||
interface Props extends I18nProps {
|
||||
address: string;
|
||||
allowTags: string[];
|
||||
className?: string;
|
||||
isFavorite: boolean;
|
||||
toggleFavorite: (address: string) => void;
|
||||
}
|
||||
|
||||
function Account ({ address, className, t }: Props): React.ReactElement<Props> {
|
||||
function Account ({ address, allowTags, className, isFavorite, t, toggleFavorite }: Props): React.ReactElement<Props> | null {
|
||||
const api = useApi();
|
||||
const [tags, setTags] = useState<string[]>([]);
|
||||
const [accName, setAccName] = useState('');
|
||||
const [genesisHash, setGenesisHash] = useState<string | null>(null);
|
||||
const [isBackupOpen, setIsBackupOpen] = useState(false);
|
||||
const [isEditingName, setIsEditingName] = useState(false);
|
||||
const [isEditingTags, setIsEditingTags] = useState(false);
|
||||
const [{ isDevelopment, isEditable, isExternal }, setFlags] = useState({ isDevelopment: false, isEditable: false, isExternal: false });
|
||||
const [isDeriveOpen, setIsDeriveOpen] = useState(false);
|
||||
const [isForgetOpen, setIsForgetOpen] = useState(false);
|
||||
const [isPasswordOpen, setIsPasswordOpen] = useState(false);
|
||||
const [isSettingPopupOpen, setIsSettingPopupOpen] = useState(false);
|
||||
const [isTransferOpen, setIsTransferOpen] = useState(false);
|
||||
const [isVisible, setIsVisible] = useState(true);
|
||||
|
||||
useEffect((): void => {
|
||||
const account = keyring.getAccount(address);
|
||||
@ -42,14 +51,64 @@ function Account ({ address, className, t }: Props): React.ReactElement<Props> {
|
||||
isEditable: (account && !(account.meta.isInjected || account.meta.isHardware)) || false,
|
||||
isExternal: (account && account.meta.isExternal) || false
|
||||
});
|
||||
setTags(account?.meta?.tags || []);
|
||||
setAccName(account?.meta?.name || '');
|
||||
}, [address]);
|
||||
|
||||
useEffect((): void => {
|
||||
if (allowTags.length === 0) {
|
||||
setIsVisible(true);
|
||||
} else {
|
||||
setIsVisible(
|
||||
allowTags.reduce((result: boolean, tag: string): boolean => {
|
||||
return result || tags.includes(tag);
|
||||
}, false)
|
||||
);
|
||||
}
|
||||
}, [allowTags, tags]);
|
||||
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const _toggleEditName = (): void => setIsEditingName(!isEditingName);
|
||||
const _toggleEditTags = (): void => setIsEditingTags(!isEditingTags);
|
||||
const _toggleBackup = (): void => setIsBackupOpen(!isBackupOpen);
|
||||
const _toggleDerive = (): void => setIsDeriveOpen(!isDeriveOpen);
|
||||
const _toggleForget = (): void => setIsForgetOpen(!isForgetOpen);
|
||||
const _togglePass = (): void => setIsPasswordOpen(!isPasswordOpen);
|
||||
const _toggleSettingPopup = (): void => setIsSettingPopupOpen(!isSettingPopupOpen);
|
||||
const _toggleTransfer = (): void => setIsTransferOpen(!isTransferOpen);
|
||||
const _saveName = (): 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
const _saveTags = (): 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
const _onForget = (): void => {
|
||||
if (!address) {
|
||||
return;
|
||||
@ -76,137 +135,193 @@ function Account ({ address, className, t }: Props): React.ReactElement<Props> {
|
||||
|
||||
setGenesisHash(genesisHash);
|
||||
};
|
||||
const _onFavorite = (): void => toggleFavorite(address);
|
||||
|
||||
// FIXME It is a bit heavy-handled switching of being editable here completely
|
||||
// (and removing the tags, however the keyring cannot save these)
|
||||
return (
|
||||
<AddressCard
|
||||
buttons={
|
||||
<div className='accounts--Account-buttons buttons'>
|
||||
<div className='actions'>
|
||||
<Button
|
||||
icon='paper plane'
|
||||
isPrimary
|
||||
label={t('send')}
|
||||
onClick={_toggleTransfer}
|
||||
size='small'
|
||||
tooltip={t('Send funds from this account')}
|
||||
/>
|
||||
<Popup
|
||||
className='theme--default'
|
||||
onClose={_toggleSettingPopup}
|
||||
open={isSettingPopupOpen}
|
||||
position='bottom right'
|
||||
trigger={
|
||||
<Button
|
||||
icon='setting'
|
||||
onClick={_toggleSettingPopup}
|
||||
size='small'
|
||||
<tr className={className}>
|
||||
<td className='favorite'>
|
||||
<Icon
|
||||
className={`${isFavorite && 'isSelected'}`}
|
||||
name={isFavorite ? 'star' : 'star outline'}
|
||||
onClick={_onFavorite}
|
||||
/>
|
||||
</td>
|
||||
<td className='top'>
|
||||
<AddressSmall
|
||||
overrideName={
|
||||
isEditingName
|
||||
? (
|
||||
<Input
|
||||
className='name--input'
|
||||
autoFocus
|
||||
defaultValue={accName}
|
||||
onBlur={_saveName}
|
||||
onChange={setAccName}
|
||||
onEnter={_saveName}
|
||||
withLabel={false}
|
||||
/>
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
onClickName={_toggleEditName}
|
||||
toggle={isEditingName}
|
||||
value={address}
|
||||
/>
|
||||
{isBackupOpen && (
|
||||
<Backup
|
||||
address={address}
|
||||
key='modal-backup-account'
|
||||
onClose={_toggleBackup}
|
||||
/>
|
||||
)}
|
||||
{isDeriveOpen && (
|
||||
<Derive
|
||||
from={address}
|
||||
key='modal-derive-account'
|
||||
onClose={_toggleDerive}
|
||||
/>
|
||||
)}
|
||||
{isForgetOpen && (
|
||||
<Forget
|
||||
address={address}
|
||||
onForget={_onForget}
|
||||
key='modal-forget-account'
|
||||
onClose={_toggleForget}
|
||||
/>
|
||||
)}
|
||||
{isPasswordOpen && (
|
||||
<ChangePass
|
||||
address={address}
|
||||
key='modal-change-pass'
|
||||
onClose={_togglePass}
|
||||
/>
|
||||
)}
|
||||
{isTransferOpen && (
|
||||
<Transfer
|
||||
key='modal-transfer'
|
||||
onClose={_toggleTransfer}
|
||||
senderId={address}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
<td className='top'>
|
||||
{isEditingTags
|
||||
? (
|
||||
<InputTags
|
||||
onBlur={_saveTags}
|
||||
onChange={setTags}
|
||||
onClose={_saveTags}
|
||||
openOnFocus
|
||||
defaultValue={tags}
|
||||
searchInput={{ autoFocus: true }}
|
||||
value={tags}
|
||||
withLabel={false}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<div className='tags--toggle' onClick={_toggleEditTags}>
|
||||
{tags.length
|
||||
? tags.map((tag): React.ReactNode => (
|
||||
<Label key={tag} size='tiny' color='grey'>{tag}</Label>
|
||||
))
|
||||
: <label>{t('no tags')}</label>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</td>
|
||||
<td className='top'>
|
||||
<AddressInfo
|
||||
address={address}
|
||||
withBalance
|
||||
withBalanceToggle
|
||||
withExtended={false}
|
||||
/>
|
||||
</td>
|
||||
<td className='top'>
|
||||
<AddressInfo
|
||||
address={address}
|
||||
withBalance={false}
|
||||
withExtended
|
||||
/>
|
||||
</td>
|
||||
<td className='number top'>
|
||||
<Button
|
||||
icon='paper plane'
|
||||
isPrimary
|
||||
label={t('send')}
|
||||
onClick={_toggleTransfer}
|
||||
size='small'
|
||||
tooltip={t('Send funds from this account')}
|
||||
/>
|
||||
<Popup
|
||||
className='theme--default'
|
||||
onClose={_toggleSettingPopup}
|
||||
open={isSettingPopupOpen}
|
||||
position='bottom right'
|
||||
trigger={
|
||||
<Button
|
||||
icon='setting'
|
||||
onClick={_toggleSettingPopup}
|
||||
size='small'
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Menu
|
||||
vertical
|
||||
text
|
||||
onClick={_toggleSettingPopup}
|
||||
>
|
||||
<Menu.Item
|
||||
disabled={!isEditable || isExternal}
|
||||
onClick={_toggleDerive}
|
||||
>
|
||||
<Menu
|
||||
vertical
|
||||
text
|
||||
onClick={_toggleSettingPopup}
|
||||
>
|
||||
<Menu.Item
|
||||
disabled={!isEditable || isExternal}
|
||||
onClick={_toggleDerive}
|
||||
>
|
||||
{t('Derive account from source')}
|
||||
</Menu.Item>
|
||||
<Menu.Item disabled>
|
||||
{t('Change on-chain nickname')}
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
disabled={!isEditable || isExternal || isDevelopment}
|
||||
onClick={_toggleBackup}
|
||||
>
|
||||
{t('Create a backup file for this account')}
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
disabled={!isEditable || isExternal || isDevelopment}
|
||||
onClick={_togglePass}
|
||||
>
|
||||
{t("Change this account's password")}
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
disabled={!isEditable || isDevelopment}
|
||||
onClick={_toggleForget}
|
||||
>
|
||||
{t('Forget this account')}
|
||||
</Menu.Item>
|
||||
{!api.isDevelopment && (
|
||||
<>
|
||||
<Menu.Divider />
|
||||
<ChainLock
|
||||
className='accounts--network-toggle'
|
||||
genesisHash={genesisHash}
|
||||
onChange={_onGenesisChange}
|
||||
preventDefault
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
</Popup>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
className={className}
|
||||
isEditable={isEditable}
|
||||
type='account'
|
||||
value={address}
|
||||
withExplorer
|
||||
withIndexOrAddress={false}
|
||||
withTags
|
||||
>
|
||||
{address && (
|
||||
<>
|
||||
{isBackupOpen && (
|
||||
<Backup
|
||||
address={address}
|
||||
key='modal-backup-account'
|
||||
onClose={_toggleBackup}
|
||||
/>
|
||||
)}
|
||||
{isDeriveOpen && (
|
||||
<Derive
|
||||
from={address}
|
||||
key='modal-derive-account'
|
||||
onClose={_toggleDerive}
|
||||
/>
|
||||
)}
|
||||
{isForgetOpen && (
|
||||
<Forget
|
||||
address={address}
|
||||
onForget={_onForget}
|
||||
key='modal-forget-account'
|
||||
onClose={_toggleForget}
|
||||
/>
|
||||
)}
|
||||
{isPasswordOpen && (
|
||||
<ChangePass
|
||||
address={address}
|
||||
key='modal-change-pass'
|
||||
onClose={_togglePass}
|
||||
/>
|
||||
)}
|
||||
{isTransferOpen && (
|
||||
<Transfer
|
||||
key='modal-transfer'
|
||||
onClose={_toggleTransfer}
|
||||
senderId={address}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<AddressInfo
|
||||
address={address}
|
||||
withBalance
|
||||
withExtended
|
||||
/>
|
||||
</AddressCard>
|
||||
{t('Derive account from source')}
|
||||
</Menu.Item>
|
||||
<Menu.Item disabled>
|
||||
{t('Change on-chain nickname')}
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
disabled={!isEditable || isExternal || isDevelopment}
|
||||
onClick={_toggleBackup}
|
||||
>
|
||||
{t('Create a backup file for this account')}
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
disabled={!isEditable || isExternal || isDevelopment}
|
||||
onClick={_togglePass}
|
||||
>
|
||||
{t("Change this account's password")}
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
disabled={!isEditable || isDevelopment}
|
||||
onClick={_toggleForget}
|
||||
>
|
||||
{t('Forget this account')}
|
||||
</Menu.Item>
|
||||
{!api.isDevelopment && (
|
||||
<>
|
||||
<Menu.Divider />
|
||||
<ChainLock
|
||||
className='accounts--network-toggle'
|
||||
genesisHash={genesisHash}
|
||||
onChange={_onGenesisChange}
|
||||
preventDefault
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
</Popup>
|
||||
</td>
|
||||
<td className='mini top'>
|
||||
<LinkPolkascan
|
||||
className='ui--AddressCard-exporer-link'
|
||||
data={address}
|
||||
type='address'
|
||||
withShort
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
@ -215,5 +330,19 @@ export default translate(
|
||||
.accounts--Account-buttons {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.tags--toggle {
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
min-height: 1.5rem;
|
||||
|
||||
label {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.name--input {
|
||||
width: 16rem;
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
@ -5,11 +5,12 @@
|
||||
import { I18nProps } from '@polkadot/react-components/types';
|
||||
import { ComponentProps } from './types';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import keyring from '@polkadot/ui-keyring';
|
||||
import { getLedger, isLedger } from '@polkadot/react-api';
|
||||
import { useAccounts } from '@polkadot/react-hooks';
|
||||
import { Button, CardGrid } from '@polkadot/react-components';
|
||||
import { useAccounts, useFavorites } from '@polkadot/react-hooks';
|
||||
import { Button, InputTags, Table } from '@polkadot/react-components';
|
||||
|
||||
import CreateModal from './modals/Create';
|
||||
import ImportModal from './modals/Import';
|
||||
@ -21,6 +22,10 @@ import translate from './translate';
|
||||
interface Props extends ComponentProps, I18nProps {
|
||||
}
|
||||
|
||||
type SortedAccount = { address: string; isFavorite: boolean };
|
||||
|
||||
const STORE_FAVS = 'accounts:favorites';
|
||||
|
||||
// query the ledger for the address, adding it to the keyring
|
||||
async function queryLedger (): Promise<void> {
|
||||
const ledger = getLedger();
|
||||
@ -34,58 +39,36 @@ async function queryLedger (): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
function Overview ({ onStatusChange, t }: Props): React.ReactElement<Props> {
|
||||
function Overview ({ className, onStatusChange, t }: Props): React.ReactElement<Props> {
|
||||
const { allAccounts, hasAccounts } = useAccounts();
|
||||
const [isCreateOpen, setIsCreateOpen] = useState(false);
|
||||
const [isImportOpen, setIsImportOpen] = useState(false);
|
||||
const [isQrOpen, setIsQrOpen] = useState(false);
|
||||
const emptyScreen = !(isCreateOpen || isImportOpen || isQrOpen) && !hasAccounts;
|
||||
const [favorites, toggleFavorite] = useFavorites(STORE_FAVS);
|
||||
const [sortedAccounts, setSortedAccounts] = useState<SortedAccount[]>([]);
|
||||
const [tags, setTags] = useState<string[]>([]);
|
||||
|
||||
useEffect((): void => {
|
||||
setSortedAccounts(
|
||||
allAccounts
|
||||
.map((address): SortedAccount => ({ address, isFavorite: favorites.includes(address) }))
|
||||
.sort((a, b): number =>
|
||||
a.isFavorite === b.isFavorite
|
||||
? 0
|
||||
: b.isFavorite
|
||||
? 1
|
||||
: -1
|
||||
)
|
||||
);
|
||||
}, [allAccounts, favorites]);
|
||||
|
||||
const _toggleCreate = (): void => setIsCreateOpen(!isCreateOpen);
|
||||
const _toggleImport = (): void => setIsImportOpen(!isImportOpen);
|
||||
const _toggleQr = (): void => setIsQrOpen(!isQrOpen);
|
||||
|
||||
return (
|
||||
<CardGrid
|
||||
banner={<Banner />}
|
||||
buttons={
|
||||
<Button.Group>
|
||||
<Button
|
||||
icon='add'
|
||||
isPrimary
|
||||
label={t('Add account')}
|
||||
onClick={_toggleCreate}
|
||||
/>
|
||||
<Button.Or />
|
||||
<Button
|
||||
icon='sync'
|
||||
isPrimary
|
||||
label={t('Restore JSON')}
|
||||
onClick={_toggleImport}
|
||||
/>
|
||||
<Button.Or />
|
||||
<Button
|
||||
icon='qrcode'
|
||||
isPrimary
|
||||
label={t('Add via Qr')}
|
||||
onClick={_toggleQr}
|
||||
/>
|
||||
{isLedger() && (
|
||||
<>
|
||||
<Button.Or />
|
||||
<Button
|
||||
icon='question'
|
||||
isPrimary
|
||||
label={t('Query Ledger')}
|
||||
onClick={queryLedger}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Button.Group>
|
||||
}
|
||||
isEmpty={emptyScreen}
|
||||
emptyText={t('No account yet?')}
|
||||
>
|
||||
<div className={className}>
|
||||
<Banner />
|
||||
{isCreateOpen && (
|
||||
<CreateModal
|
||||
onClose={_toggleCreate}
|
||||
@ -104,14 +87,82 @@ function Overview ({ onStatusChange, t }: Props): React.ReactElement<Props> {
|
||||
onStatusChange={onStatusChange}
|
||||
/>
|
||||
)}
|
||||
{allAccounts.map((address): React.ReactNode => (
|
||||
<Account
|
||||
address={address}
|
||||
key={address}
|
||||
<Button.Group>
|
||||
<Button
|
||||
icon='add'
|
||||
isPrimary
|
||||
label={t('Add account')}
|
||||
onClick={_toggleCreate}
|
||||
/>
|
||||
))}
|
||||
</CardGrid>
|
||||
<Button.Or />
|
||||
<Button
|
||||
icon='sync'
|
||||
isPrimary
|
||||
label={t('Restore JSON')}
|
||||
onClick={_toggleImport}
|
||||
/>
|
||||
<Button.Or />
|
||||
<Button
|
||||
icon='qrcode'
|
||||
isPrimary
|
||||
label={t('Add via Qr')}
|
||||
onClick={_toggleQr}
|
||||
/>
|
||||
{isLedger() && (
|
||||
<>
|
||||
<Button.Or />
|
||||
<Button
|
||||
icon='question'
|
||||
isPrimary
|
||||
label={t('Query Ledger')}
|
||||
onClick={queryLedger}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Button.Group>
|
||||
{hasAccounts
|
||||
? (
|
||||
<>
|
||||
<div className='filter--tags'>
|
||||
<InputTags
|
||||
allowAdd={false}
|
||||
label={t('filter by tags')}
|
||||
onChange={setTags}
|
||||
defaultValue={tags}
|
||||
value={tags}
|
||||
/>
|
||||
</div>
|
||||
<Table>
|
||||
<Table.Body>
|
||||
{sortedAccounts.map(({ address, isFavorite }): React.ReactNode => (
|
||||
<Account
|
||||
address={address}
|
||||
allowTags={tags}
|
||||
isFavorite={isFavorite}
|
||||
key={address}
|
||||
toggleFavorite={toggleFavorite}
|
||||
/>
|
||||
))}
|
||||
</Table.Body>
|
||||
</Table>
|
||||
</>
|
||||
)
|
||||
: t('no accounts yet, create or import and existing')
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default translate(Overview);
|
||||
export default translate(
|
||||
styled(Overview)`
|
||||
.filter--tags {
|
||||
.ui--Dropdown {
|
||||
padding-left: 0;
|
||||
|
||||
label {
|
||||
left: 1.55rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
@ -7,8 +7,9 @@ import { ActionStatus } from '@polkadot/react-components/Status/types';
|
||||
import { I18nProps } from '@polkadot/react-components/types';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Label } from 'semantic-ui-react';
|
||||
import styled from 'styled-components';
|
||||
import { AddressCard, AddressInfo, Button, ChainLock, Forget, Menu, Popup } from '@polkadot/react-components';
|
||||
import { AddressSmall, AddressInfo, Button, ChainLock, Icon, InputTags, Input, LinkPolkascan, Forget, Menu, Popup } from '@polkadot/react-components';
|
||||
import { useApi } from '@polkadot/react-hooks';
|
||||
import keyring from '@polkadot/ui-keyring';
|
||||
|
||||
@ -18,7 +19,10 @@ import translate from './translate';
|
||||
|
||||
interface Props extends I18nProps {
|
||||
address: string;
|
||||
allowTags: string[];
|
||||
className?: string;
|
||||
isFavorite: boolean;
|
||||
toggleFavorite: (address: string) => void;
|
||||
}
|
||||
|
||||
const WITH_BALANCE = { available: true, bonded: true, free: true, locked: true, reserved: true, total: true };
|
||||
@ -26,13 +30,18 @@ const WITH_EXTENDED = { nonce: true };
|
||||
|
||||
const isEditable = true;
|
||||
|
||||
function Address ({ address, className, t }: Props): React.ReactElement<Props> {
|
||||
function Address ({ address, allowTags, className, isFavorite, t, toggleFavorite }: Props): React.ReactElement<Props> | null {
|
||||
const api = useApi();
|
||||
const [tags, setTags] = useState<string[]>([]);
|
||||
const [accName, setAccName] = useState('');
|
||||
const [current, setCurrent] = useState<KeyringAddress | null>(null);
|
||||
const [genesisHash, setGenesisHash] = useState<string | null>(null);
|
||||
const [isEditingName, setIsEditingName] = useState(false);
|
||||
const [isEditingTags, setIsEditingTags] = useState(false);
|
||||
const [isForgetOpen, setIsForgetOpen] = useState(false);
|
||||
const [isSettingPopupOpen, setIsSettingPopupOpen] = useState(false);
|
||||
const [isTransferOpen, setIsTransferOpen] = useState(false);
|
||||
const [isVisible, setIsVisible] = useState(true);
|
||||
|
||||
useEffect((): void => {
|
||||
const current = keyring.getAddress(address);
|
||||
@ -41,6 +50,31 @@ function Address ({ address, className, t }: Props): React.ReactElement<Props> {
|
||||
setGenesisHash((current && current.meta.genesisHash) || null);
|
||||
}, []);
|
||||
|
||||
useEffect((): void => {
|
||||
const account = keyring.getAddress(address);
|
||||
|
||||
setTags(account?.meta?.tags || []);
|
||||
setAccName(account?.meta?.name || '');
|
||||
}, [address]);
|
||||
|
||||
useEffect((): void => {
|
||||
if (allowTags.length === 0) {
|
||||
setIsVisible(true);
|
||||
} else {
|
||||
setIsVisible(
|
||||
allowTags.reduce((result: boolean, tag: string): boolean => {
|
||||
return result || tags.includes(tag);
|
||||
}, false)
|
||||
);
|
||||
}
|
||||
}, [allowTags, tags]);
|
||||
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const _toggleEditName = (): void => setIsEditingName(!isEditingName);
|
||||
const _toggleEditTags = (): void => setIsEditingTags(!isEditingTags);
|
||||
const _toggleForget = (): void => setIsForgetOpen(!isForgetOpen);
|
||||
const _toggleSettingPopup = (): void => setIsSettingPopupOpen(!isSettingPopupOpen);
|
||||
const _toggleTransfer = (): void => setIsTransferOpen(!isTransferOpen);
|
||||
@ -70,96 +104,188 @@ function Address ({ address, className, t }: Props): React.ReactElement<Props> {
|
||||
|
||||
setGenesisHash(genesisHash);
|
||||
};
|
||||
const _onFavorite = (): void => toggleFavorite(address);
|
||||
const _saveName = (): 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
const _saveTags = (): 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AddressCard
|
||||
buttons={
|
||||
<div className='addresses--Address-buttons buttons'>
|
||||
<div className='actions'>
|
||||
<Button
|
||||
icon='paper plane'
|
||||
isPrimary
|
||||
key='deposit'
|
||||
label={t('deposit')}
|
||||
onClick={_toggleTransfer}
|
||||
size='small'
|
||||
tooltip={t('Send funds to this address')}
|
||||
/>
|
||||
<Popup
|
||||
className='theme--default'
|
||||
onClose={_toggleSettingPopup}
|
||||
open={isSettingPopupOpen}
|
||||
position='bottom right'
|
||||
trigger={
|
||||
<Button
|
||||
icon='setting'
|
||||
onClick={_toggleSettingPopup}
|
||||
size='small'
|
||||
<tr className={className}>
|
||||
<td className='favorite'>
|
||||
<Icon
|
||||
className={`${isFavorite && 'isSelected'}`}
|
||||
name={isFavorite ? 'star' : 'star outline'}
|
||||
onClick={_onFavorite}
|
||||
/>
|
||||
</td>
|
||||
<td className='top'>
|
||||
<AddressSmall
|
||||
overrideName={
|
||||
isEditingName
|
||||
? (
|
||||
<Input
|
||||
className='name--input'
|
||||
autoFocus
|
||||
defaultValue={accName}
|
||||
onBlur={_saveName}
|
||||
onChange={setAccName}
|
||||
onEnter={_saveName}
|
||||
withLabel={false}
|
||||
/>
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
onClickName={_toggleEditName}
|
||||
toggle={isEditingName}
|
||||
value={address}
|
||||
/>
|
||||
{address && current && (
|
||||
<>
|
||||
{isForgetOpen && (
|
||||
<Forget
|
||||
address={current.address}
|
||||
onForget={_onForget}
|
||||
key='modal-forget-account'
|
||||
mode='address'
|
||||
onClose={_toggleForget}
|
||||
/>
|
||||
)}
|
||||
{isTransferOpen && (
|
||||
<Transfer
|
||||
key='modal-transfer'
|
||||
onClose={_toggleTransfer}
|
||||
recipientId={address}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
<td className='top'>
|
||||
{isEditingTags
|
||||
? (
|
||||
<InputTags
|
||||
onBlur={_saveTags}
|
||||
onChange={setTags}
|
||||
onClose={_saveTags}
|
||||
openOnFocus
|
||||
defaultValue={tags}
|
||||
searchInput={{ autoFocus: true }}
|
||||
value={tags}
|
||||
withLabel={false}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<div className='tags--toggle' onClick={_toggleEditTags}>
|
||||
{tags.length
|
||||
? tags.map((tag): React.ReactNode => (
|
||||
<Label key={tag} size='tiny' color='grey'>{tag}</Label>
|
||||
))
|
||||
: <label>{t('no tags')}</label>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</td>
|
||||
<td className='top'>
|
||||
<AddressInfo
|
||||
address={address}
|
||||
withBalance={WITH_BALANCE}
|
||||
withBalanceToggle
|
||||
withExtended={false}
|
||||
/>
|
||||
</td>
|
||||
<td className='top'>
|
||||
<AddressInfo
|
||||
address={address}
|
||||
withBalance={false}
|
||||
withExtended={WITH_EXTENDED}
|
||||
/>
|
||||
</td>
|
||||
<td className='number top'>
|
||||
<Button
|
||||
icon='paper plane'
|
||||
isPrimary
|
||||
key='deposit'
|
||||
label={t('deposit')}
|
||||
onClick={_toggleTransfer}
|
||||
size='small'
|
||||
tooltip={t('Send funds to this address')}
|
||||
/>
|
||||
<Popup
|
||||
className='theme--default'
|
||||
onClose={_toggleSettingPopup}
|
||||
open={isSettingPopupOpen}
|
||||
position='bottom right'
|
||||
trigger={
|
||||
<Button
|
||||
icon='setting'
|
||||
onClick={_toggleSettingPopup}
|
||||
size='small'
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Menu
|
||||
vertical
|
||||
text
|
||||
onClick={_toggleSettingPopup}
|
||||
>
|
||||
<Menu.Item
|
||||
disabled={!isEditable}
|
||||
onClick={_toggleForget}
|
||||
>
|
||||
<Menu
|
||||
vertical
|
||||
text
|
||||
onClick={_toggleSettingPopup}
|
||||
>
|
||||
<Menu.Item
|
||||
disabled={!isEditable}
|
||||
onClick={_toggleForget}
|
||||
>
|
||||
{t('Forget this address')}
|
||||
</Menu.Item>
|
||||
{!api.isDevelopment && (
|
||||
<>
|
||||
<Menu.Divider />
|
||||
<ChainLock
|
||||
className='addresses--network-toggle'
|
||||
genesisHash={genesisHash}
|
||||
isDisabled={!isEditable}
|
||||
onChange={_onGenesisChange}
|
||||
preventDefault
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
</Popup>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
className={className}
|
||||
isEditable={isEditable}
|
||||
type='address'
|
||||
value={address}
|
||||
withExplorer
|
||||
withIndexOrAddress={false}
|
||||
withTags
|
||||
>
|
||||
{address && current && (
|
||||
<>
|
||||
{isForgetOpen && (
|
||||
<Forget
|
||||
address={current.address}
|
||||
onForget={_onForget}
|
||||
key='modal-forget-account'
|
||||
mode='address'
|
||||
onClose={_toggleForget}
|
||||
/>
|
||||
)}
|
||||
{isTransferOpen && (
|
||||
<Transfer
|
||||
key='modal-transfer'
|
||||
onClose={_toggleTransfer}
|
||||
recipientId={address}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<AddressInfo
|
||||
address={address}
|
||||
withBalance={WITH_BALANCE}
|
||||
withExtended={WITH_EXTENDED}
|
||||
/>
|
||||
</AddressCard>
|
||||
{t('Forget this address')}
|
||||
</Menu.Item>
|
||||
{!api.isDevelopment && (
|
||||
<>
|
||||
<Menu.Divider />
|
||||
<ChainLock
|
||||
className='addresses--network-toggle'
|
||||
genesisHash={genesisHash}
|
||||
isDisabled={!isEditable}
|
||||
onChange={_onGenesisChange}
|
||||
preventDefault
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
</Popup>
|
||||
</td>
|
||||
<td className='mini top'>
|
||||
<LinkPolkascan
|
||||
className='ui--AddressCard-exporer-link'
|
||||
data={address}
|
||||
type='address'
|
||||
withShort
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
@ -168,5 +294,19 @@ export default translate(
|
||||
.addresses--Address-buttons {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.tags--toggle {
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
min-height: 1.5rem;
|
||||
|
||||
label {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.name--input {
|
||||
width: 16rem;
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
@ -5,9 +5,10 @@
|
||||
import { I18nProps } from '@polkadot/react-components/types';
|
||||
import { ComponentProps } from './types';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Button, CardGrid } from '@polkadot/react-components';
|
||||
import { useAddresses } from '@polkadot/react-hooks';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Button, InputTags, Table } from '@polkadot/react-components';
|
||||
import { useAddresses, useFavorites } from '@polkadot/react-hooks';
|
||||
|
||||
import CreateModal from './modals/Create';
|
||||
import Address from './Address';
|
||||
@ -16,42 +17,92 @@ import translate from './translate';
|
||||
interface Props extends ComponentProps, I18nProps {
|
||||
}
|
||||
|
||||
function Overview ({ onStatusChange, t }: Props): React.ReactElement<Props> {
|
||||
type SortedAddress = { address: string; isFavorite: boolean };
|
||||
|
||||
const STORE_FAVS = 'accounts:favorites';
|
||||
|
||||
function Overview ({ className, onStatusChange, t }: Props): React.ReactElement<Props> {
|
||||
const { hasAddresses, allAddresses } = useAddresses();
|
||||
const [isCreateOpen, setIsCreateOpen] = useState(false);
|
||||
const emptyScreen = !isCreateOpen && !hasAddresses;
|
||||
const [favorites, toggleFavorite] = useFavorites(STORE_FAVS);
|
||||
const [sortedAddresses, setSortedAddresses] = useState<SortedAddress[]>([]);
|
||||
const [tags, setTags] = useState<string[]>([]);
|
||||
|
||||
useEffect((): void => {
|
||||
setSortedAddresses(
|
||||
allAddresses
|
||||
.map((address): SortedAddress => ({ address, isFavorite: favorites.includes(address) }))
|
||||
.sort((a, b): number =>
|
||||
a.isFavorite === b.isFavorite
|
||||
? 0
|
||||
: b.isFavorite
|
||||
? 1
|
||||
: -1
|
||||
)
|
||||
);
|
||||
}, [allAddresses, favorites]);
|
||||
|
||||
const _toggleCreate = (): void => setIsCreateOpen(!isCreateOpen);
|
||||
|
||||
return (
|
||||
<CardGrid
|
||||
buttons={
|
||||
<Button.Group>
|
||||
<Button
|
||||
icon='add'
|
||||
isPrimary
|
||||
label={t('Add contact')}
|
||||
onClick={_toggleCreate}
|
||||
/>
|
||||
</Button.Group>
|
||||
}
|
||||
isEmpty={emptyScreen}
|
||||
emptyText={t('No contacts found.')}
|
||||
>
|
||||
<div className={className}>
|
||||
<Button.Group>
|
||||
<Button
|
||||
icon='add'
|
||||
isPrimary
|
||||
label={t('Add contact')}
|
||||
onClick={_toggleCreate}
|
||||
/>
|
||||
</Button.Group>
|
||||
{isCreateOpen && (
|
||||
<CreateModal
|
||||
onClose={_toggleCreate}
|
||||
onStatusChange={onStatusChange}
|
||||
/>
|
||||
)}
|
||||
{allAddresses.map((address): React.ReactNode => (
|
||||
<Address
|
||||
address={address}
|
||||
key={address}
|
||||
/>
|
||||
))}
|
||||
</CardGrid>
|
||||
{hasAddresses
|
||||
? (
|
||||
<>
|
||||
<div className='filter--tags'>
|
||||
<InputTags
|
||||
allowAdd={false}
|
||||
label={t('filter by tags')}
|
||||
onChange={setTags}
|
||||
defaultValue={tags}
|
||||
value={tags}
|
||||
/>
|
||||
</div>
|
||||
<Table>
|
||||
<Table.Body>
|
||||
{sortedAddresses.map(({ address, isFavorite }): React.ReactNode => (
|
||||
<Address
|
||||
address={address}
|
||||
allowTags={tags}
|
||||
isFavorite={isFavorite}
|
||||
key={address}
|
||||
toggleFavorite={toggleFavorite}
|
||||
/>
|
||||
))}
|
||||
</Table.Body>
|
||||
</Table>
|
||||
</>
|
||||
)
|
||||
: t('no addresses yet add and existring contact')
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default translate(Overview);
|
||||
export default translate(
|
||||
styled(Overview)`
|
||||
.filter--tags {
|
||||
.ui--Dropdown {
|
||||
padding-left: 0;
|
||||
|
||||
label {
|
||||
left: 1.55rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
@ -51,6 +51,7 @@ interface Props extends BareProps, I18nProps {
|
||||
extraInfo?: [string, string][];
|
||||
stakingInfo?: DerivedStaking;
|
||||
withBalance?: boolean | BalanceActiveType;
|
||||
withBalanceToggle?: false;
|
||||
withExtended?: boolean | CryptoActiveType;
|
||||
withHexSessionId?: (string | null)[];
|
||||
withRewardDestination?: boolean;
|
||||
@ -237,7 +238,7 @@ function renderValidatorPrefs ({ stakingInfo, t, withValidatorPrefs = false }: P
|
||||
}
|
||||
|
||||
function renderBalances (props: Props, allAccounts: string[]): React.ReactNode {
|
||||
const { balancesAll, stakingInfo, t, withBalance = true } = props;
|
||||
const { balancesAll, stakingInfo, t, withBalance = true, withBalanceToggle = false } = props;
|
||||
const balanceDisplay = withBalance === true
|
||||
? DEFAULT_BALANCES
|
||||
: withBalance || false;
|
||||
@ -249,7 +250,7 @@ function renderBalances (props: Props, allAccounts: string[]): React.ReactNode {
|
||||
const [ownBonded, otherBonded] = calcBonded(stakingInfo, balanceDisplay.bonded);
|
||||
const controllerId = stakingInfo?.controllerId?.toString();
|
||||
|
||||
return (
|
||||
const allItems = (
|
||||
<>
|
||||
{balancesAll && balanceDisplay.total && (
|
||||
<>
|
||||
@ -344,15 +345,39 @@ function renderBalances (props: Props, allAccounts: string[]): React.ReactNode {
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
if (withBalanceToggle) {
|
||||
return (
|
||||
<>
|
||||
<label>{t('balances')}</label>
|
||||
<details>
|
||||
<summary>
|
||||
<div className='body'>
|
||||
<FormatBalance value={balancesAll?.votingBalance} />
|
||||
</div>
|
||||
</summary>
|
||||
<div className='body column'>
|
||||
{allItems}
|
||||
</div>
|
||||
</details>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{allItems}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function AddressInfo (props: Props): React.ReactElement<Props> {
|
||||
const { allAccounts } = useAccounts();
|
||||
const { className, children, extraInfo, stakingInfo, t, withHexSessionId, withRewardDestination } = props;
|
||||
const { className, children, extraInfo, stakingInfo, t, withBalanceToggle, withHexSessionId, withRewardDestination } = props;
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className='column'>
|
||||
<div className={`ui--AddressInfo ${className} ${withBalanceToggle ? 'ui--AddressInfo-expander' : ''}`}>
|
||||
<div className={`column ${withBalanceToggle ? 'column--expander' : ''}`}>
|
||||
{renderBalances(props, allAccounts)}
|
||||
{withHexSessionId && withHexSessionId[0] && (
|
||||
<>
|
||||
@ -402,36 +427,62 @@ export default withMulti(
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
white-space: nowrap;
|
||||
|
||||
&:not(.ui--AddressInfo-expander) {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.column {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
opacity: 1;
|
||||
&.column--expander {
|
||||
text-align: left;
|
||||
width: 15rem;
|
||||
|
||||
label {
|
||||
grid-column: 1;
|
||||
padding-right: 0.5rem;
|
||||
text-align: right;
|
||||
vertical-align: middle;
|
||||
details[open] summary {
|
||||
.body {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.help.circle.icon {
|
||||
display: none;
|
||||
details summary {
|
||||
width: 100%;
|
||||
|
||||
.body {
|
||||
display: inline-block;
|
||||
text-align: right;
|
||||
min-width: 12rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.result {
|
||||
grid-column: 2;
|
||||
&:not(.column--expander) {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
opacity: 1;
|
||||
|
||||
.icon {
|
||||
margin-left: .3em;
|
||||
margin-right: 0;
|
||||
padding-right: 0 !important;
|
||||
label {
|
||||
grid-column: 1;
|
||||
padding-right: 0.5rem;
|
||||
text-align: right;
|
||||
vertical-align: middle;
|
||||
|
||||
.help.circle.icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
button.ui.icon.primary.button.icon-button {
|
||||
background: white !important;
|
||||
.result {
|
||||
grid-column: 2;
|
||||
|
||||
.icon {
|
||||
margin-left: .3em;
|
||||
margin-right: 0;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
button.ui.icon.primary.button.icon-button {
|
||||
background: white !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,10 +13,13 @@ import IdentityIcon from './IdentityIcon';
|
||||
interface Props {
|
||||
className?: string;
|
||||
defaultName?: string;
|
||||
onClickName?: () => void;
|
||||
overrideName?: React.ReactNode;
|
||||
toggle?: any;
|
||||
value?: string | Address | AccountId;
|
||||
}
|
||||
|
||||
function AddressSmall ({ className, defaultName, value }: Props): React.ReactElement<Props> {
|
||||
function AddressSmall ({ className, defaultName, onClickName, overrideName, toggle, value }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<div className={`ui--AddressSmall ${className}`}>
|
||||
<IdentityIcon
|
||||
@ -24,7 +27,14 @@ function AddressSmall ({ className, defaultName, value }: Props): React.ReactEle
|
||||
value={value}
|
||||
/>
|
||||
<div className='nameInfo'>
|
||||
<AccountName defaultName={defaultName} params={value} />
|
||||
<AccountName
|
||||
className={(overrideName || !onClickName) ? '' : 'name--clickable'}
|
||||
defaultName={defaultName}
|
||||
override={overrideName}
|
||||
onClick={onClickName}
|
||||
params={value}
|
||||
toggle={toggle}
|
||||
/>
|
||||
<AccountIndex params={value} />
|
||||
</div>
|
||||
</div>
|
||||
@ -35,6 +45,10 @@ export default styled(AddressSmall)`
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
|
||||
.name--clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ui--IdentityIcon,
|
||||
.nameInfo {
|
||||
display: inline-block;
|
||||
@ -45,9 +59,11 @@ export default styled(AddressSmall)`
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
|
||||
.nameInfo > div {
|
||||
max-width: 16rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
.nameInfo {
|
||||
> div {
|
||||
max-width: 16rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@ -54,10 +54,10 @@ function onAddTag (value: string): void {
|
||||
saveTags(tags);
|
||||
}
|
||||
|
||||
export default function InputTags ({ className, defaultValue, help, isDisabled, isError, label, onBlur, onChange, onClose, placeholder, searchInput, value, withLabel }: Props): React.ReactElement<Props> {
|
||||
export default function InputTags ({ allowAdd = true, className, defaultValue, help, isDisabled, isError, label, onBlur, onChange, onClose, placeholder, searchInput, value, withLabel }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<Dropdown
|
||||
allowAdd={!isDisabled}
|
||||
allowAdd={allowAdd && !isDisabled}
|
||||
className={className}
|
||||
defaultValue={defaultValue}
|
||||
help={help}
|
||||
|
||||
@ -9,6 +9,7 @@ import styled from 'styled-components';
|
||||
import { useApi } from '@polkadot/react-hooks';
|
||||
|
||||
import translate from './translate';
|
||||
import Icon from './Icon';
|
||||
|
||||
export type LinkTypes = 'address' | 'block' | 'extrinsic';
|
||||
|
||||
@ -16,6 +17,7 @@ interface Props extends I18nProps {
|
||||
className?: string;
|
||||
data: string;
|
||||
type: LinkTypes;
|
||||
withShort?: boolean;
|
||||
}
|
||||
|
||||
const BASE = 'https://polkascan.io/pre/';
|
||||
@ -34,7 +36,7 @@ const TYPES: Record<string, string> = {
|
||||
extrinsic: '/system/extrinsic/'
|
||||
};
|
||||
|
||||
function LinkPolkascan ({ className, data, t, type }: Props): React.ReactElement<Props> | null {
|
||||
function LinkPolkascan ({ className, data, t, type, withShort }: Props): React.ReactElement<Props> | null {
|
||||
const { systemChain } = useApi();
|
||||
const extChain = CHAINS[systemChain];
|
||||
const extType = TYPES[type];
|
||||
@ -44,13 +46,16 @@ function LinkPolkascan ({ className, data, t, type }: Props): React.ReactElement
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className={`${className} ${withShort ? 'withShort' : ''}`}>
|
||||
<a
|
||||
href={`${BASE}${extChain}${extType}${data}`}
|
||||
rel='noopener noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
{t('View this {{type}} on Polkascan.io', { replace: { type } })}
|
||||
{withShort
|
||||
? <Icon name='external' />
|
||||
: t('View this {{type}} on Polkascan.io', { replace: { type } })
|
||||
}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -343,7 +343,7 @@ export default class Row<P extends RowProps, S extends RowState> extends React.P
|
||||
onChange={this.onChangeTags}
|
||||
onClose={this.saveTags}
|
||||
openOnFocus
|
||||
defaultValue = {tags}
|
||||
defaultValue={tags}
|
||||
searchInput={{ autoFocus: true }}
|
||||
value={tags}
|
||||
withLabel={false}
|
||||
|
||||
@ -57,7 +57,6 @@ export default styled(Table)`
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
table {
|
||||
/* border-collapse: collapse; */
|
||||
border-collapse: separate;
|
||||
border-spacing: 0 0.25rem;
|
||||
width: 100%;
|
||||
@ -79,6 +78,10 @@ export default styled(Table)`
|
||||
label {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
td {
|
||||
border-color: #ccc;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:hover) {
|
||||
@ -146,6 +149,10 @@ export default styled(Table)`
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.mini {
|
||||
padding: 0 0.75rem 0 0;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-left-width: 1px;
|
||||
border-radius: 0.25rem 0 0 0.25rem;
|
||||
|
||||
@ -14,7 +14,10 @@ interface Props extends BareProps {
|
||||
children?: React.ReactNode;
|
||||
defaultName?: string;
|
||||
label?: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
override?: React.ReactNode;
|
||||
params?: AccountId | AccountIndex | Address | string | null;
|
||||
toggle?: any;
|
||||
withShort?: boolean;
|
||||
}
|
||||
|
||||
@ -40,7 +43,7 @@ function defaultOrAddr (defaultName = '', _address?: AccountId | AccountIndex |
|
||||
return extracted;
|
||||
}
|
||||
|
||||
export default function AccountName ({ children, className, defaultName, label = '', params, style, withShort }: Props): React.ReactElement<Props> {
|
||||
export default function AccountName ({ children, className, defaultName, label = '', onClick, override, params, style, toggle, withShort }: Props): React.ReactElement<Props> {
|
||||
const { api } = useApi();
|
||||
const info = useStream<DeriveAccountInfo>(api.derive.accounts.info as any, [params]);
|
||||
const [name, setName] = useState(defaultOrAddr(defaultName, params));
|
||||
@ -56,14 +59,19 @@ export default function AccountName ({ children, className, defaultName, label =
|
||||
} else {
|
||||
setName(defaultOrAddr(defaultName, accountId || params, withShort ? null : accountIndex));
|
||||
}
|
||||
}, [info]);
|
||||
}, [info, toggle]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
onClick={
|
||||
override
|
||||
? undefined
|
||||
: onClick
|
||||
}
|
||||
style={style}
|
||||
>
|
||||
{label}{name}{children}
|
||||
{label}{override || name}{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user