diff --git a/packages/apps/src/index.tsx b/packages/apps/src/index.tsx index 3ab0965a8f..9d31d5c3a3 100644 --- a/packages/apps/src/index.tsx +++ b/packages/apps/src/index.tsx @@ -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( - - - + + + + + diff --git a/packages/page-accounts/src/Accounts/Account.tsx b/packages/page-accounts/src/Accounts/Account.tsx index 6bc5e00b97..c2c473abe5 100644 --- a/packages/page-accounts/src/Accounts/Account.tsx +++ b/packages/page-accounts/src/Accounts/Account.tsx @@ -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 | null { const { t } = useTranslation(); const api = useApi(); - const info = useCall(api.api.derive.accounts.info, [address]); const balancesAll = useCall(api.api.derive.balances.all, [address]); const recoveryInfo = useCall(api.api.query.recovery?.recoverable, [address], { transform: (opt: Option) => opt.unwrapOr(null) }); - const [tags, setTags] = useState([]); - const [accName, setAccName] = useState(''); - const [genesisHash, setGenesisHash] = useState(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 )} - - ) - : undefined - } - toggle={isEditingName} - value={address} - /> + {isBackupOpen && ( - {isEditingTags - ? ( - - ) - : ( -
- {tags.length - ? tags.map((tag): React.ReactNode => ( - - )) - : - } -
- ) - } +
+ {tags.length + ? tags.map((tag): React.ReactNode => ( + + )) + : + } +
{balancesAll && formatNumber(balancesAll.accountNonce)} @@ -443,7 +320,7 @@ function Account ({ account: { address, meta }, className, filter, isFavorite, s diff --git a/packages/page-accounts/src/Contacts/Address.tsx b/packages/page-accounts/src/Contacts/Address.tsx index 2a006b398b..011f68dbe3 100644 --- a/packages/page-accounts/src/Contacts/Address.tsx +++ b/packages/page-accounts/src/Contacts/Address.tsx @@ -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(null); const [genesisHash, setGenesisHash] = useState(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 /> - - ) - : undefined - } - toggle={isEditingName} - 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 && ( )} @@ -223,36 +178,17 @@ function Address ({ address, className, filter, isFavorite, toggleFavorite }: Pr )} - {isEditingTags - ? ( - - ) - : ( -
- {tags.length - ? tags.map((tag): React.ReactNode => ( - - )) - : - } -
- ) - } +
+ {tags.length + ? tags.map((tag): React.ReactNode => ( + + )) + : + } +
{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')} /> } > {t('Forget this address')} @@ -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 { diff --git a/packages/page-accounts/src/Sidebar/Sidebar.tsx b/packages/page-accounts/src/Sidebar/Sidebar.tsx new file mode 100644 index 0000000000..faef91eb29 --- /dev/null +++ b/packages/page-accounts/src/Sidebar/Sidebar.tsx @@ -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 { + 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 ( + <> +
+ + )} + +
+ + {useIdentity && identity?.isExistent && ( +
+
+
+ + {' '} + {t('identity')} +
+ +
+
+ + // : + // + + } + subtitle={identity.legal} + title={identity.display} + /> +
+ {identity.parent && ( +
+
{t('parent')}
+
+ +
+
+ )} + {identity.email && ( +
+
{t('email')}
+ +
+ )} + {identity.web && ( +
+
{t('website')}
+ +
+ )} + {identity.twitter && ( +
+
{t('twitter')}
+ +
+ )} + {identity.riot && ( +
+
{t('riot')}
+
+ {identity.riot} +
+
+ )} +
+
+
+ )} + {address && identity?.isExistent && isRegistrar && ( +
+
+
    +
  • + +
  • +
+
+
+ )} +
+ +
+ + {isTransferOpen && ( + + )} + {(!!address && isJudgementOpen && isRegistrar && useIdentity) && ( + + )} + + ); +} + +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); + } + } + } +`); diff --git a/packages/page-accounts/src/Sidebar/index.tsx b/packages/page-accounts/src/Sidebar/index.tsx new file mode 100644 index 0000000000..fa97717ef5 --- /dev/null +++ b/packages/page-accounts/src/Sidebar/index.tsx @@ -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 = React.createContext(undefined); + +function AccountSidebar ({ children }: Props): React.ReactElement { + const [[address, onUpdateName], setAddress] = useState<[StringOrNull, VoidFn | null]>([null, null]); + + const onClose = useCallback( + () => setAddress([null, null]), + [] + ); + + return ( + + {children} + {address && ( + + )} + + ); +} + +export { AccountSidebarToggle }; + +export default React.memo(AccountSidebar); diff --git a/packages/page-democracy/src/Overview/External.tsx b/packages/page-democracy/src/Overview/External.tsx index bf22c00318..2bee13e75d 100644 --- a/packages/page-democracy/src/Overview/External.tsx +++ b/packages/page-democracy/src/Overview/External.tsx @@ -25,7 +25,9 @@ function External ({ className, value: { image, imageHash, threshold } }: Props) proposal={image?.proposal} /> - {image && } + {image && ( + + )} {image && } diff --git a/packages/page-explorer/src/BlockHeader.tsx b/packages/page-explorer/src/BlockHeader.tsx index 622dd5299c..96ef7674d7 100644 --- a/packages/page-explorer/src/BlockHeader.tsx +++ b/packages/page-explorer/src/BlockHeader.tsx @@ -29,7 +29,9 @@ function BlockHeader ({ value }: Props): React.ReactElement | null { {hashHex} - {value.author && } + {value.author && ( + + )} ); diff --git a/packages/page-explorer/src/BlockInfo/ByHash.tsx b/packages/page-explorer/src/BlockInfo/ByHash.tsx index 8fe8404475..649364f6f7 100644 --- a/packages/page-explorer/src/BlockInfo/ByHash.tsx +++ b/packages/page-explorer/src/BlockInfo/ByHash.tsx @@ -50,7 +50,11 @@ function BlockByHash ({ className, value }: Props): React.ReactElement { > {getBlock && !getBlock.isEmpty && getHeader && !getHeader.isEmpty && ( - {getHeader.author && } + + {getHeader.author && ( + + )} + {getHeader.hash.toHex()} {parentHash} {getHeader.extrinsicsRoot.toHex()} diff --git a/packages/page-parachains/src/ParachainInfo.tsx b/packages/page-parachains/src/ParachainInfo.tsx index a13444a26c..1b31a991c1 100644 --- a/packages/page-parachains/src/ParachainInfo.tsx +++ b/packages/page-parachains/src/ParachainInfo.tsx @@ -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 ( -
-
- { - info?.icon - ? ( - - ) - : ( - - ) - } -
-
-
- {parachainName(t, info)} -
-
- {parachainOwner(t, info)} -
-
+ + ) + : ( + + ) + } + isBig={isBig} + subtitle={parachainOwner(t, info)} + title={parachainName(t, info)} + > {children} -
+ ); } 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; - } - } } } `); diff --git a/packages/react-components/src/AccountName.tsx b/packages/react-components/src/AccountName.tsx index dacae1e64e..750e5d07a9 100644 --- a/packages/react-components/src/AccountName.tsx +++ b/packages/react-components/src/AccountName.tsx @@ -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 (
{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 = ( -
-
- { - 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 } }) - : '' - } -
- - - {identity.parent && ( - - - - - )} - {DISPLAY_KEYS - .filter((key): boolean => !!identity[key as 'web']) - .map((key): React.ReactNode => ( - - - - - )) - } - -
{t('parent')}
{t(key)}{identity[key as 'web']}
-
- ); const nameElem = displayParent ? {displayParent}/{displayName} : {displayName}; @@ -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 { - const { t } = useTranslation(); +function AccountName ({ children, className, defaultName, label, noLookup, onClick, override, toggle, value, withSidebar }: Props): React.ReactElement { const { api } = useApi(); - const { isRegistrar, registrars } = useRegistrars(noLookup); - const [isJudgementOpen, toggleJudgement] = useToggle(); const info = useCall(!noLookup && api.derive.accounts.info, [value]); const [name, setName] = useState(() => 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 && ( - - )} -
- {label || ''}{override || name}{children} -
- +
+ {label || ''}{override || name}{children} +
); } 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; diff --git a/packages/react-components/src/AccountNameJudgement.tsx b/packages/react-components/src/AccountNameJudgement.tsx index 0a94c466a2..20f36af5f1 100644 --- a/packages/react-components/src/AccountNameJudgement.tsx +++ b/packages/react-components/src/AccountNameJudgement.tsx @@ -48,6 +48,7 @@ function AccountNameJudgement ({ address, registrars, toggleJudgement }: Props): return ( diff --git a/packages/react-components/src/AddressMini.tsx b/packages/react-components/src/AddressMini.tsx index 11bae2de2a..037ea33dda 100644 --- a/packages/react-components/src/AddressMini.tsx +++ b/packages/react-components/src/AddressMini.tsx @@ -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 | 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 | null { if (!value) { return null; } @@ -67,6 +68,7 @@ function AddressMini ({ balance, bonded, children, className, iconInfo, isPadded ) : toShortAddress(value) diff --git a/packages/react-components/src/AddressSmall.tsx b/packages/react-components/src/AddressSmall.tsx index dd03f4088d..0f89b90e0d 100644 --- a/packages/react-components/src/AddressSmall.tsx +++ b/packages/react-components/src/AddressSmall.tsx @@ -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 { +function AddressSmall ({ children, className, defaultName, onClickName, overrideName, toggle, value, withIndex, withSidebar = true }: Props): React.ReactElement { return (
-
+
+ withSidebar={withSidebar} + > + {children} + + {withIndex && ( + + )}
); } 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; } } `); diff --git a/packages/react-components/src/AddressToggle.tsx b/packages/react-components/src/AddressToggle.tsx index 09fab824e1..5c51628f0c 100644 --- a/packages/react-components/src/AddressToggle.tsx +++ b/packages/react-components/src/AddressToggle.tsx @@ -69,6 +69,7 @@ function AddressToggle ({ address, className, filter, isHidden, noLookup, noTogg className='ui--AddressToggle-address' noLookup={noLookup} value={address} + withSidebar={false} /> {!noToggle && (
diff --git a/packages/react-components/src/AvatarItem.tsx b/packages/react-components/src/AvatarItem.tsx new file mode 100644 index 0000000000..12b9545596 --- /dev/null +++ b/packages/react-components/src/AvatarItem.tsx @@ -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 { + return ( +
+
+ {icon} +
+
+
+ {title} +
+
+ {subtitle} +
+
+ {children} +
+ ); +} + +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; + } + } + } + } +`); diff --git a/packages/react-components/src/Button/Button.tsx b/packages/react-components/src/Button/Button.tsx index 872211da7e..5e13a62361 100644 --- a/packages/react-components/src/Button/Button.tsx +++ b/packages/react-components/src/Button/Button.tsx @@ -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 { +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 { 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), diff --git a/packages/react-components/src/Button/Content.tsx b/packages/react-components/src/Button/Content.tsx new file mode 100644 index 0000000000..c8dbdcccdc --- /dev/null +++ b/packages/react-components/src/Button/Content.tsx @@ -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 { + return ( + + ); +} diff --git a/packages/react-components/src/Button/index.tsx b/packages/react-components/src/Button/index.tsx index 5e22c1ed36..3b50e66598 100644 --- a/packages/react-components/src/Button/index.tsx +++ b/packages/react-components/src/Button/index.tsx @@ -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; diff --git a/packages/react-components/src/Button/types.ts b/packages/react-components/src/Button/types.ts index c8cdab4029..05d4fee1ad 100644 --- a/packages/react-components/src/Button/types.ts +++ b/packages/react-components/src/Button/types.ts @@ -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; +export type Button$Callback = () => void | Promise; 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 & { }; export type ButtonType = React.ComponentType & { + Content: React.ComponentType; Divider: React.ComponentType; Group: GroupType; Or: React.ComponentType; diff --git a/packages/react-components/src/IconLink.tsx b/packages/react-components/src/IconLink.tsx index 4540e4f496..c9ffd8b5cc 100644 --- a/packages/react-components/src/IconLink.tsx +++ b/packages/react-components/src/IconLink.tsx @@ -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 { +function IconLink ({ className, href, icon, label, onClick, rel, target }: Props): React.ReactElement { return ( {icon && } {label} diff --git a/packages/react-components/src/IdentityIcon.tsx b/packages/react-components/src/IdentityIcon.tsx index fb666a7553..42b5f23963 100644 --- a/packages/react-components/src/IdentityIcon.tsx +++ b/packages/react-components/src/IdentityIcon.tsx @@ -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( diff --git a/packages/react-components/src/Input.tsx b/packages/react-components/src/Input.tsx index f6980c0503..864f62f3c7 100644 --- a/packages/react-components/src/Input.tsx +++ b/packages/react-components/src/Input.tsx @@ -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) { diff --git a/packages/react-components/src/InputBalance.tsx b/packages/react-components/src/InputBalance.tsx index 2f48cec0cd..f405505caf 100644 --- a/packages/react-components/src/InputBalance.tsx +++ b/packages/react-components/src/InputBalance.tsx @@ -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; diff --git a/packages/react-components/src/Popup.tsx b/packages/react-components/src/Popup.tsx index 3ce22a7266..c936836443 100644 --- a/packages/react-components/src/Popup.tsx +++ b/packages/react-components/src/Popup.tsx @@ -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 { +function Popup ({ children, className, isOpen, on = 'click', onClose, position = 'bottom right', trigger }: Props): React.ReactElement { return ( {children} diff --git a/packages/react-components/src/Transfer.tsx b/packages/react-components/src/Transfer.tsx new file mode 100644 index 0000000000..6cd8529881 --- /dev/null +++ b/packages/react-components/src/Transfer.tsx @@ -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 { + const { t } = useTranslation(); + const { api } = useApi(); + const [amount, setAmount] = useState(new BN(0)); + const [extrinsic, setExtrinsic] = useState(null); + const [hasAvailable] = useState(true); + const [maxBalance] = useState(new BN(0)); + const [recipientId, setRecipientId] = useState(propRecipientId || null); + const [senderId, setSenderId] = useState(propSenderId || null); + + useEffect((): void => { + senderId && recipientId && setExtrinsic( + () => api.tx.balances.transfer(recipientId, amount || ZERO) + ); + }, [api, amount, recipientId, senderId]); + + const transferrable = {t('transferrable')}; + + return ( + + +
+ + } + onChange={setSenderId} + type='account' + /> + + } + onChange={setRecipientId} + type='allPlus' + /> + +
+
+ + + +
+ ); +} + +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; + } +`); diff --git a/packages/react-components/src/index.tsx b/packages/react-components/src/index.tsx index 9761a5fab3..00c8910e9c 100644 --- a/packages/react-components/src/index.tsx +++ b/packages/react-components/src/index.tsx @@ -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'; diff --git a/packages/react-components/src/styles/theme.ts b/packages/react-components/src/styles/theme.ts index 511c6e229c..1f3535d340 100644 --- a/packages/react-components/src/styles/theme.ts +++ b/packages/react-components/src/styles/theme.ts @@ -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 { diff --git a/packages/react-components/src/types.ts b/packages/react-components/src/types.ts index f7683747af..9dff5edbb8 100644 --- a/packages/react-components/src/types.ts +++ b/packages/react-components/src/types.ts @@ -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; } diff --git a/packages/react-hooks/src/index.ts b/packages/react-hooks/src/index.ts index 8ad18d87b7..df7efa4b0b 100644 --- a/packages/react-hooks/src/index.ts +++ b/packages/react-hooks/src/index.ts @@ -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'; diff --git a/packages/react-hooks/src/types.ts b/packages/react-hooks/src/types.ts index 98d5219c92..3727b2ca52 100644 --- a/packages/react-hooks/src/types.ts +++ b/packages/react-hooks/src/types.ts @@ -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; + tags: string[]; + setTags: React.Dispatch; + genesisHash: StringOrNull; + setGenesisHash: React.Dispatch; + identity?: AddressIdentity; + isEditingName: boolean; + toggleIsEditingName: VoidFn; + isEditingTags: boolean; + toggleIsEditingTags: VoidFn; + onSaveName: VoidFn; + onSaveTags: VoidFn; + onSaveGenesisHash: VoidFn; + onForgetAddress: VoidFn; +} diff --git a/packages/react-hooks/src/useAccountInfo.ts b/packages/react-hooks/src/useAccountInfo.ts new file mode 100644 index 0000000000..f573db0d37 --- /dev/null +++ b/packages/react-hooks/src/useAccountInfo.ts @@ -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(api.derive.accounts.info as any, [value]); + const accountFlags = useCall(api.derive.accounts.flags as any, [value]); + const { isAccount } = useAccounts(); + const { isAddress } = useAddresses(); + + const [tags, setSortedTags] = useState([]); + const [name, setName] = useState(''); + const [genesisHash, setGenesisHash] = useState(null); + const [identity, setIdentity] = useState(); + + const [flags, setFlags] = useState(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 + }; +} diff --git a/packages/react-hooks/src/useAccounts.ts b/packages/react-hooks/src/useAccounts.ts index 0748e57639..3fd1d47420 100644 --- a/packages/react-hooks/src/useAccounts.ts +++ b/packages/react-hooks/src/useAccounts.ts @@ -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({ allAccounts: [], hasAccounts: false }); + const [state, setState] = useState({ 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 }); } }); diff --git a/packages/react-hooks/src/useAddresses.ts b/packages/react-hooks/src/useAddresses.ts index 156c6bd60d..b718b17c07 100644 --- a/packages/react-hooks/src/useAddresses.ts +++ b/packages/react-hooks/src/useAddresses.ts @@ -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({ allAddresses: [], hasAddresses: false }); + const [state, setState] = useState({ 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 }); } }); diff --git a/yarn.lock b/yarn.lock index 85b4454b5d..f208dc18f7 100644 --- a/yarn.lock +++ b/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