Additional functional components (#1668)
* Additional functional components * Additional * SideBar * Sign & Verify * Small Signer modal updates * Formatting * Remove staking css (external file) * Defaults in props * Small adjustments
This commit is contained in:
parent
8e86102df7
commit
1440d83545
@ -122,7 +122,7 @@ class Account extends React.PureComponent<Props, State> {
|
||||
|
||||
public render (): React.ReactNode {
|
||||
const { className, isSubstrateV2, t } = this.props;
|
||||
const { stashId } = this.state;
|
||||
const { controllerId, hexSessionId, isBondExtraOpen, isStashValidating, isUnbondOpen, nominees, sessionIds, stashId } = this.state;
|
||||
|
||||
if (!stashId) {
|
||||
return null;
|
||||
@ -148,100 +148,79 @@ class Account extends React.PureComponent<Props, State> {
|
||||
unlocking: false
|
||||
}}
|
||||
>
|
||||
{this.renderBondExtra()}
|
||||
<BondExtra
|
||||
controllerId={controllerId}
|
||||
isOpen={isBondExtraOpen}
|
||||
onClose={this.toggleBondExtra}
|
||||
stashId={stashId}
|
||||
/>
|
||||
<Unbond
|
||||
controllerId={controllerId}
|
||||
isOpen={isUnbondOpen}
|
||||
onClose={this.toggleUnbond}
|
||||
stashId={stashId}
|
||||
/>
|
||||
{this.renderSetValidatorPrefs()}
|
||||
{this.renderNominate()}
|
||||
{this.renderSetControllerAccount()}
|
||||
{this.renderSetRewardDestination()}
|
||||
{this.renderSetSessionAccount()}
|
||||
{this.renderUnbond()}
|
||||
{this.renderValidate()}
|
||||
<div className={className}>
|
||||
<div className='staking--Accounts'>
|
||||
{this.renderControllerAccount()}
|
||||
{!isSubstrateV2 && this.renderSessionAccount()}
|
||||
{!isSubstrateV2 && sessionIds.length && (
|
||||
<div className='staking--Account-detail actions'>
|
||||
<AddressRow
|
||||
label={t('session')}
|
||||
value={sessionIds[0]}
|
||||
withAddressOrName
|
||||
withBalance={{
|
||||
available: true,
|
||||
bonded: false,
|
||||
free: false,
|
||||
redeemable: false,
|
||||
unlocking: false
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='staking--Infos'>
|
||||
<div className='staking--balances'>
|
||||
{this.renderInfos()}
|
||||
<AddressInfo
|
||||
address={stashId}
|
||||
withBalance={{
|
||||
available: false,
|
||||
bonded: true,
|
||||
free: false,
|
||||
redeemable: true,
|
||||
unlocking: true
|
||||
}}
|
||||
withRewardDestination
|
||||
withHexSessionId={ isSubstrateV2 && hexSessionId !== '0x' && hexSessionId}
|
||||
withValidatorPrefs={isStashValidating}
|
||||
/>
|
||||
</div>
|
||||
{this.renderNominee()}
|
||||
{nominees && nominees.length && (
|
||||
<div className='staking--Account-Nominee'>
|
||||
<label className='staking--label'>{t('nominating')}</label>
|
||||
{nominees.map((nomineeId, index): React.ReactNode => (
|
||||
<AddressMini
|
||||
key={index}
|
||||
value={nomineeId}
|
||||
withBalance={false}
|
||||
withBonded
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</AddressCard>
|
||||
);
|
||||
}
|
||||
|
||||
private renderBondExtra (): React.ReactNode {
|
||||
const { controllerId, isBondExtraOpen, stashId } = this.state;
|
||||
|
||||
return (
|
||||
<BondExtra
|
||||
controllerId={controllerId}
|
||||
isOpen={isBondExtraOpen}
|
||||
onClose={this.toggleBondExtra}
|
||||
stashId={stashId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderUnbond (): React.ReactNode {
|
||||
const { controllerId, isUnbondOpen, stashId } = this.state;
|
||||
|
||||
return (
|
||||
<Unbond
|
||||
controllerId={controllerId}
|
||||
isOpen={isUnbondOpen}
|
||||
onClose={this.toggleUnbond}
|
||||
stashId={stashId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderInfos (): React.ReactNode {
|
||||
const { isSubstrateV2 } = this.props;
|
||||
const { hexSessionId, isStashValidating, stashId } = this.state;
|
||||
|
||||
return (
|
||||
<AddressInfo
|
||||
address={stashId}
|
||||
withBalance={{
|
||||
available: false,
|
||||
bonded: true,
|
||||
free: false,
|
||||
redeemable: true,
|
||||
unlocking: true
|
||||
}}
|
||||
withRewardDestination
|
||||
withHexSessionId={ isSubstrateV2 && hexSessionId !== '0x' && hexSessionId}
|
||||
withValidatorPrefs={isStashValidating}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderNominee (): React.ReactNode {
|
||||
const { t } = this.props;
|
||||
const { nominees } = this.state;
|
||||
|
||||
if (!nominees || !nominees.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='staking--Account-Nominee'>
|
||||
<label className='staking--label'>{t('nominating')}</label>
|
||||
{nominees.map((nomineeId, index): React.ReactNode => (
|
||||
<AddressMini
|
||||
key={index}
|
||||
value={nomineeId}
|
||||
withBalance={false}
|
||||
withBonded
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderOnlineStatus (): React.ReactNode {
|
||||
const { onlineStatus, controllerId } = this.state;
|
||||
|
||||
@ -285,32 +264,6 @@ class Account extends React.PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
private renderSessionAccount (): React.ReactNode {
|
||||
const { t } = this.props;
|
||||
const { sessionIds } = this.state;
|
||||
|
||||
if (!sessionIds.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='staking--Account-detail actions'>
|
||||
<AddressRow
|
||||
label={t('session')}
|
||||
value={sessionIds[0]}
|
||||
withAddressOrName
|
||||
withBalance={{
|
||||
available: true,
|
||||
bonded: false,
|
||||
free: false,
|
||||
redeemable: false,
|
||||
unlocking: false
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderNominate (): React.ReactNode {
|
||||
const { stashOptions } = this.props;
|
||||
const { controllerId, isNominateOpen, nominees, stashId } = this.state;
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
import { I18nProps } from '@polkadot/react-components/types';
|
||||
import { QueryTypes, StorageEntryPromise, StorageModuleQuery } from './types';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Compact } from '@polkadot/types';
|
||||
import { Button, Labelled } from '@polkadot/react-components';
|
||||
@ -22,12 +22,6 @@ interface Props extends I18nProps {
|
||||
value: QueryTypes;
|
||||
}
|
||||
|
||||
interface State {
|
||||
inputs?: React.ReactNode[];
|
||||
Component?: React.ComponentType<{}>;
|
||||
spread: Record<number, boolean>;
|
||||
}
|
||||
|
||||
interface CacheInstance {
|
||||
Component: React.ComponentType<any>;
|
||||
render: RenderFn;
|
||||
@ -123,102 +117,95 @@ function getCachedComponent (query: QueryTypes): CacheInstance {
|
||||
return cache[id];
|
||||
}
|
||||
|
||||
class Query extends React.PureComponent<Props, State> {
|
||||
public state: State = { spread: {} };
|
||||
function Query ({ className, onRemove, value }: Props): React.ReactElement<Props> | null {
|
||||
// const [inputs, setInputs] = useState<React.ReactNode[]>([]);
|
||||
const [{ Component }, setComponent] = useState<Partial<CacheInstance>>({});
|
||||
const [isSpreadable, setIsSpreadable] = useState(false);
|
||||
const [spread, setSpread] = useState<Record<number, boolean>>({});
|
||||
|
||||
public static getDerivedStateFromProps ({ value }: Props): Pick<State, never> {
|
||||
const Component = getCachedComponent(value).Component;
|
||||
const inputs: React.ReactNode[] = isU8a(value.key)
|
||||
? []
|
||||
// FIXME We need to render the actual key params
|
||||
// const { key, params } = value;
|
||||
// const inputs = key.params.map(({ name, type }, index) => (
|
||||
// <span key={`param_${name}_${index}`}>
|
||||
// {name}={valueToText(type, params[index].value)}
|
||||
// </span>
|
||||
// ));
|
||||
: [];
|
||||
|
||||
return {
|
||||
Component,
|
||||
inputs
|
||||
};
|
||||
}
|
||||
|
||||
public render (): React.ReactNode {
|
||||
const { className, value } = this.props;
|
||||
const { Component } = this.state;
|
||||
const { id, isConst, key } = value;
|
||||
const type = isConst
|
||||
? (key as unknown as ConstValue).meta.type.toString()
|
||||
: isU8a(key)
|
||||
? 'Data'
|
||||
: typeToString(key as StorageEntryPromise);
|
||||
|
||||
if (!Component) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`storage--Query storage--actionrow ${className}`}>
|
||||
<div className='storage--actionrow-value'>
|
||||
<Labelled
|
||||
label={
|
||||
<div className='ui--Param-text'>
|
||||
{keyToName(isConst, key)}: {type}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Component />
|
||||
</Labelled>
|
||||
</div>
|
||||
<div className='storage--actionrow-buttons'>
|
||||
<div className='container'>
|
||||
{(key as StorageEntryPromise).creator && (key as StorageEntryPromise).creator.meta && ['Bytes', 'Data'].includes((key as StorageEntryPromise).creator.meta.type.toString()) && (
|
||||
<Button
|
||||
icon='ellipsis horizontal'
|
||||
key='spread'
|
||||
onClick={this.spreadHandler(id)}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
icon='close'
|
||||
isNegative
|
||||
key='close'
|
||||
onClick={this.onRemove}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
useEffect((): void => {
|
||||
setComponent(getCachedComponent(value));
|
||||
// setInputs(
|
||||
// isU8a(value.key)
|
||||
// ? []
|
||||
// // FIXME We need to render the actual key params
|
||||
// // const { key, params } = value;
|
||||
// // const inputs = key.params.map(({ name, type }, index) => (
|
||||
// // <span key={`param_${name}_${index}`}>
|
||||
// // {name}={valueToText(type, params[index].value)}
|
||||
// // </span>
|
||||
// // ));
|
||||
// : []
|
||||
// );
|
||||
setIsSpreadable(
|
||||
(value.key as StorageEntryPromise).creator &&
|
||||
(value.key as StorageEntryPromise).creator.meta &&
|
||||
['Bytes', 'Data'].includes((value.key as StorageEntryPromise).creator.meta.type.toString())
|
||||
);
|
||||
}, [value]);
|
||||
|
||||
const { id, isConst, key } = value;
|
||||
const type = isConst
|
||||
? (key as unknown as ConstValue).meta.type.toString()
|
||||
: isU8a(key)
|
||||
? 'Data'
|
||||
: typeToString(key as StorageEntryPromise);
|
||||
|
||||
if (!Component) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private spreadHandler (id: number): () => void {
|
||||
const _spreadHandler = (id: number): () => void => {
|
||||
return (): void => {
|
||||
const { spread } = this.state;
|
||||
|
||||
cache[id].Component = cache[id].refresh(true, !!spread[id]);
|
||||
spread[id] = !spread[id];
|
||||
|
||||
this.setState({
|
||||
...this.state,
|
||||
...spread,
|
||||
Component: cache[id].Component
|
||||
});
|
||||
setComponent(cache[id]);
|
||||
setSpread({ ...spread });
|
||||
};
|
||||
}
|
||||
};
|
||||
const _onRemove = (): void => {
|
||||
delete cache[value.id];
|
||||
|
||||
private onRemove = (): void => {
|
||||
const { onRemove, value: { id } } = this.props;
|
||||
onRemove(value.id);
|
||||
};
|
||||
|
||||
delete cache[id];
|
||||
|
||||
onRemove(id);
|
||||
}
|
||||
return (
|
||||
<div className={`storage--Query storage--actionrow ${className}`}>
|
||||
<div className='storage--actionrow-value'>
|
||||
<Labelled
|
||||
label={
|
||||
<div className='ui--Param-text'>
|
||||
{keyToName(isConst, key)}: {type}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Component />
|
||||
</Labelled>
|
||||
</div>
|
||||
<div className='storage--actionrow-buttons'>
|
||||
<div className='container'>
|
||||
{isSpreadable && (
|
||||
<Button
|
||||
icon='ellipsis horizontal'
|
||||
key='spread'
|
||||
onClick={_spreadHandler(id)}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
icon='close'
|
||||
isNegative
|
||||
key='close'
|
||||
onClick={_onRemove}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default translate(
|
||||
styled(Query as React.ComponentClass<Props>)`
|
||||
styled(Query)`
|
||||
margin-bottom: 0.25em;
|
||||
|
||||
label {
|
||||
|
||||
@ -4,14 +4,13 @@
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { AppProps, I18nProps } from '@polkadot/react-components/types';
|
||||
import { ApiProps } from '@polkadot/react-api/types';
|
||||
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
|
||||
import { ComponentProps } from './types';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Route, Switch } from 'react-router';
|
||||
import { Icon, Tabs } from '@polkadot/react-components';
|
||||
import { withApi, withCalls, withMulti, withObservable } from '@polkadot/react-api';
|
||||
import { withCalls, withMulti, withObservable } from '@polkadot/react-api';
|
||||
import accountObservable from '@polkadot/ui-keyring/observable/accounts';
|
||||
|
||||
import SetKey from './SetKey';
|
||||
@ -19,70 +18,23 @@ import Sudo from './Sudo';
|
||||
|
||||
import translate from './translate';
|
||||
|
||||
interface Props extends AppProps, ApiProps, I18nProps {
|
||||
interface Props extends AppProps, I18nProps {
|
||||
allAccounts: SubjectInfo;
|
||||
sudo_key?: string;
|
||||
sudoKey?: string;
|
||||
}
|
||||
|
||||
interface State {
|
||||
isMine: boolean;
|
||||
}
|
||||
function App ({ allAccounts, basePath, sudoKey, t }: Props): React.ReactElement<Props> {
|
||||
const [isMine, setIsMine] = useState(false);
|
||||
|
||||
class App extends React.PureComponent<Props, State> {
|
||||
public state: State = {
|
||||
isMine: false
|
||||
};
|
||||
|
||||
public static getDerivedStateFromProps ({ allAccounts = {}, sudo_key }: Props): State | null {
|
||||
return {
|
||||
isMine: !!sudo_key && !!Object.keys(allAccounts).find((key): boolean => key === sudo_key.toString())
|
||||
};
|
||||
}
|
||||
|
||||
public render (): React.ReactNode {
|
||||
const { basePath, t } = this.props;
|
||||
const { isMine } = this.state;
|
||||
|
||||
return (
|
||||
<main>
|
||||
<header>
|
||||
<Tabs
|
||||
basePath={basePath}
|
||||
items={[
|
||||
{
|
||||
isRoot: true,
|
||||
name: 'index',
|
||||
text: t('Sudo access')
|
||||
},
|
||||
{
|
||||
name: 'key',
|
||||
text: t('Set sudo key')
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</header>
|
||||
{isMine ? (
|
||||
<Switch>
|
||||
<Route path={`${basePath}/key`} render={this.renderComponent(SetKey)} />
|
||||
<Route render={this.renderComponent(Sudo)} />
|
||||
</Switch>
|
||||
) : (
|
||||
<article className='error padded'>
|
||||
<div>
|
||||
<Icon name='ban' />
|
||||
{t('You do not have access to the current sudo key')}
|
||||
</div>
|
||||
</article>
|
||||
)}
|
||||
</main>
|
||||
useEffect((): void => {
|
||||
setIsMine(
|
||||
!!sudoKey && !!allAccounts && Object.keys(allAccounts).some((key): boolean => key === sudoKey)
|
||||
);
|
||||
}
|
||||
}, [allAccounts, sudoKey]);
|
||||
|
||||
private renderComponent (Component: React.ComponentType<ComponentProps>): () => React.ReactNode {
|
||||
const _renderComponent = (Component: React.ComponentType<ComponentProps>): () => React.ReactNode => {
|
||||
// eslint-disable-next-line react/display-name
|
||||
return (): React.ReactNode => {
|
||||
const { allAccounts = {}, sudo_key: sudoKey = '' } = this.props;
|
||||
const { isMine } = this.state;
|
||||
|
||||
return (
|
||||
<Component
|
||||
allAccounts={allAccounts}
|
||||
@ -91,15 +43,52 @@ class App extends React.PureComponent<Props, State> {
|
||||
/>
|
||||
);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<main>
|
||||
<header>
|
||||
<Tabs
|
||||
basePath={basePath}
|
||||
items={[
|
||||
{
|
||||
isRoot: true,
|
||||
name: 'index',
|
||||
text: t('Sudo access')
|
||||
},
|
||||
{
|
||||
name: 'key',
|
||||
text: t('Set sudo key')
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</header>
|
||||
{isMine
|
||||
? (
|
||||
<Switch>
|
||||
<Route path={`${basePath}/key`} render={_renderComponent(SetKey)} />
|
||||
<Route render={_renderComponent(Sudo)} />
|
||||
</Switch>
|
||||
)
|
||||
: (
|
||||
<article className='error padded'>
|
||||
<div>
|
||||
<Icon name='ban' />
|
||||
{t('You do not have access to the current sudo key')}
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
export default withMulti(
|
||||
App,
|
||||
translate,
|
||||
withApi,
|
||||
withCalls<Props>(
|
||||
['query.sudo.key', {
|
||||
propName: 'sudoKey',
|
||||
transform: (key): string =>
|
||||
key.toString()
|
||||
}]
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
import { I18nProps as Props } from '@polkadot/react-components/types';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { Input, Output, Static } from '@polkadot/react-components';
|
||||
import { hexToU8a, isHex, stringToU8a } from '@polkadot/util';
|
||||
import { blake2AsHex } from '@polkadot/util-crypto';
|
||||
@ -17,59 +17,52 @@ interface State {
|
||||
isHexData: boolean;
|
||||
}
|
||||
|
||||
class Hash extends React.PureComponent<Props, State> {
|
||||
public state: State = {
|
||||
function Hash ({ t }: Props): React.ReactElement<Props> {
|
||||
const [{ data, hash, isHexData }, setState] = useState<State>({
|
||||
data: '',
|
||||
hash: blake2AsHex(stringToU8a(''), 256),
|
||||
isHexData: false
|
||||
});
|
||||
|
||||
const _onChangeData = (data: string): void => {
|
||||
const isHexData = isHex(data);
|
||||
|
||||
setState({
|
||||
data,
|
||||
hash: blake2AsHex(
|
||||
isHexData
|
||||
? hexToU8a(data)
|
||||
: stringToU8a(data),
|
||||
256
|
||||
),
|
||||
isHexData
|
||||
});
|
||||
};
|
||||
|
||||
public render (): React.ReactNode {
|
||||
return (
|
||||
<div className='toolbox--Hash'>
|
||||
{this.renderInput()}
|
||||
{this.renderOutput()}
|
||||
return (
|
||||
<div className='toolbox--Hash'>
|
||||
<div className='ui--row'>
|
||||
<Input
|
||||
autoFocus
|
||||
className='full'
|
||||
help={t('The input data to hash. This can be either specified as a hex value (0x-prefix) or as a string.')}
|
||||
label={t('from the following data')}
|
||||
onChange={_onChangeData}
|
||||
value={data}
|
||||
/>
|
||||
</div>
|
||||
<div className='ui--row'>
|
||||
<Static
|
||||
className='medium'
|
||||
help={t('Detection on the input string to determine if it is hex or non-hex.')}
|
||||
label={t('hex input data')}
|
||||
value={
|
||||
isHexData
|
||||
? t('Yes')
|
||||
: t('No')
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public renderInput (): React.ReactNode {
|
||||
const { t } = this.props;
|
||||
const { data, isHexData } = this.state;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='ui--row'>
|
||||
<Input
|
||||
autoFocus
|
||||
className='full'
|
||||
help={t('The input data to hash. This can be either specified as a hex value (0x-prefix) or as a string.')}
|
||||
label={t('from the following data')}
|
||||
onChange={this.onChangeData}
|
||||
value={data}
|
||||
/>
|
||||
</div>
|
||||
<div className='ui--row'>
|
||||
<Static
|
||||
className='medium'
|
||||
help={t('Detection on the input string to determine if it is hex or non-hex.')}
|
||||
label={t('hex input data')}
|
||||
value={
|
||||
isHexData
|
||||
? t('Yes')
|
||||
: t('No')
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
public renderOutput (): React.ReactNode {
|
||||
const { t } = this.props;
|
||||
const { hash } = this.state;
|
||||
|
||||
return (
|
||||
<div className='ui--row'>
|
||||
<Output
|
||||
className='full'
|
||||
@ -81,20 +74,8 @@ class Hash extends React.PureComponent<Props, State> {
|
||||
withCopy
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private onChangeData = (data: string): void => {
|
||||
const isHexData = isHex(data);
|
||||
const hash = blake2AsHex(
|
||||
isHexData
|
||||
? hexToU8a(data)
|
||||
: stringToU8a(data),
|
||||
256
|
||||
);
|
||||
|
||||
this.setState({ data, hash, isHexData });
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default translate(Hash);
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
import { I18nProps } from '@polkadot/react-components/types';
|
||||
import { KeyringPair } from '@polkadot/keyring/types';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { withMulti } from '@polkadot/react-api';
|
||||
import { Button, Input, InputAddress, Output, Static } from '@polkadot/react-components';
|
||||
@ -28,16 +28,21 @@ interface State {
|
||||
signature: string;
|
||||
}
|
||||
|
||||
class Sign extends React.PureComponent<Props, State> {
|
||||
public state: State;
|
||||
|
||||
public constructor (props: Props) {
|
||||
super(props);
|
||||
function Sign ({ className, t }: Props): React.ReactElement<Props> {
|
||||
const [state, setState] = useState<State>({
|
||||
currentPair: null,
|
||||
data: '',
|
||||
isHexData: false,
|
||||
isLocked: false,
|
||||
isUnlockVisible: false,
|
||||
signature: ''
|
||||
});
|
||||
|
||||
useEffect((): void => {
|
||||
const pairs = keyring.getPairs();
|
||||
const currentPair = pairs[0] || null;
|
||||
|
||||
this.state = {
|
||||
setState({
|
||||
currentPair,
|
||||
data: '',
|
||||
isHexData: false,
|
||||
@ -46,83 +51,62 @@ class Sign extends React.PureComponent<Props, State> {
|
||||
: false,
|
||||
isUnlockVisible: false,
|
||||
signature: ''
|
||||
};
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
public render (): React.ReactNode {
|
||||
const { className } = this.props;
|
||||
const { isLocked } = this.state;
|
||||
const _nextState = ({ currentPair = state.currentPair, data = state.data, isHexData = state.isHexData, isUnlockVisible = state.isUnlockVisible }: Partial<State>): void => {
|
||||
const isLocked = !currentPair || currentPair.isLocked;
|
||||
let signature = '';
|
||||
|
||||
return (
|
||||
<div className='toolbox--Sign'>
|
||||
{this.renderAccount()}
|
||||
<div className={className}>
|
||||
{this.renderInput()}
|
||||
{this.renderSignature()}
|
||||
<div className='unlock-overlay' hidden={!isLocked}>
|
||||
{this.renderUnlockWarning()}
|
||||
</div>
|
||||
{this.renderUnlock()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (!isLocked && currentPair) {
|
||||
signature = u8aToHex(
|
||||
currentPair.sign(
|
||||
isHexData
|
||||
? hexToU8a(data)
|
||||
: stringToU8a(data)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public renderAccount (): React.ReactNode {
|
||||
const { t } = this.props;
|
||||
setState({
|
||||
currentPair,
|
||||
data,
|
||||
isHexData,
|
||||
isLocked,
|
||||
isUnlockVisible,
|
||||
signature
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
const _toggleUnlock = (): void =>
|
||||
_nextState({ isUnlockVisible: !state.isUnlockVisible });
|
||||
const _onChangeAccount = (accountId: string | null): void =>
|
||||
_nextState({ currentPair: keyring.getPair(accountId || '') });
|
||||
const _onChangeData = (data: string): void =>
|
||||
_nextState({ data, isHexData: isHex(data) });
|
||||
|
||||
const { currentPair, data, isHexData, isLocked, isUnlockVisible, signature } = state;
|
||||
|
||||
return (
|
||||
<div className={`toolbox--Sign ${className}`}>
|
||||
<div className='ui--row'>
|
||||
<InputAddress
|
||||
className='full'
|
||||
help={t('select the account you wish to sign data with')}
|
||||
isInput={false}
|
||||
label={t('account')}
|
||||
onChange={this.onChangeAccount}
|
||||
onChange={_onChangeAccount}
|
||||
type='account'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public renderUnlockWarning (): React.ReactNode {
|
||||
const { t } = this.props;
|
||||
const { isLocked } = this.state;
|
||||
|
||||
if (!isLocked) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='unlock-overlay-warning'>
|
||||
<div className='unlock-overlay-content'>
|
||||
{t('You need to unlock this account to be able to sign data.')}<br/>
|
||||
<Button.Group>
|
||||
<Button
|
||||
isPrimary
|
||||
onClick={this.toggleUnlock}
|
||||
label={t('Unlock account')}
|
||||
icon='unlock'
|
||||
/>
|
||||
</Button.Group>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public renderInput (): React.ReactNode {
|
||||
const { t } = this.props;
|
||||
const { data, isHexData } = this.state;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='toolbox--Sign-input'>
|
||||
<div className='ui--row'>
|
||||
<Input
|
||||
autoFocus
|
||||
className='full'
|
||||
help={t('The input data to sign. This can be either specified as a hex value (0x-prefix) or as a string.')}
|
||||
label={t('sign the following data')}
|
||||
onChange={this.onChangeData}
|
||||
onChange={_onChangeData}
|
||||
value={data}
|
||||
/>
|
||||
</div>
|
||||
@ -138,122 +122,78 @@ class Sign extends React.PureComponent<Props, State> {
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
public renderSignature (): React.ReactNode {
|
||||
const { t } = this.props;
|
||||
const { signature } = this.state;
|
||||
|
||||
return (
|
||||
<div className='ui--row'>
|
||||
<Output
|
||||
className='full'
|
||||
help={t('The resulting signature of the input data, as done with the crypto algorithm from the account. (This could be non-deterministic for some types such as sr25519).')}
|
||||
isHidden={signature.length === 0}
|
||||
isMonospace
|
||||
label={t('signature of supplied data')}
|
||||
value={signature}
|
||||
withCopy
|
||||
/>
|
||||
<div className='ui--row'>
|
||||
<Output
|
||||
className='full'
|
||||
help={t('The resulting signature of the input data, as done with the crypto algorithm from the account. (This could be non-deterministic for some types such as sr25519).')}
|
||||
isHidden={signature.length === 0}
|
||||
isMonospace
|
||||
label={t('signature of supplied data')}
|
||||
value={signature}
|
||||
withCopy
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className='unlock-overlay'
|
||||
hidden={!isLocked}
|
||||
>
|
||||
{isLocked && (
|
||||
<div className='unlock-overlay-warning'>
|
||||
<div className='unlock-overlay-content'>
|
||||
{t('You need to unlock this account to be able to sign data.')}<br/>
|
||||
<Button.Group>
|
||||
<Button
|
||||
isPrimary
|
||||
onClick={_toggleUnlock}
|
||||
label={t('Unlock account')}
|
||||
icon='unlock'
|
||||
/>
|
||||
</Button.Group>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isUnlockVisible && (
|
||||
<Unlock
|
||||
onClose={_toggleUnlock}
|
||||
pair={currentPair}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public renderUnlock (): React.ReactNode {
|
||||
const { currentPair, isUnlockVisible } = this.state;
|
||||
|
||||
if (!isUnlockVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Unlock
|
||||
onClose={this.toggleUnlock}
|
||||
pair={currentPair}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private nextState = (newState: Partial<State>): void => {
|
||||
this.setState(
|
||||
(prevState: State): State => {
|
||||
const { currentPair = prevState.currentPair, data = prevState.data, isHexData = prevState.isHexData, isUnlockVisible = prevState.isUnlockVisible } = newState;
|
||||
const isLocked = !currentPair || currentPair.isLocked;
|
||||
let signature = '';
|
||||
|
||||
if (!isLocked && currentPair) {
|
||||
signature = u8aToHex(
|
||||
currentPair.sign(
|
||||
isHexData
|
||||
? hexToU8a(data)
|
||||
: stringToU8a(data)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
currentPair,
|
||||
data,
|
||||
isHexData,
|
||||
isLocked,
|
||||
isUnlockVisible,
|
||||
signature
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private toggleUnlock = (): void => {
|
||||
const { isUnlockVisible } = this.state;
|
||||
|
||||
this.nextState({
|
||||
isUnlockVisible: !isUnlockVisible
|
||||
});
|
||||
}
|
||||
|
||||
private onChangeAccount = (accountId: string | null): void => {
|
||||
const currentPair = keyring.getPair(accountId || '');
|
||||
|
||||
this.nextState({ currentPair });
|
||||
}
|
||||
|
||||
private onChangeData = (data: string): void => {
|
||||
const isHexData = isHex(data);
|
||||
|
||||
this.nextState({ data, isHexData });
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default withMulti(
|
||||
styled(Sign)`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.unlock-overlay {
|
||||
position: absolute;
|
||||
.toolbox--Sign-input {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top:0;
|
||||
left:0;
|
||||
background-color: #0f0e0e7a;
|
||||
}
|
||||
|
||||
.unlock-overlay-warning {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height:100%;
|
||||
}
|
||||
.unlock-overlay {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top:0;
|
||||
left:0;
|
||||
background-color: #0f0e0e7a;
|
||||
}
|
||||
|
||||
.unlock-overlay-content {
|
||||
color:#fff;
|
||||
text-align:center;
|
||||
.unlock-overlay-warning {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
.ui--Button-Group {
|
||||
text-align: center;
|
||||
.unlock-overlay-content {
|
||||
color:#fff;
|
||||
text-align:center;
|
||||
|
||||
.ui--Button-Group {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
|
||||
@ -63,11 +63,21 @@ class Verify extends React.PureComponent<Props, State> {
|
||||
|
||||
public render (): React.ReactNode {
|
||||
const { t } = this.props;
|
||||
const { cryptoOptions, cryptoType, data, isHexData } = this.state;
|
||||
const { cryptoOptions, cryptoType, data, defaultPublicKey, isHexData, isValid, isValidAddress, isValidSignature, signature } = this.state;
|
||||
|
||||
return (
|
||||
<div className='toolbox--Verify'>
|
||||
{this.renderAddress()}
|
||||
<div className='ui--row'>
|
||||
<InputAddress
|
||||
className='full'
|
||||
defaultValue={defaultPublicKey}
|
||||
help={t('The account that signed the input')}
|
||||
isError={!isValidAddress}
|
||||
isInput
|
||||
label={t('verify using address')}
|
||||
onChange={this.onChangeAddress}
|
||||
/>
|
||||
</div>
|
||||
<div className='ui--row'>
|
||||
<Input
|
||||
autoFocus
|
||||
@ -78,7 +88,23 @@ class Verify extends React.PureComponent<Props, State> {
|
||||
value={data}
|
||||
/>
|
||||
</div>
|
||||
{this.renderSignature()}
|
||||
<div className='ui--row'>
|
||||
<Input
|
||||
className='full'
|
||||
icon={
|
||||
<Icon
|
||||
color={isValid ? 'green' : (isValidSignature ? 'red' : undefined)}
|
||||
name={isValid ? 'check circle' : (isValidSignature ? 'exclamation circle' : 'help circle')}
|
||||
size='big'
|
||||
/>
|
||||
}
|
||||
isError={!isValidSignature}
|
||||
help={t('The signature as by the account being checked, supplied as a hex-formatted string.')}
|
||||
label={t('the supplied signature')}
|
||||
onChange={this.onChangeSignature}
|
||||
value={signature}
|
||||
/>
|
||||
</div>
|
||||
<div className='ui--row'>
|
||||
<Dropdown
|
||||
defaultValue={cryptoType}
|
||||
@ -102,50 +128,6 @@ class Verify extends React.PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
private renderAddress (): React.ReactNode {
|
||||
const { t } = this.props;
|
||||
const { defaultPublicKey, isValidAddress } = this.state;
|
||||
|
||||
return (
|
||||
<div className='ui--row'>
|
||||
<InputAddress
|
||||
className='full'
|
||||
defaultValue={defaultPublicKey}
|
||||
help={t('The account that signed the input')}
|
||||
isError={!isValidAddress}
|
||||
isInput
|
||||
label={t('verify using address')}
|
||||
onChange={this.onChangeAddress}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderSignature (): React.ReactNode {
|
||||
const { t } = this.props;
|
||||
const { isValid, isValidSignature, signature } = this.state;
|
||||
|
||||
return (
|
||||
<div className='ui--row'>
|
||||
<Input
|
||||
className='full'
|
||||
icon={
|
||||
<Icon
|
||||
color={isValid ? 'green' : (isValidSignature ? 'red' : undefined)}
|
||||
name={isValid ? 'check circle' : (isValidSignature ? 'exclamation circle' : 'help circle')}
|
||||
size='big'
|
||||
/>
|
||||
}
|
||||
isError={!isValidSignature}
|
||||
help={t('The signature as by the account being checked, supplied as a hex-formatted string.')}
|
||||
label={t('the supplied signature')}
|
||||
onChange={this.onChangeSignature}
|
||||
value={signature}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private nextState (newState: Partial<State>): void {
|
||||
this.setState(
|
||||
(prevState: State): Pick<State, never> => {
|
||||
|
||||
@ -7,7 +7,7 @@ import { ProposalIndex } from '@polkadot/types/interfaces';
|
||||
import { I18nProps } from '@polkadot/react-components/types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { withCalls, withMulti } from '@polkadot/react-api';
|
||||
@ -22,98 +22,68 @@ interface Props extends I18nProps, RouteComponentProps<{}> {
|
||||
treasury_proposalCount?: BN;
|
||||
}
|
||||
|
||||
interface State {
|
||||
isEmpty: boolean;
|
||||
isProposeOpen: boolean;
|
||||
proposalIndices: BN[];
|
||||
}
|
||||
function ProposalsBase ({ history, isApprovals = false, treasury_approvals, treasury_proposalCount, t }: Props): React.ReactElement<Props> {
|
||||
const [isEmpty, setIsEmpty] = useState(true);
|
||||
const [proposalIndices, setProposalIndices] = useState<BN[]>([]);
|
||||
|
||||
class ProposalsBase extends React.PureComponent<Props, State> {
|
||||
public state: State = {
|
||||
isEmpty: true,
|
||||
isProposeOpen: false,
|
||||
proposalIndices: [] as BN[]
|
||||
};
|
||||
|
||||
public static getDerivedStateFromProps ({ isApprovals = false, treasury_approvals = [] as BN[], treasury_proposalCount = new BN(0) }: Props): Pick<State, never> {
|
||||
useEffect((): void => {
|
||||
let proposalIndices: BN[] = [];
|
||||
|
||||
if (isApprovals) {
|
||||
proposalIndices = treasury_approvals;
|
||||
} else {
|
||||
proposalIndices = treasury_approvals || [];
|
||||
} else if (treasury_proposalCount && treasury_approvals) {
|
||||
for (let i = 0; i < treasury_proposalCount.toNumber(); i++) {
|
||||
if (!treasury_approvals.find((index): boolean => index.eqn(i))) {
|
||||
proposalIndices.push(new BN(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
return { proposalIndices };
|
||||
}
|
||||
|
||||
public render (): React.ReactNode {
|
||||
const { isApprovals, t } = this.props;
|
||||
const { isEmpty } = this.state;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Column
|
||||
emptyText={t(isApprovals ? 'No approved proposals' : 'No pending proposals')}
|
||||
headerText={t(isApprovals ? 'Approved' : 'Proposals')}
|
||||
isEmpty={isEmpty}
|
||||
>
|
||||
{this.renderProposals()}
|
||||
</Column>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
private renderProposals (): React.ReactNode {
|
||||
const { isApprovals } = this.props;
|
||||
const { proposalIndices } = this.state;
|
||||
|
||||
return proposalIndices.map((proposalId): React.ReactNode => (
|
||||
<Proposal
|
||||
isApproved={isApprovals}
|
||||
onPopulate={this.onPopulateProposal}
|
||||
onRespond={this.onRespond}
|
||||
proposalId={proposalId.toString()}
|
||||
key={proposalId.toString()}
|
||||
/>
|
||||
));
|
||||
}
|
||||
|
||||
protected onRespond = (): void => {
|
||||
const { history } = this.props;
|
||||
setProposalIndices(proposalIndices);
|
||||
}, [isApprovals, treasury_approvals, treasury_approvals]);
|
||||
|
||||
const _onRespond = (): void => {
|
||||
history.push('/council/motions');
|
||||
}
|
||||
};
|
||||
const _onPopulateProposal = (): void => {
|
||||
isEmpty && setIsEmpty(false);
|
||||
};
|
||||
|
||||
private onPopulateProposal = (): void => {
|
||||
this.setState(({ isEmpty }: State): Pick<State, never> | null => {
|
||||
if (isEmpty) {
|
||||
return { isEmpty: false };
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Column
|
||||
emptyText={isApprovals ? t('No approved proposals') : t('No pending proposals')}
|
||||
headerText={isApprovals ? t('Approved') : t('Proposals')}
|
||||
isEmpty={isEmpty}
|
||||
>
|
||||
{proposalIndices.map((proposalId): React.ReactNode => (
|
||||
<Proposal
|
||||
isApproved={isApprovals}
|
||||
onPopulate={_onPopulateProposal}
|
||||
onRespond={_onRespond}
|
||||
proposalId={proposalId.toString()}
|
||||
key={proposalId.toString()}
|
||||
/>
|
||||
))}
|
||||
</Column>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const Proposals = withMulti(
|
||||
withRouter(ProposalsBase),
|
||||
translate,
|
||||
withCalls<Props>(
|
||||
[
|
||||
'query.treasury.approvals',
|
||||
{
|
||||
transform: (value: ProposalIndex[]): BN[] =>
|
||||
value.map((proposalId): BN => new BN(proposalId))
|
||||
}
|
||||
],
|
||||
['query.treasury.approvals', {
|
||||
transform: (value: ProposalIndex[]): BN[] =>
|
||||
value.map((proposalId): BN => new BN(proposalId))
|
||||
}],
|
||||
'query.treasury.proposalCount'
|
||||
)
|
||||
);
|
||||
|
||||
export default Proposals;
|
||||
|
||||
export const Approvals = (): JSX.Element => <Proposals isApprovals />;
|
||||
export function Approvals (): React.ReactElement<{}> {
|
||||
return <Proposals isApprovals />;
|
||||
}
|
||||
|
||||
@ -3,93 +3,40 @@
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { I18nProps } from '@polkadot/react-components/types';
|
||||
import { ApiProps } from '@polkadot/react-api/types';
|
||||
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
|
||||
import { Route } from '@polkadot/apps-routing/types';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { Icon, Menu, Tooltip } from '@polkadot/react-components';
|
||||
import accountObservable from '@polkadot/ui-keyring/observable/accounts';
|
||||
import { withApi, withCalls, withMulti, withObservable } from '@polkadot/react-api';
|
||||
import { ApiContext, withCalls, withMulti, withObservable } from '@polkadot/react-api';
|
||||
import { isFunction } from '@polkadot/util';
|
||||
|
||||
import translate from '../translate';
|
||||
|
||||
interface Props extends I18nProps, ApiProps {
|
||||
interface Props extends I18nProps {
|
||||
isCollapsed: boolean;
|
||||
onClick: () => void;
|
||||
allAccounts?: SubjectInfo;
|
||||
route: Route;
|
||||
sudo_key: string;
|
||||
sudoKey: string;
|
||||
}
|
||||
|
||||
class Item extends React.PureComponent<Props> {
|
||||
private _disabledLog: Map<string, string> = new Map();
|
||||
const disabledLog: Map<string, string> = new Map();
|
||||
|
||||
public render (): React.ReactNode {
|
||||
const { route: { Modal, i18n, icon, name }, t, isCollapsed, onClick } = this.props;
|
||||
function logDisabled (route: string, message: string): void {
|
||||
if (!disabledLog.get(route)) {
|
||||
disabledLog.set(route, message);
|
||||
|
||||
if (!this.isVisible()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const body = (
|
||||
<>
|
||||
<Icon name={icon} />
|
||||
<span className='text'>{t(`sidebar.${name}`, i18n)}</span>
|
||||
<Tooltip
|
||||
offset={{ right: -4 }}
|
||||
place='right'
|
||||
text={t(`sidebar.${name}`, i18n)}
|
||||
trigger={`nav-${name}`}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Menu.Item className='apps--SideBar-Item'>
|
||||
{
|
||||
Modal
|
||||
? (
|
||||
<a
|
||||
className='apps--SideBar-Item-NavLink'
|
||||
data-for={`nav-${name}`}
|
||||
data-tip
|
||||
data-tip-disable={!isCollapsed}
|
||||
onClick={onClick}
|
||||
>
|
||||
{body}
|
||||
</a>
|
||||
)
|
||||
: (
|
||||
<NavLink
|
||||
activeClassName='apps--SideBar-Item-NavLink-active'
|
||||
className='apps--SideBar-Item-NavLink'
|
||||
data-for={`nav-${name}`}
|
||||
data-tip
|
||||
data-tip-disable={!isCollapsed}
|
||||
onClick={onClick}
|
||||
to={`/${name}`}
|
||||
>
|
||||
{body}
|
||||
</NavLink>
|
||||
)
|
||||
}
|
||||
</Menu.Item>
|
||||
);
|
||||
console.warn(`Disabling ${route}: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
private logDisabled (route: string, message: string): void {
|
||||
if (!this._disabledLog.get(route)) {
|
||||
this._disabledLog.set(route, message);
|
||||
function Item ({ allAccounts, route: { Modal, display: { isHidden, needsAccounts, needsApi, needsSudo }, i18n, icon, name }, t, isCollapsed, onClick, sudoKey }: Props): React.ReactElement<Props> | null {
|
||||
const { api, isApiConnected, isApiReady } = useContext(ApiContext);
|
||||
|
||||
console.warn(`Disabling ${route}: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
private hasApi (endpoint: string): boolean {
|
||||
const { api } = this.props;
|
||||
const _hasApi = (endpoint: string): boolean => {
|
||||
const [area, section, method] = endpoint.split('.');
|
||||
|
||||
try {
|
||||
@ -97,12 +44,10 @@ class Item extends React.PureComponent<Props> {
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private isVisible (): boolean {
|
||||
const { allAccounts = {}, isApiConnected, isApiReady, route: { display: { isHidden, needsAccounts, needsApi, needsSudo }, name }, sudo_key: sudoKey } = this.props;
|
||||
const hasAccounts = Object.keys(allAccounts).length !== 0;
|
||||
const hasSudo = !!Object.keys(allAccounts).find((address): boolean => address === sudoKey);
|
||||
};
|
||||
const _isVisible = (): boolean => {
|
||||
const hasAccounts = !!allAccounts && Object.keys(allAccounts).length !== 0;
|
||||
const hasSudo = !!allAccounts && Object.keys(allAccounts).some((address): boolean => address === sudoKey);
|
||||
|
||||
if (isHidden) {
|
||||
return false;
|
||||
@ -114,33 +59,81 @@ class Item extends React.PureComponent<Props> {
|
||||
return false;
|
||||
} else if (needsSudo) {
|
||||
if (!hasSudo) {
|
||||
this.logDisabled('sudo', 'Sudo key not available');
|
||||
logDisabled(name, 'Sudo key not available');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const notFound = needsApi.filter((endpoint: string | string[]): boolean => {
|
||||
const hasApi = Array.isArray(endpoint)
|
||||
? endpoint.reduce((hasApi, endpoint): boolean => hasApi || this.hasApi(endpoint), false)
|
||||
: this.hasApi(endpoint);
|
||||
? endpoint.reduce((hasApi, endpoint): boolean => hasApi || _hasApi(endpoint), false)
|
||||
: _hasApi(endpoint);
|
||||
|
||||
return !hasApi;
|
||||
});
|
||||
|
||||
if (notFound.length !== 0) {
|
||||
this.logDisabled(name, `API not available: ${notFound}`);
|
||||
logDisabled(name, `API not available: ${notFound}`);
|
||||
}
|
||||
|
||||
return notFound.length === 0;
|
||||
};
|
||||
|
||||
if (!_isVisible()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const body = (
|
||||
<>
|
||||
<Icon name={icon} />
|
||||
<span className='text'>{t(`sidebar.${name}`, i18n)}</span>
|
||||
<Tooltip
|
||||
offset={{ right: -4 }}
|
||||
place='right'
|
||||
text={t(`sidebar.${name}`, i18n)}
|
||||
trigger={`nav-${name}`}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Menu.Item className='apps--SideBar-Item'>
|
||||
{Modal
|
||||
? (
|
||||
<a
|
||||
className='apps--SideBar-Item-NavLink'
|
||||
data-for={`nav-${name}`}
|
||||
data-tip
|
||||
data-tip-disable={!isCollapsed}
|
||||
onClick={onClick}
|
||||
>
|
||||
{body}
|
||||
</a>
|
||||
)
|
||||
: (
|
||||
<NavLink
|
||||
activeClassName='apps--SideBar-Item-NavLink-active'
|
||||
className='apps--SideBar-Item-NavLink'
|
||||
data-for={`nav-${name}`}
|
||||
data-tip
|
||||
data-tip-disable={!isCollapsed}
|
||||
onClick={onClick}
|
||||
to={`/${name}`}
|
||||
>
|
||||
{body}
|
||||
</NavLink>
|
||||
)
|
||||
}
|
||||
</Menu.Item>
|
||||
);
|
||||
}
|
||||
|
||||
export default withMulti(
|
||||
Item,
|
||||
translate,
|
||||
withApi,
|
||||
withCalls<Props>(
|
||||
['query.sudo.key', {
|
||||
propName: 'sudoKey',
|
||||
transform: (key): string =>
|
||||
key.toString()
|
||||
}]
|
||||
|
||||
@ -1,20 +1,17 @@
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
// Copyright 2017-2019 @polkadot/apps 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 { Route } from '@polkadot/apps-routing/types';
|
||||
import { ApiProps } from '@polkadot/react-api/types';
|
||||
import { I18nProps } from '@polkadot/react-components/types';
|
||||
import { SIDEBAR_MENU_THRESHOLD } from '../constants';
|
||||
|
||||
import './SideBar.css';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useContext, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Responsive } from 'semantic-ui-react';
|
||||
import routing from '@polkadot/apps-routing';
|
||||
import { withApi, withMulti } from '@polkadot/react-api';
|
||||
import { ApiContext } from '@polkadot/react-api';
|
||||
import { Button, ChainImg, Icon, Menu, media } from '@polkadot/react-components';
|
||||
import { classes } from '@polkadot/react-components/util';
|
||||
import { BestNumber, Chain } from '@polkadot/react-query';
|
||||
@ -24,7 +21,7 @@ import Item from './Item';
|
||||
import NodeInfo from './NodeInfo';
|
||||
import NetworkModal from '../modals/Network';
|
||||
|
||||
interface Props extends ApiProps, I18nProps {
|
||||
interface Props extends I18nProps {
|
||||
className?: string;
|
||||
collapse: () => void;
|
||||
handleResize: () => void;
|
||||
@ -33,222 +30,137 @@ interface Props extends ApiProps, I18nProps {
|
||||
toggleMenu: () => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
modals: Record<string, boolean>;
|
||||
}
|
||||
function SideBar ({ className, collapse, handleResize, isCollapsed, toggleMenu, menuOpen }: Props): React.ReactElement<Props> {
|
||||
const { api, isApiReady } = useContext(ApiContext);
|
||||
const [modals, setModals] = useState<Record<string, boolean>>(
|
||||
routing.routes.reduce((result: Record<string, boolean>, route): Record<string, boolean> => {
|
||||
if (route && route.Modal) {
|
||||
result[route.name] = false;
|
||||
}
|
||||
|
||||
class SideBar extends React.PureComponent<Props, State> {
|
||||
public state: State;
|
||||
return result;
|
||||
}, { network: false })
|
||||
);
|
||||
|
||||
public constructor (props: Props) {
|
||||
super(props);
|
||||
const _toggleModal = (name: string): () => void =>
|
||||
(): void => setModals({ ...modals, [name]: !modals[name] });
|
||||
|
||||
// setup modals for each of the actual modal routes
|
||||
this.state = {
|
||||
modals: routing.routes.reduce((result, route): Record<string, boolean> => {
|
||||
if (route && route.Modal) {
|
||||
result[route.name] = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}, { network: false } as unknown as Record<string, boolean>)
|
||||
};
|
||||
}
|
||||
|
||||
public render (): React.ReactNode {
|
||||
const { className, handleResize, isCollapsed, toggleMenu, menuOpen } = this.props;
|
||||
|
||||
return (
|
||||
<Responsive
|
||||
onUpdate={handleResize}
|
||||
className={classes(className, 'apps-SideBar-Wrapper', isCollapsed ? 'collapsed' : 'expanded')}
|
||||
>
|
||||
<ChainImg
|
||||
className={`toggleImg ${menuOpen ? 'closed' : 'open delayed'}`}
|
||||
onClick={toggleMenu}
|
||||
/>
|
||||
{this.renderModals()}
|
||||
{this.state.modals.network && (
|
||||
<NetworkModal onClose={this.toggleNetworkModal}/>
|
||||
)}
|
||||
<div className='apps--SideBar'>
|
||||
<Menu
|
||||
secondary
|
||||
vertical
|
||||
>
|
||||
<div className='apps-SideBar-Scroll'>
|
||||
{this.renderLogo()}
|
||||
{this.renderRoutes()}
|
||||
<Menu.Divider hidden />
|
||||
{this.renderGithub()}
|
||||
{this.renderWiki()}
|
||||
<Menu.Divider hidden />
|
||||
{
|
||||
isCollapsed
|
||||
? undefined
|
||||
: <NodeInfo />
|
||||
}
|
||||
</div>
|
||||
{this.renderCollapse()}
|
||||
</Menu>
|
||||
<Responsive minWidth={SIDEBAR_MENU_THRESHOLD}>
|
||||
return (
|
||||
<Responsive
|
||||
onUpdate={handleResize}
|
||||
className={classes(className, 'apps-SideBar-Wrapper', isCollapsed ? 'collapsed' : 'expanded')}
|
||||
>
|
||||
<ChainImg
|
||||
className={`toggleImg ${menuOpen ? 'closed' : 'open delayed'}`}
|
||||
onClick={toggleMenu}
|
||||
/>
|
||||
{routing.routes.map((route): React.ReactNode => (
|
||||
route && route.Modal
|
||||
? route.Modal && modals[route.name]
|
||||
? (
|
||||
<route.Modal
|
||||
key={route.name}
|
||||
onClose={_toggleModal(route.name)}
|
||||
/>
|
||||
)
|
||||
: <div key={route.name} />
|
||||
: null
|
||||
))}
|
||||
{modals.network && (
|
||||
<NetworkModal onClose={_toggleModal('network')}/>
|
||||
)}
|
||||
<div className='apps--SideBar'>
|
||||
<Menu
|
||||
secondary
|
||||
vertical
|
||||
>
|
||||
<div className='apps-SideBar-Scroll'>
|
||||
<div
|
||||
className='apps--SideBar-toggle'
|
||||
onClick={this.props.collapse}
|
||||
className='apps--SideBar-logo'
|
||||
onClick={_toggleModal('network')}
|
||||
>
|
||||
<ChainImg />
|
||||
<div className='info'>
|
||||
<Chain className='chain' />
|
||||
{isApiReady && (
|
||||
<div className='runtimeVersion'>version {api.runtimeVersion.specVersion.toNumber()}</div>
|
||||
)}
|
||||
<BestNumber label='#' />
|
||||
</div>
|
||||
</div>
|
||||
{routing.routes.map((route, index): React.ReactNode => (
|
||||
route
|
||||
? (
|
||||
<Item
|
||||
isCollapsed={isCollapsed}
|
||||
key={route.name}
|
||||
route={route}
|
||||
onClick={
|
||||
route.Modal
|
||||
? _toggleModal(route.name)
|
||||
: handleResize
|
||||
}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<Menu.Divider
|
||||
hidden
|
||||
key={index}
|
||||
/>
|
||||
)
|
||||
))}
|
||||
<Menu.Divider hidden />
|
||||
<Menu.Item className='apps--SideBar-Item'>
|
||||
<a
|
||||
className='apps--SideBar-Item-NavLink'
|
||||
href='https://github.com/polkadot-js/apps'
|
||||
rel='noopener noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
<Icon name='github' /><span className='text'>GitHub</span>
|
||||
</a>
|
||||
</Menu.Item>
|
||||
<Menu.Item className='apps--SideBar-Item'>
|
||||
<a
|
||||
className='apps--SideBar-Item-NavLink'
|
||||
href='https://wiki.polkadot.network'
|
||||
rel='noopener noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
<Icon name='book' /><span className='text'>Wiki</span>
|
||||
</a>
|
||||
</Menu.Item>
|
||||
<Menu.Divider hidden />
|
||||
{
|
||||
isCollapsed
|
||||
? undefined
|
||||
: <NodeInfo />
|
||||
}
|
||||
</div>
|
||||
<Responsive
|
||||
minWidth={SIDEBAR_MENU_THRESHOLD}
|
||||
className={`apps--SideBar-collapse ${isCollapsed ? 'collapsed' : 'expanded'}`}
|
||||
>
|
||||
<Button
|
||||
icon={`angle double ${isCollapsed ? 'right' : 'left'}`}
|
||||
isBasic
|
||||
isCircular
|
||||
onClick={collapse}
|
||||
/>
|
||||
</Responsive>
|
||||
</div>
|
||||
</Responsive>
|
||||
);
|
||||
}
|
||||
|
||||
private renderCollapse (): React.ReactNode {
|
||||
const { isCollapsed } = this.props;
|
||||
|
||||
return (
|
||||
<Responsive
|
||||
minWidth={SIDEBAR_MENU_THRESHOLD}
|
||||
className={`apps--SideBar-collapse ${isCollapsed ? 'collapsed' : 'expanded'}`}
|
||||
>
|
||||
<Button
|
||||
icon={`angle double ${isCollapsed ? 'right' : 'left'}`}
|
||||
isBasic
|
||||
isCircular
|
||||
onClick={this.props.collapse}
|
||||
/>
|
||||
</Responsive>
|
||||
);
|
||||
}
|
||||
|
||||
private renderLogo (): React.ReactNode {
|
||||
const { api, isApiReady } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className='apps--SideBar-logo'
|
||||
onClick={this.toggleNetworkModal}
|
||||
>
|
||||
<ChainImg />
|
||||
<div className='info'>
|
||||
<Chain className='chain' />
|
||||
{isApiReady &&
|
||||
<div className='runtimeVersion'>version {api.runtimeVersion.specVersion.toNumber()}</div>
|
||||
}
|
||||
<BestNumber label='#' />
|
||||
</div>
|
||||
</Menu>
|
||||
<Responsive minWidth={SIDEBAR_MENU_THRESHOLD}>
|
||||
<div
|
||||
className='apps--SideBar-toggle'
|
||||
onClick={collapse}
|
||||
/>
|
||||
</Responsive>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderModals (): React.ReactNode {
|
||||
const { modals } = this.state;
|
||||
const filtered = routing.routes.filter((route): any => route && route.Modal) as Route[];
|
||||
|
||||
return filtered.map(({ name, Modal }): React.ReactNode => (
|
||||
Modal && modals[name]
|
||||
? (
|
||||
<Modal
|
||||
key={name}
|
||||
onClose={this.closeModal(name)}
|
||||
/>
|
||||
)
|
||||
: <div key={name} />
|
||||
));
|
||||
}
|
||||
|
||||
private renderRoutes (): React.ReactNode {
|
||||
const { handleResize, isCollapsed } = this.props;
|
||||
|
||||
return routing.routes.map((route, index): React.ReactNode => (
|
||||
route
|
||||
? (
|
||||
<Item
|
||||
isCollapsed={isCollapsed}
|
||||
key={route.name}
|
||||
route={route}
|
||||
onClick={
|
||||
route.Modal
|
||||
? this.openModal(route.name)
|
||||
: handleResize
|
||||
}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<Menu.Divider
|
||||
hidden
|
||||
key={index}
|
||||
/>
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
private renderGithub (): React.ReactNode {
|
||||
return (
|
||||
<Menu.Item className='apps--SideBar-Item'>
|
||||
<a
|
||||
className='apps--SideBar-Item-NavLink'
|
||||
href='https://github.com/polkadot-js/apps'
|
||||
rel='noopener noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
<Icon name='github' /><span className='text'>GitHub</span>
|
||||
</a>
|
||||
</Menu.Item>
|
||||
);
|
||||
}
|
||||
|
||||
private renderWiki (): React.ReactNode {
|
||||
return (
|
||||
<Menu.Item className='apps--SideBar-Item'>
|
||||
<a
|
||||
className='apps--SideBar-Item-NavLink'
|
||||
href='https://wiki.polkadot.network'
|
||||
rel='noopener noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
<Icon name='book' /><span className='text'>Wiki</span>
|
||||
</a>
|
||||
</Menu.Item>
|
||||
);
|
||||
}
|
||||
|
||||
private closeModal = (name: string): () => void => {
|
||||
return (): void => {
|
||||
this.setState(({ modals }): State => ({
|
||||
modals: {
|
||||
...modals,
|
||||
[name]: false
|
||||
}
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
private openModal = (name: string): () => void => {
|
||||
return (): void => {
|
||||
this.setState(({ modals }): State => {
|
||||
return {
|
||||
modals: {
|
||||
...modals,
|
||||
[name]: true
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
private toggleNetworkModal = (): void => {
|
||||
this.setState(({ modals }): State => {
|
||||
return {
|
||||
modals: {
|
||||
...modals,
|
||||
network: !modals.network
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
</Responsive>
|
||||
);
|
||||
}
|
||||
|
||||
export default withMulti(
|
||||
export default translate(
|
||||
styled(SideBar)`
|
||||
.toggleImg {
|
||||
cursor: pointer;
|
||||
@ -274,7 +186,5 @@ export default withMulti(
|
||||
top: -2.9rem !important;
|
||||
`}
|
||||
}
|
||||
`,
|
||||
translate,
|
||||
withApi
|
||||
`
|
||||
);
|
||||
|
||||
@ -73,22 +73,21 @@ function InputFile ({ accept, className, clearContent, help, isDisabled, isError
|
||||
const dropZone = (
|
||||
<Dropzone
|
||||
accept={accept}
|
||||
className={classes('ui--InputFile', isError ? 'error' : '', className)}
|
||||
className={classes('ui--InputFile', isError && 'error', className)}
|
||||
disabled={isDisabled}
|
||||
multiple={false}
|
||||
ref={dropRef}
|
||||
onDrop={_onDrop}
|
||||
>
|
||||
<em className='label'>
|
||||
{
|
||||
!file || clearContent
|
||||
? placeholder || t('click to select or drag and drop the file here')
|
||||
: placeholder || t('{{name}} ({{size}} bytes)', {
|
||||
replace: {
|
||||
name: file.name,
|
||||
size: formatNumber(file.size)
|
||||
}
|
||||
})
|
||||
{!file || clearContent
|
||||
? placeholder || t('click to select or drag and drop the file here')
|
||||
: placeholder || t('{{name}} ({{size}} bytes)', {
|
||||
replace: {
|
||||
name: file.name,
|
||||
size: formatNumber(file.size)
|
||||
}
|
||||
})
|
||||
}
|
||||
</em>
|
||||
</Dropzone>
|
||||
|
||||
@ -56,6 +56,41 @@ interface State {
|
||||
|
||||
let qrId = 0;
|
||||
|
||||
function extractExternal (accountId?: string | null): { isExternal: boolean; isHardware: boolean; hardwareType?: string } {
|
||||
if (!accountId) {
|
||||
return { isExternal: false, isHardware: false };
|
||||
}
|
||||
|
||||
let publicKey;
|
||||
|
||||
try {
|
||||
publicKey = keyring.decodeAddress(accountId);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
return { isExternal: false, isHardware: false };
|
||||
}
|
||||
|
||||
const pair = keyring.getPair(publicKey);
|
||||
|
||||
return {
|
||||
isExternal: !!pair.meta.isExternal,
|
||||
isHardware: !!pair.meta.isHardware,
|
||||
hardwareType: pair.meta.hardwareType
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
async function makeExtrinsicSignature (payload: SignerPayloadJSON, { id, signerCb }: QueueTx, pair: KeyringPair): Promise<void> {
|
||||
console.log('makeExtrinsicSignature: payload ::', JSON.stringify(payload));
|
||||
|
||||
const result = createType('ExtrinsicPayload', payload, { version: payload.version }).sign(pair);
|
||||
|
||||
if (isFunction(signerCb)) {
|
||||
signerCb(id, { id, ...result });
|
||||
}
|
||||
}
|
||||
|
||||
class Signer extends React.PureComponent<Props, State> {
|
||||
public state: State = {
|
||||
isSendable: false,
|
||||
@ -142,7 +177,7 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { isExternal, isHardware, hardwareType } = this.isExternal(currentItem.accountId);
|
||||
const { isExternal, isHardware, hardwareType } = extractExternal(currentItem.accountId);
|
||||
|
||||
return (
|
||||
<Modal.Actions>
|
||||
@ -256,7 +291,7 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
const { currentItem, isSendable, password, unlockError } = this.state;
|
||||
|
||||
const { isExternal } = currentItem
|
||||
? this.isExternal(currentItem.accountId)
|
||||
? extractExternal(currentItem.accountId)
|
||||
: { isExternal: false };
|
||||
|
||||
if (!isSendable || !currentItem || currentItem.isUnsigned || isExternal) {
|
||||
@ -280,30 +315,6 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
private isExternal (accountId?: string | null): { isExternal: boolean; isHardware: boolean; hardwareType?: string } {
|
||||
if (!accountId) {
|
||||
return { isExternal: false, isHardware: false };
|
||||
}
|
||||
|
||||
let publicKey;
|
||||
|
||||
try {
|
||||
publicKey = keyring.decodeAddress(accountId);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
return { isExternal: false, isHardware: false };
|
||||
}
|
||||
|
||||
const pair = keyring.getPair(publicKey);
|
||||
|
||||
return {
|
||||
isExternal: !!pair.meta.isExternal,
|
||||
isHardware: !!pair.meta.isHardware,
|
||||
hardwareType: pair.meta.hardwareType
|
||||
};
|
||||
}
|
||||
|
||||
private unlockAccount (accountId: string, password?: string): string | null {
|
||||
let publicKey;
|
||||
|
||||
@ -450,7 +461,7 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
if (payload) {
|
||||
return this.makeExtrinsicSignature(
|
||||
return makeExtrinsicSignature(
|
||||
{
|
||||
...payload,
|
||||
...((isV2 && tip && !payload.tip) ? { tip: tip.toString() } : {})
|
||||
@ -577,17 +588,6 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
private async makeExtrinsicSignature (payload: SignerPayloadJSON, { id, signerCb }: QueueTx, pair: KeyringPair): Promise<void> {
|
||||
console.log('makeExtrinsicSignature: payload ::', JSON.stringify(payload));
|
||||
|
||||
const result = createType('ExtrinsicPayload', payload, { version: payload.version }).sign(pair);
|
||||
|
||||
if (isFunction(signerCb)) {
|
||||
signerCb(id, { id, ...result });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user