Allow optional params in state/toolbox (#1554)

* Allow optional params in state/toolbox

* RPC calls work with optional
This commit is contained in:
Jaco Greeff 2019-08-28 17:30:11 +02:00 committed by GitHub
parent bcf197bd1e
commit 9beece08c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 178 additions and 74 deletions

View File

@ -139,6 +139,7 @@ class Referendum extends React.PureComponent<Props, State> {
</Static>
<VoteThreshold
isDisabled
isOptional={false}
defaultValue={{ isValid: true, value: threshold }}
label={t('vote threshold')}
name='voteThreshold'

View File

@ -3,8 +3,7 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { RpcMethod } from '@polkadot/jsonrpc/types';
import { TypeDef } from '@polkadot/types/types';
import { RawParam } from '@polkadot/react-params/types';
import { ParamDef, RawParam } from '@polkadot/react-params/types';
import { I18nProps } from '@polkadot/react-components/types';
import { QueueTxRpcAdd } from '@polkadot/react-components/Status/types';
@ -40,7 +39,8 @@ class Selection extends TxComponent<Props, State> {
public render (): React.ReactNode {
const { t } = this.props;
const { isValid, rpc } = this.state;
const params = rpc.params.map(({ name, type }): { name: string; type: TypeDef } => ({
const params = rpc.params.map(({ isOptional, name, type }): ParamDef => ({
isOptional,
name,
type: getTypeDef(type)
}));
@ -76,9 +76,11 @@ class Selection extends TxComponent<Props, State> {
this.setState(
(prevState: State): State => {
const { rpc = prevState.rpc, accountId = prevState.accountId, values = prevState.values } = newState;
const reqCount = rpc.params.reduce((count, { isOptional }): number => count + (isOptional ? 0 : 1), 0);
const isValid = values.reduce((isValid, value): boolean => {
return isValid && value.isValid === true;
}, rpc.params.length === values.length);
}, reqCount <= values.length);
return {
isValid,
rpc,

View File

@ -19,7 +19,7 @@ interface Props extends BareProps {
class Toggle extends React.PureComponent<Props> {
public render (): React.ReactNode {
const { className, asSwitch, defaultValue, isDisabled, value, label } = this.props;
const { className, asSwitch = true, defaultValue, isDisabled, value, label } = this.props;
return (
<div className={className}>

View File

@ -29,7 +29,7 @@ export default class Amount extends React.PureComponent<Props> {
style={style}
>
<Input
className={isDisabled ? 'full' : 'medium'}
className='full'
defaultValue={defaultValue}
isDisabled={isDisabled}
isError={isError}

View File

@ -23,7 +23,7 @@ class Balance extends React.PureComponent<Props> {
style={style}
>
<InputBalance
className={isDisabled ? 'full' : 'large'}
className='full'
defaultValue={defaultValue}
isDisabled={isDisabled}
isError={isError}

View File

@ -20,7 +20,7 @@ interface Props extends BareProps {
export default class Base extends React.PureComponent<Props> {
public render (): React.ReactNode {
const { children, className, isDisabled, label, size = 'medium', style, withLabel } = this.props;
const { children, className, label, size = 'full', style, withLabel } = this.props;
return (
<Bare
@ -28,7 +28,7 @@ export default class Base extends React.PureComponent<Props> {
style={style}
>
<Labelled
className={isDisabled ? 'full' : size}
className={size}
label={label}
withEllipsis
withLabel={withLabel}

View File

@ -27,7 +27,7 @@ export default class BoolParam extends React.PureComponent<Props> {
style={style}
>
<Dropdown
className={isDisabled ? 'full' : 'medium'}
className='full'
defaultValue={defaultValue}
isDisabled={isDisabled}
isError={isError}

View File

@ -30,7 +30,7 @@ export default class Bytes extends React.PureComponent<Props, State> {
}
private renderInput (): React.ReactNode {
const { className, defaultValue, isDisabled, isError, label, name, onChange, onEnter, style, type, withLabel } = this.props;
const { className, defaultValue, isDisabled, isOptional, isError, label, name, onChange, onEnter, style, type, withLabel } = this.props;
return (
<BaseBytes
@ -38,12 +38,12 @@ export default class Bytes extends React.PureComponent<Props, State> {
defaultValue={defaultValue}
isDisabled={isDisabled}
isError={isError}
isOptional={isOptional}
label={label}
length={-1}
name={name}
onChange={onChange}
onEnter={onEnter}
size='full'
style={style}
type={type}
withLabel={withLabel}

View File

@ -31,13 +31,14 @@ export default class Code extends React.PureComponent<Props> {
}
private renderDisabled (): React.ReactNode {
const { className, defaultValue, isError, label, onEnter, style, type, withLabel } = this.props;
const { className, defaultValue, isError, isOptional, label, onEnter, style, type, withLabel } = this.props;
return (
<Bytes
className={className}
defaultValue={defaultValue}
isError={isError}
isOptional={isOptional}
label={label}
onEnter={onEnter}
style={style}

View File

@ -75,7 +75,7 @@ export default class EnumParam extends React.PureComponent<Props, State> {
style={style}
>
<Dropdown
className='large'
className='full'
defaultValue={initialValue}
isDisabled={isDisabled}
isError={isError}

View File

@ -10,7 +10,7 @@ import BaseBytes from './BaseBytes';
export default class Hash extends React.PureComponent<Props> {
public render (): React.ReactNode {
const { className, defaultValue, isDisabled, isError, label, name, onChange, onEnter, style, type, withLabel } = this.props;
const { className, defaultValue, isDisabled, isError, isOptional, label, name, onChange, onEnter, style, type, withLabel } = this.props;
return (
<BaseBytes
@ -18,12 +18,12 @@ export default class Hash extends React.PureComponent<Props> {
defaultValue={defaultValue}
isDisabled={isDisabled}
isError={isError}
isOptional={isOptional}
label={label}
length={32}
name={name}
onChange={onChange}
onEnter={onEnter}
size={isDisabled ? 'full' : 'large'}
style={style}
type={type}
withLabel={withLabel}

View File

@ -75,7 +75,6 @@ class KeyValueArray extends React.PureComponent<Props, State> {
<Base
className={className}
label={label}
size='full'
style={style}
>
<div />
@ -88,6 +87,7 @@ class KeyValueArray extends React.PureComponent<Props, State> {
<Bytes
defaultValue={{ value } as unknown as RawParam}
isDisabled
isOptional={false}
key={keyHex}
label={keyHex}
name={keyHex}

View File

@ -22,6 +22,7 @@ export default class Code extends React.PureComponent<Props> {
className={className}
defaultValue={defaultValue}
isDisabled={isDisabled}
isOptional={false}
isError={isError}
label={label}
onChange={this.onChange}

View File

@ -11,11 +11,11 @@ import translate from '@polkadot/react-components/translate';
import Bare from './Bare';
type Props = BareProps & WithTranslation & {
interface Props extends BareProps, WithTranslation {
asHex?: boolean;
defaultValue: RawParam;
withLabel?: boolean;
};
}
class StaticParam extends React.PureComponent<Props> {
public render (): React.ReactNode {

View File

@ -53,7 +53,6 @@ export default class StructParam extends React.PureComponent<Props, State> {
<Base
className={className}
label={label}
size='full'
style={style}
withLabel={withLabel}
>

View File

@ -66,6 +66,7 @@ export default class Tuple extends React.PureComponent<Props, State> {
<Component
defaultValue={values[index] || {}}
isDisabled={isDisabled}
isOptional={false}
key={index}
label={sub[index]}
onChange={this.onChange(index)}

View File

@ -16,7 +16,7 @@ interface Props extends BareProps {
export default class Unknown extends React.PureComponent<Props> {
public render (): React.ReactNode {
const { className, defaultValue, isDisabled, isError, label, name, onChange, onEnter, style, type } = this.props;
const { className, defaultValue, isDisabled, isError, isOptional, label, name, onChange, onEnter, style, type } = this.props;
if (isDisabled) {
return <Static {...this.props} />;
@ -29,12 +29,12 @@ export default class Unknown extends React.PureComponent<Props> {
defaultValue={defaultValue}
isDisabled={isDisabled}
isError={isError}
isOptional={isOptional}
label={label}
length={-1}
name={name}
onChange={onChange}
onEnter={onEnter}
size='full'
style={style}
type={type}
withLength={false}

View File

@ -73,6 +73,7 @@ class Vector extends React.PureComponent<Props, State> {
<Component
defaultValue={value}
isDisabled={isDisabled}
isOptional={false}
key={index}
label={`${index}: ${subType.type}`}
onChange={this.onChange(index)}

View File

@ -31,7 +31,7 @@ export default class Vote extends React.PureComponent<Props> {
style={style}
>
<Dropdown
className={isDisabled ? 'full' : 'medium'}
className='full'
defaultValue={defaultValue}
isDisabled={isDisabled}
isError={isError}

View File

@ -38,7 +38,7 @@ export default class VoteThresholdParam extends React.PureComponent<Props> {
style={style}
>
<Dropdown
className={isDisabled ? 'full' : 'medium'}
className='full'
defaultValue={defaultValue}
isDisabled={isDisabled}
isError={isError}

View File

@ -12,10 +12,10 @@ import { isUndefined } from '@polkadot/util';
import findComponent from './findComponent';
type Props = I18nProps & BaseProps & {
interface Props extends I18nProps, BaseProps {
isDisabled?: boolean;
overrides?: ComponentMap;
};
}
interface State {
Component: React.ComponentType<ComponentProps> | null;
@ -41,7 +41,7 @@ class ParamComponent extends React.PureComponent<Props, State> {
return null;
}
const { className, defaultValue, isDisabled, name, onChange, onEnter, style, type } = this.props;
const { className, defaultValue, isDisabled, isOptional, name, onChange, onEnter, style, type } = this.props;
return (
<Component
@ -49,6 +49,7 @@ class ParamComponent extends React.PureComponent<Props, State> {
defaultValue={defaultValue}
key={`${name}:${type}`}
isDisabled={isDisabled}
isOptional={isOptional}
label={
isUndefined(name)
? type.type

View File

@ -5,7 +5,7 @@
.ui--Params {
border-left: 0.25rem solid #f2f2f2;
label {
.ui--Param label {
text-transform: none !important;
font-family: monospace;
}
@ -13,12 +13,28 @@
.ui--row {
flex-wrap: wrap;
}
.ui--Param-overlay {
margin-bottom: -100%;
z-index: 1;
text-align: right;
}
}
.ui--Param-Address {
font-family: monospace;
}
.ui--Param-composite {
position: relative;
.ui--Param-overlay {
position: absolute;
top: 0.5rem;
right: 4.5rem;
}
}
.ui--Params-Content {
box-sizing: border-box;
padding: 0 0 0 1.75rem;

View File

@ -2,37 +2,35 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { TypeDef } from '@polkadot/types/types';
import { I18nProps } from '@polkadot/react-components/types';
import { ComponentMap, RawParam, RawParams, RawParamOnChange, RawParamOnChangeValue } from './types';
import { ComponentMap, ParamDef, RawParam, RawParams, RawParamOnChange, RawParamOnChangeValue } from './types';
import './Params.css';
import React from 'react';
import { Toggle } from '@polkadot/react-components';
import { classes } from '@polkadot/react-components/util';
import translate from '@polkadot/react-components/translate';
import Param from './Param';
import { createValue } from './values';
interface Param {
name?: string;
type: TypeDef;
}
interface Props extends I18nProps {
isDisabled?: boolean;
onChange?: (value: RawParams) => void;
onEnter?: () => void;
overrides?: ComponentMap;
params: Param[];
params: ParamDef[];
values?: RawParams | null;
}
interface State {
handlers?: RawParamOnChange[];
handlersChange?: RawParamOnChange[];
handlersOption?: ((value: boolean) => void)[];
onChangeOption: (at: number, value: boolean) => void;
onChangeParam: (at: number, next: RawParamOnChangeValue) => void;
params?: Param[];
optional?: boolean[];
params?: ParamDef[];
values?: RawParams;
}
@ -43,38 +41,48 @@ class Params extends React.PureComponent<Props, State> {
super(props);
this.state = {
onChangeOption: this.onChangeOption,
onChangeParam: this.onChangeParam
};
}
public static getDerivedStateFromProps (props: Props, { params, onChangeParam }: State): State | null {
const isSame = JSON.stringify(params) === JSON.stringify(props.params);
public static getDerivedStateFromProps ({ isDisabled, params, values }: Props, prevState: State): Pick<State, never> | null {
const isSame = JSON.stringify(prevState.params) === JSON.stringify(params);
if (props.isDisabled || isSame) {
if (isDisabled || isSame) {
return null;
}
const values = props.params.reduce(
const newValues = params.reduce(
(result: RawParams, param, index): RawParams => [
...result,
props.values && props.values[index]
? props.values[index]
values && values[index]
? values[index]
: createValue(param)
],
[]
);
const handlers = values.map(
const handlersChange = newValues.map(
(_, index): RawParamOnChange =>
(value: RawParamOnChangeValue): void =>
onChangeParam(index, value)
prevState.onChangeParam(index, value)
);
const optional = params.map(({ isOptional }): boolean => !isOptional);
const handlersOption = params.map(
(_, index): (value: boolean) => void =>
(value: boolean): void =>
prevState.onChangeOption(index, value)
);
return {
handlers,
onChangeParam,
params: props.params,
values
handlersChange,
handlersOption,
optional,
params,
values: newValues
};
}
@ -86,19 +94,18 @@ class Params extends React.PureComponent<Props, State> {
// This is needed in the case where the item changes, i.e. the values get
// initialised and we need to alert the parent that we have new values
public componentDidUpdate (_: Props, prevState: State): void {
const { onChange, isDisabled } = this.props;
const { isDisabled } = this.props;
const { values } = this.state;
if (!isDisabled && prevState.values !== values) {
onChange && onChange(values || []);
this.triggerUpdate();
}
}
public render (): React.ReactNode {
const { className, isDisabled, onEnter, overrides, params, style } = this.props;
const { handlers = [], values = this.props.values } = this.state;
const { className, params, style } = this.props;
if (!params || params.length === 0 || !values || values.length === 0) {
if (!params || params.length === 0) {
return null;
}
@ -108,23 +115,93 @@ class Params extends React.PureComponent<Props, State> {
style={style}
>
<div className='ui--Params-Content'>
{params.map(({ name, type }, index): React.ReactNode => (
<Param
defaultValue={values[index]}
isDisabled={isDisabled}
key={`${name}:${name}:${index}`}
name={name}
onChange={handlers[index]}
onEnter={onEnter}
overrides={overrides}
type={type}
/>
))}
{params.map(this.renderComponent)}
</div>
</div>
);
}
private renderComponent = ({ isOptional = false, name, type }: ParamDef, index: number): React.ReactNode => {
const { isDisabled, onEnter, overrides } = this.props;
const { handlersChange = [], handlersOption = [], optional = [], values = this.props.values } = this.state;
const key = `${name}:${type}:${index}`;
if (!values || values.length === 0) {
return null;
}
const isDisabledOptional = isOptional && !optional[index];
return (
<div
className='ui--Param-composite'
key={key}
>
<Param
defaultValue={
isDisabledOptional
? { isValid: true, value: undefined }
: values[index]
}
isDisabled={isDisabled || isDisabledOptional}
isOptional={isOptional}
key={`input:${key}`}
name={name}
onChange={handlersChange[index]}
onEnter={onEnter}
overrides={overrides}
type={type}
/>
{
isOptional
? (
<Toggle
className='ui--Param-overlay'
key={`option:${key}`}
label={
optional[index]
? 'include option'
: 'exclude option'
}
onChange={handlersOption[index]}
/>
)
: null
}
</div>
);
}
private extractValues (): RawParam[] {
const { optional, values } = this.state;
const { isDisabled } = this.props;
if (isDisabled || !optional || !values) {
return [];
}
return values
.map((value, index): RawParam | null =>
optional[index]
? value
: null
)
.filter((value): boolean => !!value) as RawParam[];
}
private onChangeOption = (at: number, value: boolean): void => {
this.setState(
(prevState: State): Pick<State, never> => ({
optional: (prevState.optional || []).map((prev, index): boolean =>
index !== at
? prev
: value
)
}),
this.triggerUpdate
);
}
private onChangeParam = (at: number, newValue: RawParamOnChangeValue): void => {
const { isDisabled } = this.props;
@ -139,10 +216,7 @@ class Params extends React.PureComponent<Props, State> {
values: (prevState.values || []).map((prev, index): RawParam =>
index !== at
? prev
: {
isValid,
value
}
: { isValid, value }
)
}),
this.triggerUpdate
@ -150,14 +224,14 @@ class Params extends React.PureComponent<Props, State> {
}
private triggerUpdate = (): void => {
const { values } = this.state;
const { optional, values } = this.state;
const { onChange, isDisabled } = this.props;
if (isDisabled || !values) {
if (isDisabled || !optional || !values) {
return;
}
onChange && onChange(values);
onChange && onChange(this.extractValues());
}
}

View File

@ -29,6 +29,7 @@ export type RawParams = RawParam[];
export interface BaseProps extends BareProps {
defaultValue: RawParam;
isOptional: boolean;
name?: string;
onChange?: RawParamOnChange;
onEnter?: RawParamOnEnter;
@ -46,3 +47,9 @@ export interface Props extends BaseProps {
export type Size = 'full' | 'large' | 'medium' | 'small';
export type ComponentMap = Record<string, React.ComponentType<Props>>;
export interface ParamDef {
isOptional?: boolean;
name?: string;
type: TypeDef;
}