More func component conversions (#1641)

* More func component conversions

* ExtrinsicInput

* Selection inputs -> functional

* Cleanups

* State Selection & linting fixes
This commit is contained in:
Jaco Greeff 2019-09-23 15:37:55 +02:00 committed by GitHub
parent b70b7d3aa0
commit aef86d373a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 507 additions and 725 deletions

View File

@ -37,98 +37,98 @@ interface CacheInstance {
const cache: CacheInstance[] = [];
function keyToName (isConst: boolean, _key: Uint8Array | StorageEntryPromise | ConstValue): string {
if (isConst) {
const key = _key as ConstValue;
return `const ${key.section}.${key.method}`;
}
const key = _key as Uint8Array | StorageEntryPromise;
if (isU8a(key)) {
const u8a = Compact.stripLengthPrefix(key);
// If the string starts with `:`, handle it as a pure string
return u8a[0] === 0x3a
? u8aToString(u8a)
: u8aToHex(u8a);
}
return `${key.creator.section}.${key.creator.method}`;
}
function typeToString (key: StorageEntryPromise): string {
const type = key.creator.meta.type.isDoubleMap
? key.creator.meta.type.asDoubleMap.value.toString()
: key.creator.meta.type.toString();
return key.creator.meta.modifier.isOptional
? `Option<${type}>`
: type;
}
function createComponent (type: string, Component: React.ComponentType<any>, defaultProps: DefaultProps, renderHelper: ComponentRenderer): { Component: React.ComponentType<any>; render: (createComponent: RenderFn) => React.ComponentType<any>; refresh: (swallowErrors: boolean, contentShorten: boolean) => React.ComponentType<any> } {
return {
Component,
// In order to replace the default component during runtime we can provide a RenderFn to create a new 'plugged' component
render: (createComponent: RenderFn): React.ComponentType<any> => {
return renderHelper(createComponent, defaultProps);
},
// In order to modify the parameters which are used to render the default component, we can use this method
refresh: (swallowErrors: boolean, contentShorten: boolean): React.ComponentType<any> => {
return renderHelper(
(value: any): React.ReactNode => valueToText(type, value, swallowErrors, contentShorten),
defaultProps
);
}
};
}
function getCachedComponent (query: QueryTypes): CacheInstance {
const { id, isConst, key, params = [] } = query as StorageModuleQuery;
if (!cache[id]) {
let renderHelper;
let type: string;
if (isConst) {
const { meta, method, section } = key as unknown as ConstValue;
renderHelper = withCallDiv(`consts.${section}.${method}`);
type = meta.type.toString();
} else {
const values: any[] = params.map(({ value }): any => value);
// render function to create an element for the query results which is plugged to the api
renderHelper = withCallDiv('subscribe', {
paramName: 'params',
paramValid: true,
params: [key, ...values]
});
type = key.creator && key.creator.meta
? typeToString(key)
: 'Data';
}
const defaultProps = { className: 'ui--output' };
const Component = renderHelper(
// By default we render a simple div node component with the query results in it
(value: any): React.ReactNode => valueToText(type, value, true, true),
defaultProps
);
cache[query.id] = createComponent(type, Component, defaultProps, renderHelper);
}
return cache[id];
}
class Query extends React.PureComponent<Props, State> {
public state: State = { spread: {} };
private static keyToName (isConst: boolean, _key: Uint8Array | StorageEntryPromise | ConstValue): string {
if (isConst) {
const key = _key as ConstValue;
return `const ${key.section}.${key.method}`;
}
const key = _key as Uint8Array | StorageEntryPromise;
if (isU8a(key)) {
const u8a = Compact.stripLengthPrefix(key);
// If the string starts with `:`, handle it as a pure string
return u8a[0] === 0x3a
? u8aToString(u8a)
: u8aToHex(u8a);
}
return `${key.creator.section}.${key.creator.method}`;
}
private static typeToString (key: StorageEntryPromise): string {
const type = key.creator.meta.type.isDoubleMap
? key.creator.meta.type.asDoubleMap.value.toString()
: key.creator.meta.type.toString();
return key.creator.meta.modifier.isOptional
? `Option<${type}>`
: type;
}
public static getCachedComponent (query: QueryTypes): CacheInstance {
const { id, isConst, key, params = [] } = query as StorageModuleQuery;
if (!cache[id]) {
let renderHelper;
let type: string;
if (isConst) {
const { meta, method, section } = key as unknown as ConstValue;
renderHelper = withCallDiv(`consts.${section}.${method}`);
type = meta.type.toString();
} else {
const values: any[] = params.map(({ value }): any => value);
// render function to create an element for the query results which is plugged to the api
renderHelper = withCallDiv('subscribe', {
paramName: 'params',
paramValid: true,
params: [key, ...values]
});
type = key.creator.meta
? Query.typeToString(key)
: 'Data';
}
const defaultProps = { className: 'ui--output' };
const Component = renderHelper(
// By default we render a simple div node component with the query results in it
(value: any): React.ReactNode => valueToText(type, value, true, true),
defaultProps
);
cache[query.id] = Query.createComponent(type, Component, defaultProps, renderHelper);
}
return cache[id];
}
public static createComponent (type: string, Component: React.ComponentType<any>, defaultProps: DefaultProps, renderHelper: ComponentRenderer): { Component: React.ComponentType<any>; render: (createComponent: RenderFn) => React.ComponentType<any>; refresh: (swallowErrors: boolean, contentShorten: boolean) => React.ComponentType<any> } {
return {
Component,
// In order to replace the default component during runtime we can provide a RenderFn to create a new 'plugged' component
render: (createComponent: RenderFn): React.ComponentType<any> => {
return renderHelper(createComponent, defaultProps);
},
// In order to modify the parameters which are used to render the default component, we can use this method
refresh: (swallowErrors: boolean, contentShorten: boolean): React.ComponentType<any> => {
return renderHelper(
(value: any): React.ReactNode => valueToText(type, value, swallowErrors, contentShorten),
defaultProps
);
}
};
}
public static getDerivedStateFromProps ({ value }: Props): Pick<State, never> {
const Component = Query.getCachedComponent(value).Component;
const Component = getCachedComponent(value).Component;
const inputs: React.ReactNode[] = isU8a(value.key)
? []
// FIXME We need to render the actual key params
@ -149,12 +149,12 @@ class Query extends React.PureComponent<Props, State> {
public render (): React.ReactNode {
const { className, value } = this.props;
const { Component } = this.state;
const { isConst, key } = value;
const { id, isConst, key } = value;
const type = isConst
? (key as unknown as ConstValue).meta.type.toString()
: isU8a(key)
? 'Data'
: Query.typeToString(key as StorageEntryPromise);
: typeToString(key as StorageEntryPromise);
if (!Component) {
return null;
@ -166,7 +166,7 @@ class Query extends React.PureComponent<Props, State> {
<Labelled
label={
<div className='ui--Param-text'>
{Query.keyToName(isConst, key)}: {type}
{keyToName(isConst, key)}: {type}
</div>
}
>
@ -175,45 +175,25 @@ class Query extends React.PureComponent<Props, State> {
</div>
<div className='storage--actionrow-buttons'>
<div className='container'>
{this.renderButtons()}
{(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>
);
}
private renderButtons (): React.ReactNode {
const { id, key } = this.props.value as StorageModuleQuery;
const buttons = [
<Button
icon='close'
isNegative
key='close'
onClick={this.onRemove}
/>
];
if (key.creator && key.creator.meta && ['Bytes', 'Data'].includes(key.creator.meta.type.toString())) {
// TODO We are currently not performing a copy
// buttons.unshift(
// <Button
// icon='copy'
// onClick={this.copyHandler(id)}
// />
// );
buttons.unshift(
<Button
icon='ellipsis horizontal'
key='spread'
onClick={this.spreadHandler(id)}
/>
);
}
return buttons;
}
private spreadHandler (id: number): () => void {
return (): void => {
const { spread } = this.state;
@ -238,20 +218,22 @@ class Query extends React.PureComponent<Props, State> {
}
}
export default translate(styled(Query as React.ComponentClass<Props>)`
margin-bottom: 0.25em;
export default translate(
styled(Query as React.ComponentClass<Props>)`
margin-bottom: 0.25em;
label {
text-transform: none !important;
}
label {
text-transform: none !important;
}
.ui.disabled.dropdown.selection {
color: #aaa;
opacity: 1;
}
.ui.disabled.dropdown.selection {
color: #aaa;
opacity: 1;
}
.ui--IdentityIcon {
margin: -10px 0;
vertical-align: middle;
}
`);
.ui--IdentityIcon {
margin: -10px 0;
vertical-align: middle;
}
`
);

View File

@ -2,96 +2,67 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { ApiProps } from '@polkadot/react-api/types';
import { I18nProps } from '@polkadot/react-components/types';
import { TabItem } from '@polkadot/react-components/Tabs';
import { ComponentProps, QueryTypes, ParitalQueryTypes } from '../types';
import React from 'react';
import React, { useContext } from 'react';
import { Route, Switch } from 'react-router';
import { ApiContext } from '@polkadot/react-api';
import { Tabs } from '@polkadot/react-components';
import { withApi } from '@polkadot/react-api';
import Consts from './Consts';
import Modules from './Modules';
import Raw from './Raw';
import translate from '../translate';
interface Props extends ApiProps, I18nProps {
interface Props extends I18nProps {
basePath: string;
onAdd: (query: QueryTypes) => void;
}
interface State {
items: TabItem[];
}
let id = -1;
class Selection extends React.PureComponent<Props, State> {
public constructor (props: Props) {
super(props);
function Selection ({ basePath, onAdd, t }: Props): React.ReactElement<Props> {
const { isSubstrateV2 } = useContext(ApiContext);
const _onAdd = (query: ParitalQueryTypes): void => onAdd({ ...query, id: ++id });
const _renderComponent = (Component: React.ComponentType<ComponentProps>): () => React.ReactNode =>
// eslint-disable-next-line react/display-name
(): React.ReactNode => <Component onAdd={_onAdd} />;
const { t } = this.props;
this.state = {
items: [
{
isRoot: true,
name: 'modules',
text: t('Storage')
},
{
name: 'constants',
text: t('Constants')
},
{
name: 'raw',
text: t('Raw storage')
}
]
};
}
public render (): React.ReactNode {
const { basePath, isSubstrateV2 } = this.props;
const { items } = this.state;
const hidden = isSubstrateV2 ? [] : ['constants'];
return (
<>
<header>
<Tabs
basePath={basePath}
hidden={hidden}
items={items}
/>
</header>
<Switch>
<Route path={`${basePath}/constants`} render={this.renderComponent(Consts)} />
<Route path={`${basePath}/raw`} render={this.renderComponent(Raw)} />
<Route render={this.renderComponent(Modules)} />
</Switch>
</>
);
}
private renderComponent (Component: React.ComponentType<ComponentProps>): () => React.ReactNode {
return (): React.ReactNode => {
return (
<Component onAdd={this.onAdd} />
);
};
}
private onAdd = (query: ParitalQueryTypes): void => {
const { onAdd } = this.props;
onAdd({
...query,
id: ++id
});
}
return (
<>
<header>
<Tabs
basePath={basePath}
hidden={
isSubstrateV2
? []
: ['constants']
}
items={[
{
isRoot: true,
name: 'modules',
text: t('Storage')
},
{
name: 'constants',
text: t('Constants')
},
{
name: 'raw',
text: t('Raw storage')
}
]}
/>
</header>
<Switch>
<Route path={`${basePath}/constants`} render={_renderComponent(Consts)} />
<Route path={`${basePath}/raw`} render={_renderComponent(Raw)} />
<Route render={_renderComponent(Modules)} />
</Switch>
</>
);
}
export default translate(withApi(Selection));
export default translate(Selection);

View File

@ -3,15 +3,15 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { ConstantCodec } from '@polkadot/api-metadata/consts/types';
import { ApiProps } from '@polkadot/react-api/types';
import { DropdownOptions } from '../util/types';
import { I18nProps } from '../types';
import { ConstValue, ConstValueBase } from './types';
import '../InputExtrinsic/InputExtrinsic.css';
import React from 'react';
import { withApi, withMulti } from '@polkadot/react-api';
import React, { useContext, useState } from 'react';
import { ApiPromise } from '@polkadot/api';
import { ApiContext } from '@polkadot/react-api';
import Labelled from '../Labelled';
import translate from '../translate';
@ -20,7 +20,7 @@ import SelectSection from './SelectSection';
import keyOptions from './options/key';
import sectionOptions from './options/section';
interface Props extends ApiProps, I18nProps {
interface Props extends I18nProps {
defaultValue: ConstValueBase;
help?: React.ReactNode;
isError?: boolean;
@ -29,104 +29,75 @@ interface Props extends ApiProps, I18nProps {
withLabel?: boolean;
}
interface State {
optionsMethod: DropdownOptions;
optionsSection: DropdownOptions;
value: ConstValue;
function getValue (api: ApiPromise, { method, section }: ConstValueBase): ConstValue {
const firstSec = Object.keys(api.consts)[0];
const firstMet = Object.keys(api.consts[firstSec])[0];
const value = (api.consts[section] && api.consts[section][method])
? { method, section }
: { method: firstMet, section: firstSec };
return {
...value,
meta: (api.consts[value.section][value.method] as ConstantCodec).meta
};
}
class InputConsts extends React.PureComponent<Props, State> {
public state: State;
function InputConsts ({ className, defaultValue, help, label, onChange, style, withLabel }: Props): React.ReactElement<Props> {
const { api } = useContext(ApiContext);
const [optionsMethod, setOptionsMethod] = useState<DropdownOptions>(keyOptions(api, defaultValue.section));
const [optionsSection] = useState<DropdownOptions>(sectionOptions(api));
const [value, setValue] = useState<ConstValue>(getValue(api, defaultValue));
public constructor (props: Props) {
super(props);
const { api, defaultValue: { method, section } } = this.props;
const firstSec = Object.keys(api.consts)[0];
const firstMet = Object.keys(api.consts[firstSec])[0];
const value = (api.consts[section] && api.consts[section][method])
? { method, section }
: { method: firstMet, section: firstSec };
this.state = {
optionsMethod: keyOptions(api, section),
optionsSection: sectionOptions(api),
value: {
...value,
meta: (api.consts[value.section][value.method] as ConstantCodec).meta
}
};
}
public render (): React.ReactNode {
const { className, help, label, style, withLabel } = this.props;
const { optionsMethod, optionsSection, value } = this.state;
return (
<div
className={className}
style={style}
>
<Labelled
help={help}
label={label}
withLabel={withLabel}
>
<div className=' ui--DropdownLinked ui--row'>
<SelectSection
className='small'
onChange={this.onSectionChange}
options={optionsSection}
value={value}
/>
<SelectKey
className='large'
onChange={this.onKeyChange}
options={optionsMethod}
value={value}
/>
</div>
</Labelled>
</div>
);
}
private onKeyChange = (newValue: ConstValueBase): void => {
const { api, onChange } = this.props;
const { value: saveValue } = this.state;
if (saveValue.section === newValue.section && saveValue.method === newValue.method) {
const _onKeyChange = (newValue: ConstValueBase): void => {
if (value.section === newValue.section && value.method === newValue.method) {
return;
}
const { method, section } = newValue;
const meta = (api.consts[section][method] as ConstantCodec).meta;
const value = { meta, method, section };
const updated = { meta, method, section };
this.setState({ value }, (): void =>
onChange && onChange(value)
);
}
private onSectionChange = (newSection: string): void => {
const { api } = this.props;
const { value } = this.state;
if (newSection === value.section) {
setValue(updated);
onChange && onChange(updated);
};
const _onSectionChange = (section: string): void => {
if (section === value.section) {
return;
}
const optionsMethod = keyOptions(api, newSection);
const newValue = { method: optionsMethod[0].value, section: newSection };
const optionsMethod = keyOptions(api, section);
this.setState({ optionsMethod }, (): void =>
this.onKeyChange(newValue)
);
}
setOptionsMethod(optionsMethod);
_onKeyChange({ method: optionsMethod[0].value, section });
};
return (
<div
className={className}
style={style}
>
<Labelled
help={help}
label={label}
withLabel={withLabel}
>
<div className=' ui--DropdownLinked ui--row'>
<SelectSection
className='small'
onChange={_onSectionChange}
options={optionsSection}
value={value}
/>
<SelectKey
className='large'
onChange={_onKeyChange}
options={optionsMethod}
value={value}
/>
</div>
</Labelled>
</div>
);
}
export default withMulti(
InputConsts,
translate,
withApi
);
export default translate(InputConsts);

View File

@ -3,14 +3,13 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { CallFunction } from '@polkadot/types/types';
import { ApiProps } from '@polkadot/react-api/types';
import { I18nProps } from '../types';
import { DropdownOptions } from '../util/types';
import './InputExtrinsic.css';
import React from 'react';
import { withApi, withMulti } from '@polkadot/react-api';
import React, { useContext, useState } from 'react';
import { ApiContext } from '@polkadot/react-api';
import Labelled from '../Labelled';
import translate from '../translate';
@ -19,7 +18,7 @@ import SelectSection from './SelectSection';
import methodOptions from './options/method';
import sectionOptions from './options/section';
interface Props extends ApiProps, I18nProps {
interface Props extends I18nProps {
defaultValue: CallFunction;
help?: React.ReactNode;
isDisabled?: boolean;
@ -30,96 +29,60 @@ interface Props extends ApiProps, I18nProps {
withLabel?: boolean;
}
interface State {
optionsMethod?: DropdownOptions;
optionsSection?: DropdownOptions;
value: CallFunction;
}
class InputExtrinsic extends React.PureComponent<Props, State> {
public state: State;
public constructor (props: Props) {
super(props);
this.state = {
value: this.props.defaultValue
};
}
public static getDerivedStateFromProps ({ api }: Props, { value }: State): Pick<State, never> {
return {
optionsMethod: methodOptions(api, value.section),
optionsSection: sectionOptions(api)
};
}
public render (): React.ReactNode {
const { api, className, help, label, style, withLabel } = this.props;
const { optionsMethod, optionsSection, value } = this.state;
return (
<div
className={className}
style={style}
>
<Labelled
help={help}
label={label}
withLabel={withLabel}
>
<div className=' ui--DropdownLinked ui--row'>
<SelectSection
className='small'
onChange={this.onSectionChange}
options={optionsSection || []}
value={value}
/>
<SelectMethod
api={api}
className='large'
onChange={this.onKeyChange}
options={optionsMethod || []}
value={value}
/>
</div>
</Labelled>
</div>
);
}
private onKeyChange = (newValue: CallFunction): void => {
const { onChange } = this.props;
const { value } = this.state;
function InputExtrinsic ({ className, defaultValue, help, label, onChange, style, withLabel }: Props): React.ReactElement<Props> {
const { api } = useContext(ApiContext);
const [optionsMethod, setOptionsMethod] = useState<DropdownOptions>(methodOptions(api, defaultValue.section));
const [optionsSection] = useState<DropdownOptions>(sectionOptions(api));
const [value, setValue] = useState<CallFunction>((): CallFunction => defaultValue);
const _onKeyChange = (newValue: CallFunction): void => {
if (value.section === newValue.section && value.method === newValue.method) {
return;
}
this.setState({ value: newValue }, (): void =>
onChange(newValue)
);
}
private onSectionChange = (newSection: string): void => {
const { api } = this.props;
const { value } = this.state;
if (newSection === value.section) {
// set this via callback, since the we are setting a function (aletrnatively... we have issues)
setValue((): CallFunction => newValue);
onChange(newValue);
};
const _onSectionChange = (section: string): void => {
if (section === value.section) {
return;
}
const optionsMethod = methodOptions(api, newSection);
const fn = api.tx[newSection][optionsMethod[0].value];
const optionsMethod = methodOptions(api, section);
this.setState({ optionsMethod }, (): void =>
this.onKeyChange(fn)
);
}
setOptionsMethod(optionsMethod);
_onKeyChange(api.tx[section][optionsMethod[0].value]);
};
return (
<div
className={className}
style={style}
>
<Labelled
help={help}
label={label}
withLabel={withLabel}
>
<div className=' ui--DropdownLinked ui--row'>
<SelectSection
className='small'
onChange={_onSectionChange}
options={optionsSection}
value={value}
/>
<SelectMethod
api={api}
className='large'
onChange={_onKeyChange}
options={optionsMethod}
value={value}
/>
</div>
</Labelled>
</div>
);
}
export default withMulti(
InputExtrinsic,
translate,
withApi
);
export default translate(InputExtrinsic);

View File

@ -5,15 +5,14 @@
// TODO: We have a lot shared between this and InputExtrinsic & InputStorage
import { RpcMethod } from '@polkadot/jsonrpc/types';
import { ApiProps } from '@polkadot/react-api/types';
import { DropdownOptions } from '../util/types';
import { I18nProps } from '../types';
import '../InputExtrinsic/InputExtrinsic.css';
import React from 'react';
import React, { useContext, useState } from 'react';
import map from '@polkadot/jsonrpc';
import { withApi } from '@polkadot/react-api';
import { ApiContext } from '@polkadot/react-api';
import Labelled from '../Labelled';
import translate from '../translate';
@ -22,7 +21,7 @@ import SelectSection from './SelectSection';
import methodOptions from './options/method';
import sectionOptions from './options/section';
interface Props extends ApiProps, I18nProps {
interface Props extends I18nProps {
defaultValue: RpcMethod;
help?: React.ReactNode;
isError?: boolean;
@ -31,88 +30,59 @@ interface Props extends ApiProps, I18nProps {
withLabel?: boolean;
}
interface State {
optionsMethod: DropdownOptions;
optionsSection: DropdownOptions;
value: RpcMethod;
}
class InputRpc extends React.PureComponent<Props, State> {
public state: State;
public constructor (props: Props) {
super(props);
const { section } = this.props.defaultValue;
this.state = {
optionsMethod: methodOptions(props.api, section),
optionsSection: sectionOptions(props.api),
value: this.props.defaultValue
};
}
public render (): React.ReactNode {
const { className, help, label, style, withLabel } = this.props;
const { optionsMethod, optionsSection, value } = this.state;
return (
<div
className={className}
style={style}
>
<Labelled
help={help}
label={label}
withLabel={withLabel}
>
<div className=' ui--DropdownLinked ui--row'>
<SelectSection
className='small'
onChange={this.onSectionChange}
options={optionsSection}
value={value}
/>
<SelectMethod
className='large'
onChange={this.onMethodChange}
options={optionsMethod}
value={value}
/>
</div>
</Labelled>
</div>
);
}
private onMethodChange = (newValue: RpcMethod): void => {
const { onChange } = this.props;
const { value } = this.state;
function InputRpc ({ className, defaultValue, help, label, onChange, style, withLabel }: Props): React.ReactElement<Props> {
const { api } = useContext(ApiContext);
const [optionsMethod, setOptionsMethod] = useState<DropdownOptions>(methodOptions(api, defaultValue.section));
const [optionsSection] = useState<DropdownOptions>(sectionOptions(api));
const [value, setValue] = useState<RpcMethod>((): RpcMethod => defaultValue);
const _onMethodChange = (newValue: RpcMethod): void => {
if (value.section === newValue.section && value.method === newValue.method) {
return;
}
this.setState({ value: newValue }, (): void =>
onChange && onChange(newValue)
);
}
private onSectionChange = (newSection: string): void => {
const { api } = this.props;
const { value } = this.state;
if (newSection === value.section) {
// set via callback since the method is a function itself
setValue((): RpcMethod => newValue);
onChange && onChange(newValue);
};
const _onSectionChange = (section: string): void => {
if (section === value.section) {
return;
}
const optionsMethod = methodOptions(api, newSection);
const newValue = map[newSection].methods[optionsMethod[0].value];
const optionsMethod = methodOptions(api, section);
this.setState({ optionsMethod }, (): void =>
this.onMethodChange(newValue)
);
}
setOptionsMethod(optionsMethod);
_onMethodChange(map[section].methods[optionsMethod[0].value]);
};
return (
<div
className={className}
style={style}
>
<Labelled
help={help}
label={label}
withLabel={withLabel}
>
<div className=' ui--DropdownLinked ui--row'>
<SelectSection
className='small'
onChange={_onSectionChange}
options={optionsSection}
value={value}
/>
<SelectMethod
className='large'
onChange={_onMethodChange}
options={optionsMethod}
value={value}
/>
</div>
</Labelled>
</div>
);
}
export default translate(withApi(InputRpc));
export default translate(InputRpc);

View File

@ -4,15 +4,14 @@
// TODO: We have a lot shared between this and InputExtrinsic
import { ApiProps } from '@polkadot/react-api/types';
import { StorageEntryPromise } from '@polkadot/api/types';
import { DropdownOptions } from '../util/types';
import { I18nProps } from '../types';
import '../InputExtrinsic/InputExtrinsic.css';
import React from 'react';
import { withApi, withMulti } from '@polkadot/react-api';
import React, { useContext, useState } from 'react';
import { ApiContext } from '@polkadot/react-api';
import Labelled from '../Labelled';
import translate from '../translate';
@ -21,7 +20,7 @@ import SelectSection from './SelectSection';
import keyOptions from './options/key';
import sectionOptions from './options/section';
interface Props extends ApiProps, I18nProps {
interface Props extends I18nProps {
defaultValue: StorageEntryPromise;
help?: React.ReactNode;
isError?: boolean;
@ -30,92 +29,59 @@ interface Props extends ApiProps, I18nProps {
withLabel?: boolean;
}
interface State {
optionsMethod: DropdownOptions;
optionsSection: DropdownOptions;
value: StorageEntryPromise;
}
class InputStorage extends React.PureComponent<Props, State> {
public state: State;
public constructor (props: Props) {
super(props);
const { api, defaultValue: { creator: { method, section } } } = this.props;
this.state = {
optionsMethod: keyOptions(api, section),
optionsSection: sectionOptions(api),
value: api.query[section][method]
};
}
public render (): React.ReactNode {
const { className, help, label, style, withLabel } = this.props;
const { optionsMethod, optionsSection, value } = this.state;
return (
<div
className={className}
style={style}
>
<Labelled
help={help}
label={label}
withLabel={withLabel}
>
<div className=' ui--DropdownLinked ui--row'>
<SelectSection
className='small'
onChange={this.onSectionChange}
options={optionsSection}
value={value}
/>
<SelectKey
className='large'
onChange={this.onKeyChange}
options={optionsMethod}
value={value}
/>
</div>
</Labelled>
</div>
);
}
private onKeyChange = (newValue: StorageEntryPromise): void => {
const { onChange } = this.props;
const { value } = this.state;
function InputStorage ({ className, defaultValue, help, label, onChange, style, withLabel }: Props): React.ReactElement<Props> {
const { api } = useContext(ApiContext);
const [optionsMethod, setOptionsMethod] = useState<DropdownOptions>(keyOptions(api, defaultValue.creator.section));
const [optionsSection] = useState<DropdownOptions>(sectionOptions(api));
const [value, setValue] = useState<StorageEntryPromise>((): StorageEntryPromise => defaultValue);
const _onKeyChange = (newValue: StorageEntryPromise): void => {
if (value.creator.section === newValue.creator.section && value.creator.method === newValue.creator.method) {
return;
}
this.setState({ value: newValue }, (): void =>
onChange && onChange(newValue)
);
}
private onSectionChange = (newSection: string): void => {
const { api } = this.props;
const { value } = this.state;
if (newSection === value.creator.section) {
// set via callback
setValue((): StorageEntryPromise => newValue);
onChange && onChange(newValue);
};
const _onSectionChange = (section: string): void => {
if (section === value.creator.section) {
return;
}
const optionsMethod = keyOptions(api, newSection);
const newValue = api.query[newSection][optionsMethod[0].value];
const optionsMethod = keyOptions(api, section);
this.setState({ optionsMethod }, (): void =>
this.onKeyChange(newValue)
);
}
setOptionsMethod(optionsMethod);
_onKeyChange(api.query[section][optionsMethod[0].value]);
};
return (
<div
className={className}
style={style}
>
<Labelled
help={help}
label={label}
withLabel={withLabel}
>
<div className=' ui--DropdownLinked ui--row'>
<SelectSection
className='small'
onChange={_onSectionChange}
options={optionsSection}
value={value}
/>
<SelectKey
className='large'
onChange={_onKeyChange}
options={optionsMethod}
value={value}
/>
</div>
</Labelled>
</div>
);
}
export default withMulti(
InputStorage,
translate,
withApi
);
export default translate(InputStorage);

View File

@ -3,16 +3,15 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { ApiProps } from '@polkadot/react-api/types';
import { I18nProps } from '@polkadot/react-components/types';
import { DerivedFees, DerivedBalances, DerivedContractFees } from '@polkadot/api-derive/types';
import { IExtrinsic } from '@polkadot/types/types';
import { ExtraFees } from './types';
import BN from 'bn.js';
import React from 'react';
import React, { useContext, useState, useEffect } from 'react';
import { Compact, UInt } from '@polkadot/types';
import { withCalls } from '@polkadot/react-api';
import { ApiContext, withCalls } from '@polkadot/react-api';
import { Icon } from '@polkadot/react-components';
import { compactToU8a, formatBalance } from '@polkadot/util';
@ -23,7 +22,7 @@ import Proposal from './Proposal';
import Transfer from './Transfer';
import { MAX_SIZE_BYTES, MAX_SIZE_MB, ZERO_BALANCE, ZERO_FEES_BALANCES, ZERO_FEES_CONTRACT } from './constants';
interface State extends ExtraFees {
interface State {
allFees: BN;
allTotal: BN;
allWarn: boolean;
@ -35,7 +34,7 @@ interface State extends ExtraFees {
overLimit: boolean;
}
interface Props extends ApiProps, I18nProps {
interface Props extends I18nProps {
balances_fees?: DerivedFees;
balances_all?: DerivedBalances;
contract_fees?: DerivedContractFees;
@ -65,23 +64,26 @@ export const calcTxLength = (extrinsic?: IExtrinsic | null, nonce?: BN, tip?: BN
);
};
export class FeeDisplay extends React.PureComponent<Props, State> {
public state: State = {
export function FeeDisplay ({ accountId, balances_all = ZERO_BALANCE, balances_fees = ZERO_FEES_BALANCES, className, contract_fees = ZERO_FEES_CONTRACT, extrinsic, isSendable, onChange, system_accountNonce = ZERO, t, tip }: Props): React.ReactElement<Props> | null {
const { api } = useContext(ApiContext);
const [state, setState] = useState<State>({
allFees: ZERO,
allTotal: ZERO,
allWarn: false,
extraAmount: ZERO,
extraFees: ZERO,
extraWarn: false,
hasAvailable: false,
isRemovable: false,
isReserved: false,
overLimit: false
};
});
const [extra, setExtra] = useState<ExtraFees>({
extraAmount: ZERO,
extraFees: ZERO,
extraWarn: false
});
public static getDerivedStateFromProps ({ accountId, balances_all = ZERO_BALANCE, api, extrinsic, balances_fees = ZERO_FEES_BALANCES, system_accountNonce = ZERO, tip }: Props, prevState: State): State | null {
useEffect((): void => {
if (!accountId || !extrinsic) {
return null;
return;
}
const fn = api.findCall(extrinsic.callIndex);
@ -89,15 +91,15 @@ export class FeeDisplay extends React.PureComponent<Props, State> {
const extSection = fn.section;
const txLength = calcTxLength(extrinsic, system_accountNonce, tip);
const isSameExtrinsic = prevState.extMethod === extMethod && prevState.extSection === extSection;
const isSameExtrinsic = state.extMethod === extMethod && state.extSection === extSection;
const extraAmount = isSameExtrinsic
? prevState.extraAmount
? extra.extraAmount
: ZERO;
const extraFees = isSameExtrinsic
? prevState.extraFees
? extra.extraFees
: ZERO;
const extraWarn = isSameExtrinsic
? prevState.extraWarn
? extra.extraWarn
: false;
const allFees = extraFees
.add(balances_fees.transactionBaseFee)
@ -110,185 +112,142 @@ export class FeeDisplay extends React.PureComponent<Props, State> {
const allWarn = extraWarn;
const overLimit = txLength.gten(MAX_SIZE_BYTES);
return {
(hasAvailable !== state.hasAvailable) && onChange && onChange(hasAvailable);
setExtra({
extraAmount,
extraFees,
extraWarn
});
setState({
allFees,
allTotal,
allWarn,
extMethod,
extSection,
extraAmount,
extraFees,
extraWarn,
hasAvailable,
isRemovable,
isReserved,
overLimit
};
});
}, [accountId, balances_all, extrinsic, balances_fees, system_accountNonce, tip]);
if (!accountId) {
return null;
}
public componentDidUpdate (): void {
const { onChange } = this.props;
const { hasAvailable } = this.state;
const { allFees, allTotal, allWarn, extMethod, extSection, hasAvailable, isRemovable, isReserved, overLimit } = state;
const feeClass = !hasAvailable || overLimit || isRemovable
? 'error'
: allWarn
? 'warning'
: 'normal';
onChange && onChange(hasAvailable);
}
public render (): React.ReactNode {
const { accountId, balances_fees, className, isSendable, t } = this.props;
const { allFees, allTotal, allWarn, hasAvailable, isRemovable, isReserved, overLimit } = this.state;
if (!accountId) {
return null;
}
const feeClass = !hasAvailable || overLimit || isRemovable
? 'error'
: allWarn
? 'warning'
: 'normal';
// display all the errors, warning and information messages (in that order)
return (
<article
className={[className, feeClass, 'padded'].join(' ')}
key='txinfo'
>
{
isSendable
? undefined
: <div><Icon name='ban' />{t('The selected account does not exist on your keyring')}</div>
}
{
hasAvailable
? undefined
: <div><Icon name='ban' />{t('The selected account does not have the required balance available for this transaction')}</div>
}
{
overLimit
? <div><Icon name='ban' />{t(`This transaction will be rejected by the node as it is greater than the maximum size of ${MAX_SIZE_MB}MB`)}</div>
: undefined
}
{
isRemovable && hasAvailable
? (
<div>
<Icon name='ban' />
{t('Submitting this transaction will drop the account balance to below the existential amount ({{existentialDeposit}}), which can result in the account being removed from the chain state and its associated funds burned.',
{
replace: {
existentialDeposit: formatBalance(balances_fees && balances_fees.existentialDeposit)
}
}
)}
</div>
)
: undefined
}
{this.renderTransfer()}
{this.renderProposal()}
{this.renderCall()}
{this.renderDeploy()}
{
isReserved
? <div><Icon name='arrow right' />{t('This account does have a reserved/locked balance, not taken into account')}</div>
: undefined
}
<div><Icon name='arrow right' />{t('Fees includes the transaction fee and the per-byte fee')}</div>
<div><Icon name='arrow right' />{t('Fees totalling {{fees}} will be applied to the submission', {
// display all the errors, warning and information messages (in that order)
return (
<article
className={[className, feeClass, 'padded'].join(' ')}
key='txinfo'
>
{isSendable && (
<div>
<Icon name='ban' />
{t('The selected account does not exist on your keyring')}
</div>
)}
{!hasAvailable && (
<div>
<Icon name='ban' />
{t('The selected account does not have the required balance available for this transaction')}
</div>
)}
{overLimit && (
<div>
<Icon name='ban' />
{t(`This transaction will be rejected by the node as it is greater than the maximum size of ${MAX_SIZE_MB}MB`)}
</div>
)}
{isRemovable && hasAvailable && balances_fees && (
<div>
<Icon name='ban' />
{t('Submitting this transaction will drop the account balance to below the existential amount ({{existentialDeposit}}), which can result in the account being removed from the chain state and its associated funds burned.',
{
replace: {
existentialDeposit: formatBalance(balances_fees.existentialDeposit)
}
}
)}
</div>
)}
{balances_fees && extrinsic && (
<>
{(extSection === 'balances' && extMethod === 'transfer') && (
<Transfer
amount={extrinsic.args[1]}
fees={balances_fees}
recipientId={extrinsic.args[0]}
onChange={setExtra}
/>
)}
{(extSection === 'democracy' && extMethod === 'propose') && (
<Proposal
deposit={extrinsic.args[1]}
fees={balances_fees}
onChange={setExtra}
/>
)}
{(extSection === 'contract') && (
<>
{(extMethod === 'call') && (
<ContractCall
endowment={extrinsic.args[1] as unknown as Compact<UInt>}
fees={contract_fees}
onChange={setExtra}
/>
)}
{(extMethod === 'create') && (
<ContractDeploy
endowment={extrinsic.args[0] as unknown as Compact<UInt>}
fees={contract_fees}
onChange={setExtra}
/>
)}
</>
)}
</>
)}
{isReserved && (
<div>
<Icon name='arrow right' />
{t('This account does have a reserved/locked balance, not taken into account')}
</div>
)}
<div>
<Icon name='arrow right' />
{t('Fees includes the transaction fee and the per-byte fee')}
</div>
<div>
<Icon name='arrow right' />
{t('Fees totalling {{fees}} will be applied to the submission', {
replace: {
fees: formatBalance(allFees)
}
})}</div>
<div><Icon name='arrow right' />{t('{{total}} estimated total amount (fees + value)', {
})}
</div>
<div>
<Icon name='arrow right' />
{t('{{total}} estimated total amount (fees + value)', {
replace: {
total: formatBalance(allTotal)
}
})}</div>
<div><Icon name='dot circle outline' />{t('Estimation does not account for the transaction weight')}</div>
</article>
);
}
private renderProposal (): React.ReactNode {
const { extrinsic, balances_fees } = this.props;
const { extMethod, extSection } = this.state;
if (!balances_fees || !extrinsic || extSection !== 'democracy' || extMethod !== 'propose') {
return null;
}
const [, deposit] = extrinsic.args;
return (
<Proposal
deposit={deposit}
fees={balances_fees}
onChange={this.onExtraUpdate}
/>
);
}
private renderTransfer (): React.ReactNode {
const { extrinsic, balances_fees } = this.props;
const { extMethod, extSection } = this.state;
if (!balances_fees || !extrinsic || extSection !== 'balances' || extMethod !== 'transfer') {
return null;
}
const [recipientId, amount] = extrinsic.args;
return (
<Transfer
amount={amount}
fees={balances_fees}
recipientId={recipientId}
onChange={this.onExtraUpdate}
/>
);
}
private renderCall (): React.ReactNode {
const { extrinsic, contract_fees = ZERO_FEES_CONTRACT } = this.props;
const { extMethod, extSection } = this.state;
if (!contract_fees || !extrinsic || extSection !== 'contract' || extMethod !== 'call') {
return null;
}
const [, endowment] = extrinsic.args;
return (
<ContractCall
endowment={endowment as unknown as Compact<UInt>}
fees={contract_fees}
onChange={this.onExtraUpdate}
/>
);
}
private renderDeploy (): React.ReactNode {
const { extrinsic, contract_fees = ZERO_FEES_CONTRACT } = this.props;
const { extMethod, extSection } = this.state;
if (!contract_fees || !extrinsic || extSection !== 'contract' || extMethod !== 'create') {
return null;
}
const [endowment] = extrinsic.args;
return (
<ContractDeploy
endowment={endowment as unknown as Compact<UInt>}
fees={contract_fees}
onChange={this.onExtraUpdate}
/>
);
}
private onExtraUpdate = (extra: ExtraFees): void => {
this.setState({ ...extra });
}
})}
</div>
<div>
<Icon name='dot circle outline' />
{t('Estimation does not account for the transaction weight')}
</div>
</article>
);
}
export default translate(