Simplify app using setState (#23)

* Remove extra subjects from app-explorer

* Use bs58 addresses

* Base upgrades subject -> onChange

* app-storage & app-explorer works

* Remove useless placeholder

* Progres...

* Rename Submission -> Selection

* Baby steps... things display now...

* setState operational

* Explicit onChange

* Work around eslint issues (!!!)

* @flow fixes

* Small styling updates

* Small app content styling updates

* Simplify
This commit is contained in:
Jaco Greeff 2018-05-07 11:37:06 +02:00 committed by GitHub
parent 351af88ab2
commit 072c5d73f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
76 changed files with 1081 additions and 1277 deletions

View File

@ -3,7 +3,7 @@
declare type I18Next$Translate$Config = {
defaultValue: string,
replace?: {
[string]: string
[string]: any
}
};

View File

@ -1,35 +0,0 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { I18nProps } from '@polkadot/ui-react-app/types';
import React from 'react';
import BestNumber from '@polkadot/rx-react/BestNumber';
import BestHash from './BestHash';
import BlockHeaders from './BlockHeaders';
import translate from './translate';
type Props = I18nProps & {};
function App ({ className, style, t }: Props): React$Node {
return (
<div
className={['explorer--App', className].join(' ')}
style={style}
>
<BestNumber
className='explorer--BestNumber'
label={t('app.bestNumber', {
defaultValue: 'best #'
})}
/>
<BestHash />
<BlockHeaders />
</div>
);
}
export default translate(App);

View File

@ -6,21 +6,14 @@
import type { Header } from '@polkadot/primitives/header';
import headerHash from '@polkadot/primitives-codec/header/hash';
import Div from '@polkadot/rx-react/Div';
import withApiCall from '@polkadot/rx-react/with/apiCall';
import withApiDiv from '@polkadot/rx-react/with/apiDiv';
import u8aToHexShort from '@polkadot/util/u8a/toHexShort';
const apiMethod = {
method: 'newHead',
section: 'chain'
};
const props = {
className: 'explorer--BestHash',
format: (value?: Header): ?string =>
value
export default withApiDiv({ name: 'newHead', section: 'chain' })(
(value?: Header): ?string => {
return value
? u8aToHexShort(headerHash(value))
: value
};
export default withApiCall(apiMethod)(Div, props);
: value;
},
{ className: 'explorer--BestHash' }
);

View File

@ -6,15 +6,14 @@
import type { Header } from '@polkadot/primitives/header';
import React from 'react';
import Div from '@polkadot/rx-react/Div';
import withApiCall from '@polkadot/rx-react/with/apiCall';
import withApiDiv from '@polkadot/rx-react/with/apiDiv';
import BlockHeader from './BlockHeader';
let blockHeaders: Array<Header> = [];
const apiMethod = {
method: 'newHead',
name: 'newHead',
section: 'chain'
};
@ -36,15 +35,14 @@ const apiOptions = {
}
};
const props = {
className: 'explorer--BlockHeaders',
format: (value?: Array<Header> = []): React$Node =>
value.map((value) => (
export default withApiDiv(apiMethod, apiOptions)(
(value?: Array<Header> = []): Array<React$Node> => {
return value.map((value) => (
<BlockHeader
key={value.number.toString()}
value={value}
/>
))
};
export default withApiCall(apiMethod, apiOptions)(Div, props);
));
},
{ className: 'explorer--BlockHeaders' }
);

View File

@ -3,7 +3,7 @@
/* of the ISC license. See the LICENSE file for details. */
.explorer--App {
padding: 1em;
padding: 1em 2em;
}
.explorer--App .rx--updated {

View File

@ -3,8 +3,35 @@
// of the ISC license. See the LICENSE file for details.
// @flow
import type { I18nProps } from '@polkadot/ui-react-app/types';
import './index.css';
import App from './App';
import React from 'react';
import BestNumber from '@polkadot/rx-react/BestNumber';
export default App;
import BestHash from './BestHash';
import BlockHeaders from './BlockHeaders';
import translate from './translate';
type Props = I18nProps & {};
function App ({ className, style, t }: Props): React$Node {
return (
<div
className={['explorer--App', className].join(' ')}
style={style}
>
<BestNumber
className='explorer--BestNumber'
label={t('app.bestNumber', {
defaultValue: 'best #'
})}
/>
<BestHash />
<BlockHeaders />
</div>
);
}
export default translate(App);

View File

@ -8,31 +8,35 @@ import type { I18nProps } from '@polkadot/ui-react-app/types';
import React from 'react';
import Label from 'semantic-ui-react/dist/es/elements/Label';
import Balance from '@polkadot/rx-react/Balance';
import withObservableParams from '@polkadot/rx-react/with/observableParams';
import InputAddress from '@polkadot/ui-react-app/src/InputAddress';
import translate from './translate';
type Props = I18nProps & {
defaultValue?: Uint8Array,
isError?: boolean,
label: string,
subject: rxjs$BehaviorSubject<Uint8Array>
onChange: (publicKey: Uint8Array) => void
};
class Account extends React.PureComponent<Props> {
Balance: React$ComponentType<*>;
type State = {
publicKey?: Uint8Array
};
class Account extends React.PureComponent<Props, State> {
state: State;
constructor (props: Props) {
super(props);
this.Balance = withObservableParams(props.subject)(Balance, {
className: 'ui disabled dropdown selection'
});
this.state = {
publicKey: props.defaultValue
};
}
render (): React$Node {
const { className, label, subject, style, t } = this.props;
const Balance = this.Balance;
const { className, defaultValue, label, style, t } = this.props;
const { publicKey } = this.state;
return (
<div
@ -41,9 +45,10 @@ class Account extends React.PureComponent<Props> {
>
<div className='large'>
<InputAddress
defaultValue={defaultValue}
label={label}
onChange={this.onChange}
placeholder='0x...'
subject={subject}
/>
</div>
<div className='small'>
@ -52,11 +57,22 @@ class Account extends React.PureComponent<Props> {
defaultValue: 'with an available balance of'
})}
</Label>
<Balance />
<Balance
className='ui disabled dropdown selection'
params={publicKey}
/>
</div>
</div>
);
}
onChange = (publicKey: Uint8Array): void => {
const { onChange } = this.props;
this.setState({ publicKey }, () =>
onChange(publicKey)
);
};
}
export default translate(Account);

View File

@ -1,58 +0,0 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { I18nProps } from '@polkadot/ui-react-app/types';
import type { QueueTx } from './types';
import React from 'react';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import withObservable from '@polkadot/rx-react/with/observable';
import Signer from './Signer';
import Submission from './Submission';
import Status from './Status';
import translate from './translate';
type Props = I18nProps & {};
const CLOSE_STATUS = ['cancelled', 'error', 'sent'];
const queue: rxjs$BehaviorSubject<QueueTx> = new BehaviorSubject(({
isValid: false,
status: 'incomplete'
}: $Shape<QueueTx>));
const QueuedSigner = withObservable(queue)(Signer);
const QueuedStatus = withObservable(queue)(Status);
queue.subscribe(({ status }) => {
if (CLOSE_STATUS.includes(status)) {
setTimeout(() => {
const tx = queue.getValue();
if (CLOSE_STATUS.includes(tx.status)) {
queue.next({
...tx,
status: 'incomplete'
});
}
}, 5000);
}
});
function App ({ className, style }: Props): React$Node {
return (
<div
className={['extrinsics--App', className].join(' ')}
style={style}
>
<Submission subject={queue} />
<QueuedSigner subject={queue} />
<QueuedStatus />
</div>
);
}
export default translate(App);

View File

@ -0,0 +1,102 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { Extrinsic } from '@polkadot/extrinsics/types';
import type { BareProps } from '@polkadot/ui-react-app/types';
import type { RawParam } from '@polkadot/ui-react-app/Params/types';
import type { EncodedMessage } from './types';
import React from 'react';
import encode from '@polkadot/extrinsics-codec/encode/extrinsic';
import InputExtrinsic from '@polkadot/ui-react-app/src/InputExtrinsic';
import Params from '@polkadot/ui-react-app/src/Params';
import isUndefined from '@polkadot/util/is/undefined';
import paramComponents from './Params';
type Props = BareProps & {
defaultValue: Extrinsic,
isError?: boolean,
isPrivate?: boolean,
labelMethod?: string,
labelSection?: string,
onChange: (values: EncodedMessage) => void
};
type State = {
extrinsic: Extrinsic,
values: Array<RawParam>
};
export default class ExtrinsicDisplay extends React.PureComponent<Props, State> {
state: State;
constructor (props: Props) {
super(props);
this.state = ({
extrinsic: props.defaultValue
}: $Shape<State>);
}
render (): React$Node {
const { className, defaultValue, isError, isPrivate, labelMethod, labelSection, style } = this.props;
const { extrinsic } = this.state;
return (
<div
className={['extrinsics--Extrinsic', 'ui--form', className].join(' ')}
style={style}
>
<div className='full'>
<InputExtrinsic
defaultValue={defaultValue}
isError={isError}
isPrivate={isPrivate}
labelMethod={labelMethod}
labelSection={labelSection}
onChange={this.onChangeExtrinsic}
/>
</div>
<Params
extrinsic={extrinsic}
onChange={this.onChangeValues}
overrides={paramComponents}
/>
</div>
);
}
nextState (newState: $Shape<State>): void {
this.setState(newState, () => {
const { onChange } = this.props;
const { extrinsic, values } = this.state;
const params = Object.values(extrinsic.params || {});
const isValid = values.length === params.length &&
params.reduce((isValid, param, index) =>
isValid &&
!isUndefined(values[index]) &&
!isUndefined(values[index].value) &&
values[index].isValid, true);
const value = isValid && extrinsic.params
? encode(extrinsic, values.map((p) => p.value))
: new Uint8Array([]);
onChange({
extrinsic,
isValid,
value
});
});
}
onChangeExtrinsic = (extrinsic: Extrinsic): void => {
this.nextState({ extrinsic });
};
onChangeValues = (values: Array<RawParam>): void => {
this.nextState({ values });
}
}

View File

@ -1,103 +0,0 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { BareProps } from '@polkadot/ui-react-app/types';
import type { EncodedParams } from '../types';
import type { Subjects } from './types';
import React from 'react';
import encodeExtrinsic from '@polkadot/extrinsics-codec/encode/extrinsic';
import withObservable from '@polkadot/rx-react/with/observable';
import InputExtrinsic from '@polkadot/ui-react-app/src/InputExtrinsic';
import Params from '@polkadot/ui-react-app/src/Params';
import isUndefined from '@polkadot/util/is/undefined';
import paramComponents from '../Params';
import createSubjects from './subjects';
type Props = BareProps & {
isError?: boolean,
isPrivate?: boolean,
labelMethod?: string,
labelSection?: string,
subject: rxjs$BehaviorSubject<EncodedParams>
};
export default class Extrinsic extends React.PureComponent<Props> {
subjects: Subjects;
subscriptions: Array<rxjs$ISubscription>;
Params: React$ComponentType<*>;
constructor (props: Props) {
super(props);
this.subjects = createSubjects(props.subject);
this.Params = withObservable(this.subjects.method)(Params);
this.subscriptions = [];
}
componentWillMount () {
this.subscriptions = [
this.subjects.params.subscribe(this.onChange),
this.subjects.method.subscribe(this.onChange)
];
}
componentWillUnmount () {
this.subscriptions.forEach((s) =>
s.unsubscribe()
);
}
onChange = (): void => {
const extrinsic = this.subjects.method.getValue();
const values = this.subjects.params.getValue();
const isValid = !!extrinsic.params &&
Object
.values(extrinsic.params)
.reduce((isValid, param, index) => {
return isValid &&
!isUndefined(values[index]) &&
!isUndefined(values[index].value) &&
values[index].isValid;
}, !!values);
const raw = values.map((param) => param && param.value);
const data = extrinsic.params
? encodeExtrinsic(extrinsic, raw)
: new Uint8Array([]);
this.props.subject.next({
data,
extrinsic,
isValid
});
};
render (): React$Node {
const { className, isError, isPrivate, labelMethod, labelSection, style } = this.props;
const { Params, subjects } = this;
return (
<div
className={['extrinsics--Extrinsic', 'ui--form', className].join(' ')}
style={style}
>
<div className='full'>
<InputExtrinsic
isError={isError}
isPrivate={isPrivate}
labelMethod={labelMethod}
labelSection={labelSection}
subject={subjects.method}
/>
</div>
<Params
overrides={paramComponents}
subject={subjects.params}
/>
</div>
);
}
}

View File

@ -1,19 +0,0 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { Extrinsic } from '@polkadot/extrinsics/types';
import type { EncodedParams } from '../types';
import type { Subjects } from './types';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
export default function subjects (subject: rxjs$BehaviorSubject<EncodedParams>): Subjects {
const extrinsic = subject.getValue().extrinsic || ({}: $Shape<Extrinsic>);
return {
method: new BehaviorSubject(extrinsic),
params: new BehaviorSubject([])
};
}

View File

@ -1,12 +0,0 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { Extrinsic } from '@polkadot/extrinsics/types';
import type { RawParam } from '@polkadot/ui-react-app/Params/types';
export type Subjects = {
method: rxjs$BehaviorSubject<Extrinsic>,
params: rxjs$BehaviorSubject<Array<RawParam>>
};

View File

@ -3,49 +3,31 @@
// of the ISC license. See the LICENSE file for details.
// @flow
import type { Props } from '@polkadot/ui-react-app/Params/types';
import type { Props as BaseProps } from '@polkadot/ui-react-app/Params/types';
import React from 'react';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import BaseAccount from '../Account';
export default class Account extends React.PureComponent<Props> {
account: rxjs$BehaviorSubject<Uint8Array>;
subscriptions: Array<rxjs$ISubscription>
type Props = BaseProps & {
defaultValue?: Uint8Array
};
constructor (props: Props) {
super(props);
export default function Account ({ className, defaultValue, index, isError, onChange, label, style }: Props): React$Node {
const _onChange = (value?: Uint8Array): void =>
onChange(index, {
isValid: !!value && value.length === 32,
value
});
this.account = new BehaviorSubject(new Uint8Array([]));
}
componentWillMount () {
this.subscriptions = [
this.account.subscribe((value: Uint8Array) =>
this.props.subject.next({
isValid: !!value && value.length === 32,
value
})
)
];
}
componentWillUnmount () {
this.subscriptions.forEach((s) =>
s.unsubscribe()
);
}
render (): React$Node {
const { isError, label } = this.props;
return (
<BaseAccount
isError={isError}
label={label}
subject={this.account}
/>
);
}
return (
<BaseAccount
className={className}
defaultValue={defaultValue}
isError={isError}
label={label}
onChange={_onChange}
style={style}
/>
);
}

View File

@ -5,66 +5,47 @@
import type { Props as BaseProps } from '@polkadot/ui-react-app/Params/types';
import type { I18nProps } from '@polkadot/ui-react-app/types';
import type { EncodedParams } from '../types';
import type { EncodedMessage } from '../types';
import React from 'react';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import extrinsics from '@polkadot/extrinsics-substrate';
import Extrinsic from '../Extrinsic';
import translate from '../translate';
type Props = BaseProps & I18nProps;
class Proposal extends React.PureComponent<Props> {
extrinsic: rxjs$BehaviorSubject<EncodedParams>;
subscriptions: Array<rxjs$ISubscription>
const defaultValue = extrinsics.consensus.methods.private.setCode;
constructor (props: Props) {
super(props);
function Proposal ({ className, index, isError, onChange, label, style, t }: Props): React$Node {
const _onChange = ({ isValid, value }: EncodedMessage): void =>
onChange(index, {
isValid,
value
});
this.extrinsic = new BehaviorSubject(({ isValid: false }: $Shape<EncodedParams>));
}
componentWillMount () {
this.subscriptions = [
this.extrinsic.subscribe(({ data, isValid }: EncodedParams) =>
this.props.subject.next({
isValid,
value: data
})
)
];
}
componentWillUnmount () {
this.subscriptions.forEach((s) =>
s.unsubscribe()
);
}
render (): React$Node {
const { isError, label, t } = this.props;
return (
<Extrinsic
isError={isError}
isPrivate
labelMethod={t('proposal.method', {
defaultValue: '{{label}} (extrinsic)',
replace: {
label
}
})}
labelSection={t('proposal.method', {
defaultValue: '{{label}} (section)',
replace: {
label
}
})}
subject={this.extrinsic}
/>
);
}
return (
<Extrinsic
className={className}
defaultValue={defaultValue}
isError={isError}
isPrivate
labelMethod={t('proposal.method', {
defaultValue: '{{label}} (extrinsic)',
replace: {
label
}
})}
labelSection={t('proposal.method', {
defaultValue: '{{label}} (section)',
replace: {
label
}
})}
onChange={_onChange}
style={style}
/>
);
}
export default translate(Proposal);

View File

@ -0,0 +1,35 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type BN from 'bn.js';
import type { BareProps } from '@polkadot/ui-react-app/types';
import React from 'react';
import Label from 'semantic-ui-react/dist/es/elements/Label';
import RxNonce from '@polkadot/rx-react/Nonce';
type Props = BareProps & {
label: string,
onChange: (value: BN) => void,
value?: Uint8Array
};
export default function Nonce ({ className, label, style, onChange, value }: Props): React$Node {
return (
<div
className={['ui--form', className].join(' ')}
style={style}
>
<div className='small'>
<Label>{label}</Label>
<RxNonce
className='ui disabled dropdown selection'
onChange={onChange}
params={value}
/>
</div>
</div>
);
}

View File

@ -2,14 +2,14 @@
/* This software may be modified and distributed under the terms
/* of the ISC license. See the LICENSE file for details. */
.extrinsics--Submission {}
.extrinsics--Selection {}
.extrinsics--Submission-ButtonRow {
.extrinsics--Selection-ButtonRow {
margin: 1em 0;
text-align: right;
}
.extrinsics--Submission-ButtonRow .button {
margin-left: 0.5rem;
.extrinsics--Selection-ButtonRow .button {
margin: 0 0 0 0.5rem;
text-align: left;
}

View File

@ -0,0 +1,140 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type BN from 'bn.js';
import type { I18nProps } from '@polkadot/ui-react-app/types';
import type { EncodedMessage, QueueTx } from '../types';
import './Selection.css';
import React from 'react';
import Button from 'semantic-ui-react/dist/es/elements/Button';
import extrinsics from '@polkadot/extrinsics-substrate';
import Account from '../Account';
import Extrinsic from '../Extrinsic';
import translate from '../translate';
import Nonce from './Nonce';
type Props = I18nProps & {
onQueue: (value: QueueTx) => void
};
type State = {
isValid: boolean,
encoded: EncodedMessage,
nonce: BN,
publicKey: Uint8Array
};
const defaultExtrinsic = extrinsics.staking.methods.public.transfer;
let id = 0;
class Selection extends React.PureComponent<Props, State> {
state: State;
constructor (props: Props) {
super(props);
this.state = ({
isValid: false
}: $Shape<State>);
}
render () {
const { className, style, t } = this.props;
const { publicKey, isValid } = this.state;
return (
<div
className={['extrinsics--Selection', className].join(' ')}
style={style}
>
<Account
label={t('display.sender', {
defaultValue: 'using the selected account'
})}
onChange={this.onChangeSender}
/>
<Extrinsic
defaultValue={defaultExtrinsic}
labelMethod={t('display.method', {
defaultValue: 'submit the following extrinsic'
})}
onChange={this.onChangeMessage}
/>
<Nonce
label={t('display.nonce', {
defaultValue: 'with an index'
})}
onChange={this.onChangeNonce}
value={publicKey}
/>
<div className='extrinsics--Selection-ButtonRow'>
<Button
disabled={!isValid}
onClick={this.onQueue}
primary
>
{t('submit.label', {
defaultValue: 'Submit Extrinsic'
})}
</Button>
</div>
</div>
);
}
nextState (newState: $Shape<State>): void {
this.setState(
(prevState: State): State => {
const { encoded = prevState.encoded, nonce = prevState.nonce, publicKey = prevState.publicKey } = newState;
const isValid = !!(
publicKey &&
publicKey.length &&
encoded &&
encoded.isValid
);
return {
encoded,
isValid,
nonce,
publicKey
};
}
);
}
onChangeMessage = (encoded: EncodedMessage): void => {
this.nextState({ encoded });
}
onChangeNonce = (nonce: BN): void => {
this.nextState({ nonce });
}
onChangeSender = (publicKey: Uint8Array): void => {
this.nextState({ publicKey });
}
onQueue = (): void => {
const { onQueue } = this.props;
const { encoded: { extrinsic, isValid, value }, nonce, publicKey } = this.state;
onQueue({
extrinsic,
id: ++id,
isValid,
nonce,
publicKey,
status: 'queued',
value
});
}
}
export default translate(Selection);

View File

@ -19,7 +19,7 @@ type Props = I18nProps & {
value: QueueTx
};
function Decoded ({ className, style, t, value: { data, extrinsic, index, publicKey } }: Props): React$Node {
function Decoded ({ className, style, t, value: { extrinsic: { name, section }, nonce, publicKey, value } }: Props): React$Node {
const from = addressEncode(publicKey);
return (
@ -35,7 +35,7 @@ function Decoded ({ className, style, t, value: { data, extrinsic, index, public
<div className='expanded'>
<p>
<Trans i18nkey='decoded.short'>
You are about to sign a message from <span className='code'>{from}</span> calling <span className='code'>{extrinsic.section}_{extrinsic.name}</span> with an index of <span className='code'>{index.toString()}</span>
You are about to sign a message from <span className='code'>{from}</span> calling <span className='code'>{section}_{name}</span> with an index of <span className='code'>{nonce.toString()}</span>
</Trans>
</p>
<p>
@ -43,7 +43,9 @@ function Decoded ({ className, style, t, value: { data, extrinsic, index, public
defaultValue: 'The encoded parameters contains the data'
})}
</p>
<p className='code'>{u8aToHexShort(data, 512)}</p>
<p className='code'>
{u8aToHexShort(value, 512)}
</p>
</div>
</div>
</Modal.Content>

View File

@ -19,23 +19,23 @@ import Decoded from './Decoded';
import submitExtrinsic from './submit';
type Props = I18nProps & ApiProps & {
subject: rxjs$BehaviorSubject<QueueTx>,
value: QueueTx
onSetStatus: (id: number, status: string) => void,
queue: Array<QueueTx>
};
function Signer ({ api, className, subject, style, t, value }: Props): React$Node {
if (value.status !== 'queued') {
function Signer ({ api, className, onSetStatus, queue, style, t }: Props): React$Node {
const first = queue.find(({ status }) => status === 'queued');
if (!first) {
return null;
}
const onClose = (): void => {
subject.next({
...value,
status: 'cancelled'
});
};
const onSign = (): void => {
submitExtrinsic(api, value, subject);
const onClose = (): void =>
onSetStatus(first.id, 'cancelled');
const onSign = async (): Promise<void> => {
const status = await submitExtrinsic(api, first);
onSetStatus(first.id, status);
};
return (
@ -50,7 +50,7 @@ function Signer ({ api, className, subject, style, t, value }: Props): React$Nod
defaultValue: 'Sign and submit'
})}
</Modal.Header>
<Decoded value={value} />
<Decoded value={first} />
<Modal.Actions>
<Button onClick={onClose}>
{t('signer.cancel', {

View File

@ -11,9 +11,9 @@ import keyring from '@polkadot/ui-react-app/src/keyring';
import u8aConcat from '@polkadot/util/u8a/concat';
import u8aToHex from '@polkadot/util/u8a/toHex';
export default function submit (api: RxApiInterface, tx: QueueTx, subject: rxjs$BehaviorSubject<QueueTx>): Promise<void> {
const message = encodeCall(tx.publicKey, tx.index, tx.data);
const signature = keyring.getPair(tx.publicKey).sign(message);
export default function submit (api: RxApiInterface, { nonce, publicKey, value }: QueueTx): Promise<string> {
const message = encodeCall(publicKey, nonce, value);
const signature = keyring.getPair(publicKey).sign(message);
console.log('submitExtrinsic: message =', u8aToHex(message));
console.log('submitExtrinsic: signature =', u8aToHex(signature));
@ -24,17 +24,11 @@ export default function submit (api: RxApiInterface, tx: QueueTx, subject: rxjs$
.then((result) => {
console.log('submitExtrinsic: result=', result);
subject.next({
...tx,
status: 'sent'
});
return 'sent';
})
.catch((error) => {
console.error('submitExtrinsic: error=', error);
subject.next({
...tx,
status: 'error'
});
return 'error';
});
}

View File

@ -3,35 +3,42 @@
// of the ISC license. See the LICENSE file for details.
// @flow
import type { I18nProps } from '@polkadot/ui-react-app/types';
import type { BareProps } from '@polkadot/ui-react-app/types';
import type { QueueTx } from './types';
import React from 'react';
import translate from './translate';
type Props = I18nProps & {
value: QueueTx
type Props = BareProps & {
queue: Array<QueueTx>
}
function Status ({ className, style, value: { extrinsic, isValid, status } }: Props): React$Node {
if (!isValid || ['incomplete'].includes(status)) {
export default function Status ({ className, queue, style }: Props): React$Node {
const available = queue.filter(({ isValid, status }) =>
!['completed', 'incomplete'].includes(status)
);
if (!available.length) {
return null;
}
return (
<div
className={['extrinsics--Status', status, className].join(' ')}
className={['extrinsics--Status', className].join(' ')}
style={style}
>
<div className='header'>
{extrinsic.section}_{extrinsic.name}
</div>
<div className='status'>
{status}
</div>
{available.map(({ extrinsic: { name, section }, id, status }) =>
<div
className={['extrinsics--Status-Item', status].join(' ')}
key={id}
>
<div className='header'>
{section}_{name}
</div>
<div className='status'>
{status}
</div>
</div>
)}
</div>
);
}
export default translate(Status);

View File

@ -1,49 +0,0 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type BN from 'bn.js';
import type { BareProps } from '@polkadot/ui-react-app/types';
import React from 'react';
import Label from 'semantic-ui-react/dist/es/elements/Label';
import RxNonce from '@polkadot/rx-react/Nonce';
import withObservableParams from '@polkadot/rx-react/with/observableParams';
type Props = BareProps & {
label: string,
subject: rxjs$BehaviorSubject<BN>,
value: rxjs$BehaviorSubject<Uint8Array>
};
export default class Nonce extends React.PureComponent<Props> {
Nonce: React$ComponentType<*>;
constructor (props: Props) {
super(props);
// NOTE we basically get away with not checking updates since the parent is only created once
this.Nonce = withObservableParams(props.value)(RxNonce);
}
render (): React$Node {
const { className, label, style, subject } = this.props;
const Nonce = this.Nonce;
return (
<div
className={['ui--form', className].join(' ')}
style={style}
>
<div className='small'>
<Label>{label}</Label>
<Nonce
className='ui disabled dropdown selection'
subject={subject}
/>
</div>
</div>
);
}
}

View File

@ -1,40 +0,0 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { I18nProps } from '@polkadot/ui-react-app/types';
import type { QueueTx } from '../types';
import React from 'react';
import Button from 'semantic-ui-react/dist/es/elements/Button';
import translate from '../translate';
type Props = I18nProps & {
subject: rxjs$BehaviorSubject<QueueTx>,
value: QueueTx;
};
function Submit ({ className, subject, style, t, value }: Props): React$Node {
const onSubmit = (): void => {
subject.next({
...value,
status: 'queued'
});
};
return (
<Button
disabled={!value.isValid}
onClick={onSubmit}
primary
>
{t('submit.label', {
defaultValue: 'Submit Extrinsic'
})}
</Button>
);
}
export default translate(Submit);

View File

@ -1,95 +0,0 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { I18nProps } from '@polkadot/ui-react-app/types';
import type { QueueTx } from '../types';
import type { Subjects } from './types';
import './Submission.css';
import React from 'react';
import withObservable from '@polkadot/rx-react/with/observable';
import Account from '../Account';
import Extrinsic from '../Extrinsic';
import translate from '../translate';
import Nonce from './Nonce';
import Submit from './Submit';
import createSubjects from './subjects';
type Props = I18nProps & {
subject: rxjs$BehaviorSubject<QueueTx>
};
class Submission extends React.PureComponent<Props> {
Submit: React$ComponentType<*>;
subjects: Subjects;
constructor (props: Props) {
super(props);
this.subjects = createSubjects();
this.Submit = withObservable(this.subjects.call)(Submit);
}
componentWillMount () {
this.subjects.index.subscribe(this.onChange);
this.subjects.method.subscribe(this.onChange);
this.subjects.sender.subscribe(this.onChange);
}
onChange = (): void => {
const index = this.subjects.index.getValue();
const message = this.subjects.method.getValue();
const publicKey = this.subjects.sender.getValue();
const isValid = !!(publicKey && publicKey.length && message && message.isValid);
this.subjects.call.next({
data: message.data,
extrinsic: message.extrinsic,
isValid,
index,
publicKey,
status: 'incomplete'
});
};
render (): React$Node {
const { className, subject, style, t } = this.props;
const { Submit, subjects } = this;
return (
<div
className={['extrinsics--Submission', className].join(' ')}
style={style}
>
<Account
label={t('display.sender', {
defaultValue: 'using the selected account'
})}
subject={subjects.sender}
/>
<Extrinsic
labelMethod={t('display.method', {
defaultValue: 'submit the following extrinsic'
})}
subject={subjects.method}
/>
<Nonce
label={t('display.nonce', {
defaultValue: 'with an index'
})}
subject={subjects.index}
value={subjects.sender}
/>
<div className='extrinsics--Submission-ButtonRow'>
<Submit subject={subject} />
</div>
</div>
);
}
}
export default translate(Submission);

View File

@ -1,25 +0,0 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { EncodedParams, QueueTx } from '../types';
import type { Subjects } from './types';
import BN from 'bn.js';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import map from '@polkadot/extrinsics-substrate';
export default function createSubjects (): Subjects {
return {
index: new BehaviorSubject(new BN(0)),
call: new BehaviorSubject(({
isValid: false
}: $Shape<QueueTx>)),
method: new BehaviorSubject(({
extrinsic: map.staking.methods.public.transfer,
isValid: false
}: $Shape<EncodedParams>)),
sender: new BehaviorSubject(new Uint8Array([]))
};
}

View File

@ -1,14 +0,0 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type BN from 'bn.js';
import type { EncodedParams, QueueTx } from '../types';
export type Subjects = {
call: rxjs$BehaviorSubject<QueueTx>,
index: rxjs$BehaviorSubject<BN>,
method: rxjs$BehaviorSubject<EncodedParams>,
sender: rxjs$BehaviorSubject<Uint8Array>
};

View File

@ -3,25 +3,29 @@
/* of the ISC license. See the LICENSE file for details. */
.extrinsics--App {
padding: 1em;
padding: 1em 2em;
}
.extrinsics--Status {
background: #bcdaf0;
bottom: 0;
display: inline-block;
padding: 0.5rem 1rem;
position: fixed;
bottom: 0.125rem;
right: 0.125rem;
text-align: center;
z-index: 1001;
}
.extrinsics--Status .status {
.extrinsics--Status-Item {
background: #bcdaf0;
margin-bottom: 0.125rem;
padding: 0.5rem 1rem;
text-align: center;
}
.extrinsics--Status-Item .status {
font-weight: 700;
}
.extrinsics--Status.error {
.extrinsics--Status-Item.error {
background: red;
color: white;
}

View File

@ -3,8 +3,76 @@
// of the ISC license. See the LICENSE file for details.
// @flow
import type { BareProps } from '@polkadot/ui-react-app/types';
import type { QueueTx, QueueTx$Status } from './types';
import './index.css';
import App from './App';
import React from 'react';
export default App;
import Selection from './Selection';
import Signer from './Signer';
import Status from './Status';
type Props = BareProps & {};
type State = {
queue: Array<QueueTx>
};
export default class App extends React.PureComponent<Props, State> {
state: State;
constructor (props: Props) {
super(props);
this.state = {
queue: []
};
}
render (): React$Node {
const { className, style } = this.props;
const { queue } = this.state;
return (
<div
className={['extrinsics--App', className].join(' ')}
style={style}
>
<Selection onQueue={this.onQueue} />
<Signer
onSetStatus={this.setStatus}
queue={queue}
/>
<Status queue={queue} />
</div>
);
}
setStatus = (id: number, status: QueueTx$Status): void => {
this.setState(
(prevState: State): State => ({
queue: prevState.queue.map((item) =>
item.id === id
? { ...item, status }
: item
)
})
);
if (!['cancelled', 'error', 'sent'].includes(status)) {
return;
}
setTimeout(() => this.setStatus(id, 'completed'), 5000);
}
onQueue = (value: QueueTx): void => {
this.setState(
(prevState: State): State => ({
queue: prevState.queue.concat([value])
})
);
}
}

View File

@ -6,16 +6,17 @@
import type BN from 'bn.js';
import type { Extrinsic } from '@polkadot/extrinsics/types';
export type EncodedParams = {
data: Uint8Array,
export type EncodedMessage = {
extrinsic: Extrinsic,
isValid: boolean
isValid: boolean,
value: Uint8Array
};
export type QueueTx$Status = 'incomplete' | 'cancelled' | 'error' | 'queued' | 'sent';
export type QueueTx$Status = 'cancelled' | 'completed' | 'error' | 'incomplete' | 'queued' | 'sent';
export type QueueTx = EncodedParams & {
index: BN,
export type QueueTx = EncodedMessage & {
id: number,
nonce: BN,
publicKey: Uint8Array,
status: QueueTx$Status
};

View File

@ -1,28 +0,0 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { I18nProps } from '@polkadot/ui-react-app/types';
import React from 'react';
import Queries from './Queries';
import Selection from './Selection';
import translate from './translate';
type Props = I18nProps & {};
function App ({ className, style, t }: Props): React$Node {
return (
<div
className={['storage--App', className].join(' ')}
style={style}
>
<Selection />
<Queries />
</div>
);
}
export default translate(App);

View File

@ -4,20 +4,19 @@
// @flow
import type { I18nProps } from '@polkadot/ui-react-app/types';
import type { StorageQuery } from '../types';
import type { StorageQuery } from './types';
import React from 'react';
import withObservable from '@polkadot/rx-react/with/observable';
import subjects from '../subjects';
import translate from '../translate';
import translate from './translate';
import Query from './Query';
type Props = I18nProps & {
onRemove: (id: number) => void,
value?: Array<StorageQuery>
};
function Queries ({ className, style, value }: Props): React$Node {
function Queries ({ className, onRemove, style, value }: Props): React$Node {
if (!value || !value.length) {
return null;
}
@ -27,16 +26,15 @@ function Queries ({ className, style, value }: Props): React$Node {
className={['storage--Queries', className].join(' ')}
style={style}
>
{value.map((query) => (
{value.map((query) =>
<Query
key={query.id}
onRemove={onRemove}
value={query}
/>
))}
)}
</div>
);
}
export default translate(
withObservable(subjects.queries)(Queries)
);
export default translate(Queries);

View File

@ -1,46 +0,0 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { I18nProps } from '@polkadot/ui-react-app/types';
import React from 'react';
import Button from 'semantic-ui-react/dist/es/elements/Button';
import subjects from '../subjects';
import translate from '../translate';
import cache from './cache';
type Props = I18nProps & {
value: number;
};
function Remove ({ className, style, value }: Props): React$Node {
const onRemove = (): void => {
const current = subjects.queries.getValue();
delete cache[value];
subjects.queries.next(
current
.filter(({ id }) => id !== value)
.reduce((next, item) => {
next.push(item);
return next;
}, [])
);
};
return (
<Button
disabled={!value}
icon='close'
negative
onClick={onRemove}
/>
);
}
export default translate(Remove);

View File

@ -1,8 +0,0 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
const cache = {};
export default cache;

View File

@ -1,33 +0,0 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type BN from 'bn.js';
import type { StorageDef$Key } from '@polkadot/storage/types';
import u8aToHexShort from '@polkadot/util/u8a/toHexShort';
import isU8a from '@polkadot/util/is/u8a';
type Formatter = (value: boolean | BN | Date | Uint8Array) => string;
export default function transform ({ type }: StorageDef$Key): Formatter {
return (value: boolean | BN | Date | Uint8Array): string => {
if (typeof value === typeof true) {
return value
? 'Yes'
: 'No';
}
if (!value) {
return 'unknown';
}
if (isU8a(value)) {
// $FlowFixMe type has been determined
return u8aToHexShort(value, 256);
}
return value.toString();
};
}

View File

@ -0,0 +1,37 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type BN from 'bn.js';
import type { StorageDef$Key } from '@polkadot/storage/types';
import withStorageDiv from '@polkadot/rx-react/with/storageDiv';
import u8aToHexShort from '@polkadot/util/u8a/toHexShort';
import isU8a from '@polkadot/util/is/u8a';
export default function cached (key: StorageDef$Key, params?: Array<*> = []): React$ComponentType<*> {
return withStorageDiv(key, { params })(
(value?: boolean | BN | Date | Uint8Array): string => {
if (typeof value === typeof true) {
// flowlint-next-line sketchy-null-bool:off
return value
? 'Yes'
: 'No';
}
// flowlint-next-line sketchy-null-bool:off
if (!value) {
return 'unknown';
}
if (isU8a(value)) {
// $FlowFixMe type has been determined
return u8aToHexShort(value, 256);
}
return value.toString();
},
{ className: 'ui disabled dropdown selection' }
);
}

View File

@ -7,32 +7,34 @@ import type { I18nProps } from '@polkadot/ui-react-app/types';
import type { StorageQuery } from '../types';
import React from 'react';
import Button from 'semantic-ui-react/dist/es/elements/Button';
import Label from 'semantic-ui-react/dist/es/elements/Label';
import Div from '@polkadot/rx-react/Div';
import withStorage from '@polkadot/rx-react/with/storage';
import translate from '../translate';
import Remove from './Remove';
import cache from './cache';
import createTransform from './transform';
import createCached from './cached';
type Props = I18nProps & {
onRemove: (id: number) => void,
value: StorageQuery
};
function Query ({ className, style, value: { id, key, params } }: Props): React$Node {
const cache = [];
function Query ({ className, onRemove, style, value: { id, key, params } }: Props): React$Node {
const CachedQuery = (() => {
if (!cache[id]) {
const transform = createTransform(key);
cache[id] = withStorage(key, { params, transform })(
Div, { className: 'ui disabled dropdown selection' }
);
cache[id] = createCached(key, params);
}
return cache[id];
})();
const _onRemove = (): void => {
delete cache[id];
onRemove(id);
};
const inputs = Object
.keys(key.params || {})
// $FlowFixMe key.params exists
@ -52,7 +54,11 @@ function Query ({ className, style, value: { id, key, params } }: Props): React$
</div>
<div className='storage--actionrow-button'>
<Label>&nbsp;</Label>
<Remove value={id} />
<Button
icon='close'
negative
onClick={_onRemove}
/>
</div>
</div>
);

View File

@ -0,0 +1,85 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { StorageDef$Key } from '@polkadot/storage/types';
import type { I18nProps } from '@polkadot/ui-react-app/types';
import type { StorageQuery } from './types';
import React from 'react';
import Button from 'semantic-ui-react/dist/es/elements/Button';
import Label from 'semantic-ui-react/dist/es/elements/Label';
import storage from '@polkadot/storage-substrate/keys';
import InputStorage from '@polkadot/ui-react-app/src/InputStorage';
import translate from './translate';
type Props = I18nProps & {
onAdd: (query: StorageQuery) => void
};
type State = {
key: StorageDef$Key
};
const defaultValue = storage.timestamp.keys.current;
let id = -1;
class Selection extends React.PureComponent<Props, State> {
constructor (props: Props) {
super(props);
this.state = {
key: defaultValue
};
}
render (): React$Node {
const { className, style, t } = this.props;
const { key } = this.state;
return (
<div
className={['storage--Selection', 'storage--actionrow', className].join(' ')}
style={style}
>
<div className='storage--actionrow-value'>
<InputStorage
defaultValue={defaultValue}
labelSection={t('selection.section', {
defaultValue: 'query storage area'
})}
onChange={this.onChangeStorage}
/>
</div>
<div className='storage--actionrow-button'>
<Label>&nbsp;</Label>
<Button
disabled={!key}
icon='plus'
onClick={this.onAdd}
primary
/>
</div>
</div>
);
}
onAdd = (): void => {
const { onAdd } = this.props;
const { key } = this.state;
onAdd({
id: ++id,
key,
params: []
});
}
onChangeStorage = (key: StorageDef$Key): void => {
this.setState({ key });
}
}
export default translate(Selection);

View File

@ -1,56 +0,0 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { StorageDef$Key } from '@polkadot/storage/types';
import type { I18nProps } from '@polkadot/ui-react-app/types';
import React from 'react';
import Button from 'semantic-ui-react/dist/es/elements/Button';
import withObservable from '@polkadot/rx-react/with/observable';
import subjects from '../subjects';
import translate from '../translate';
type Props = I18nProps & {
value: StorageDef$Key;
};
let id = 0;
function Add ({ className, style, value }: Props): React$Node {
const onAdd = (): void => {
if (!value) {
return;
}
const current = subjects.queries.getValue();
const next = [{
id: ++id,
key: value,
params: subjects.params.getValue().map((s) => s.getValue())
}];
subjects.queries.next(
current.reduce((next, item) => {
next.push(item);
return next;
}, next)
);
};
return (
<Button
disabled={!value}
icon='plus'
onClick={onAdd}
primary
/>
);
}
export default translate(
withObservable(subjects.next)(Add)
);

View File

@ -1,44 +0,0 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { I18nProps } from '@polkadot/ui-react-app/types';
import React from 'react';
import Label from 'semantic-ui-react/dist/es/elements/Label';
import InputStorage from '@polkadot/ui-react-app/src/InputStorage';
import subjects from '../subjects';
import translate from '../translate';
import Add from './Add';
type Props = I18nProps & {};
function Selection ({ className, style, t }: Props): React$Node {
return (
<div
className={['storage--Selection', 'storage--actionrow', className].join(' ')}
style={style}
>
<div className='storage--actionrow-value'>
<div className='ui--form'>
<div className='full'>
<InputStorage
labelSection={t('selection.section', {
defaultValue: 'query storage area'
})}
subject={subjects.next}
/>
</div>
</div>
</div>
<div className='storage--actionrow-button'>
<Label>&nbsp;</Label>
<Add />
</div>
</div>
);
}
export default translate(Selection);

View File

@ -3,7 +3,7 @@
/* of the ISC license. See the LICENSE file for details. */
.storage--App {
padding: 1em;
padding: 1em 2em;
}
.storage--Queries {

View File

@ -3,8 +3,67 @@
// of the ISC license. See the LICENSE file for details.
// @flow
import type { I18nProps } from '@polkadot/ui-react-app/types';
import type { StorageQuery } from './types';
import './index.css';
import App from './App';
import React from 'react';
export default App;
import Queries from './Queries';
import Selection from './Selection';
import translate from './translate';
type Props = I18nProps & {};
type State = {
queue: Array<StorageQuery>
}
class App extends React.PureComponent<Props, State> {
state: State;
constructor (props: Props) {
super(props);
this.state = {
queue: []
};
}
render (): React$Node {
const { className, style } = this.props;
const { queue } = this.state;
return (
<div
className={['storage--App', className].join(' ')}
style={style}
>
<Selection onAdd={this.onAdd} />
<Queries
onRemove={this.onRemove}
value={queue}
/>
</div>
);
}
onAdd = (query: StorageQuery): void => {
this.setState(
(prevState: State): State => ({
queue: [query].concat(prevState.queue)
})
);
}
onRemove = (id: number): void => {
this.setState(
(prevState: State): State => ({
queue: prevState.queue.filter((item) => item.id !== id)
})
);
}
}
export default translate(App);

View File

@ -1,29 +0,0 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { StorageDef$Key } from '@polkadot/storage/types';
import type { StorageQuery } from './types';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import storage from '@polkadot/storage-substrate/keys';
type ValueSubject = rxjs$BehaviorSubject<*>;
type ValueSubjects = Array<ValueSubject>;
type Subjects = {
next: rxjs$BehaviorSubject<StorageDef$Key>,
params: rxjs$BehaviorSubject<ValueSubjects>,
queries: rxjs$BehaviorSubject<Array<StorageQuery>>
};
function createSubjects (): Subjects {
return {
next: new BehaviorSubject(storage.timestamp.keys.current),
params: new BehaviorSubject([]),
queries: new BehaviorSubject([])
};
}
export default createSubjects();

View File

@ -37,7 +37,7 @@ function Connecting ({ className, style, value, t }: Props): React$Node {
}
export default translate(
withApiCall({ method: 'isConnected' })(
withApiCall({ name: 'isConnected' })(
Connecting
)
);

View File

@ -3,6 +3,7 @@
/* of the ISC license. See the LICENSE file for details. */
.apps--Content {
background: white;
height: 100%;
width: 100%;
}

View File

@ -2,12 +2,18 @@
/* This software may be modified and distributed under the terms
/* of the ISC license. See the LICENSE file for details. */
.apps--SideBar-Item {}
.apps--SideBar-Item {
padding: 0 !important;
}
.apps--SideBar-Item-NavLink {
display: block;
padding: 0.75em 0.75em;
white-space: nowrap;
}
.apps--SideBar-Item-NavLink-active {
background: white;
border-radius: .28571429rem 0 0 .28571429rem;
color: inherit;
}

View File

@ -3,8 +3,13 @@
/* of the ISC license. See the LICENSE file for details. */
.apps--SideBar {
background: #f9f9f9;
flex-shrink: 0;
min-width: 12em;
padding: 0.75em;
padding: 0.75em 0 0 0.75em;
text-align: left;
}
.apps--SideBar .ui.vertical.menu {
margin: 0;
}

View File

@ -4,9 +4,9 @@
// @flow
export type Route = {
component: React$StatelessFunctionalComponent<*>,
component: React$ComponentType<*>,
i18n: I18Next$Translate$Config,
icon: React$StatelessFunctionalComponent<*> | string,
icon: React$ComponentType<*> | string,
isExact: boolean,
isHidden: boolean,
name: string,

View File

@ -12,7 +12,7 @@
},
"dependencies": {
"@polkadot/extrinsics-substrate": "^0.12.7",
"@polkadot/rx-react": "^0.5.5",
"@polkadot/rx-react": "^0.6.1",
"@polkadot/storage-substrate": "^0.12.7",
"@polkadot/ui-react": "^0.10.3",
"@polkadot/util": "^0.19.5",

View File

@ -22,8 +22,6 @@ export default function PairDisplay ({ className, pair, style }: Props): React$N
const address = pair.address();
const { name } = pair.getMeta();
console.log('address', address);
return (
<div
className={['ui--InputyAddress-PairDisplay', className].join(' ')}

View File

@ -9,6 +9,7 @@ import './InputAddress.css';
import React from 'react';
import addressDecode from '@polkadot/util-keyring/address/decode';
import addressEncode from '@polkadot/util-keyring/address/encode';
// TODO: We need to actually pass the keyring in, this is the testing keyring
import keyring from '../keyring';
@ -16,10 +17,10 @@ import RxDropdown from '../RxDropdown';
import PairDisplay from './PairDisplay';
type Props = BareProps & {
defaultValue?: Uint8Array,
isError?: boolean,
label?: string,
onChange?: (event: SyntheticEvent<*>, value: Uint8Array) => void,
subject?: rxjs$Subject<*>
onChange: (value: Uint8Array) => void
};
const options = keyring.getPairs().map((pair) => ({
@ -32,15 +33,21 @@ const options = keyring.getPairs().map((pair) => ({
const transform = (value: string): Uint8Array =>
addressDecode(value);
export default function InputAddress ({ className, isError, label, onChange, style, subject }: Props): React$Node {
export default function InputAddress ({ className, defaultValue, isError, label, onChange, style }: Props): React$Node {
let _defaultValue;
if (defaultValue) {
_defaultValue = addressEncode(defaultValue);
}
return (
<RxDropdown
className={['ui--InputAddress', className].join(' ')}
defaultValue={_defaultValue}
isError={isError}
label={label}
onChange={onChange}
options={options}
subject={subject}
transform={transform}
/>
);

View File

@ -2,9 +2,11 @@
/* This software may be modified and distributed under the terms
/* of the ISC license. See the LICENSE file for details. */
.ui--InputExtrinsic {}
.ui--RxDropdownLinked {}
.ui--InputExtrinsic-SelectMethod .text {
.ui--RxDropdownLinked-Items {}
.ui--RxDropdownLinked-Items .text {
box-sizing: border-box;
display: flex !important;
flex-wrap: nowrap;
@ -14,16 +16,16 @@
white-space: nowrap;
}
.ui--InputExtrinsic-SelectMethod-Item > .text {
.ui--RxDropdownLinked-Item > .text {
padding-left: 1em;
}
.ui--InputExtrinsic-SelectMethod-Item-text,
.ui--InputExtrinsic-SelectMethod-Item-call {
.ui--RxDropdownLinked-Item-text,
.ui--RxDropdownLinked-Item-call {
display: inline-block;
}
.ui--InputExtrinsic-SelectMethod-Item-call {
.ui--RxDropdownLinked-Item-call {
flex: 1;
font-family: monospace;
margin-left: 1rem;
@ -33,6 +35,8 @@
text-overflow: ellipsis;
}
.ui--InputExtrinsic-SelectMethod-Item-text {
.ui--RxDropdownLinked-Item-text {
flex: 1 0;
}
.ui--RxDropdownLinked-Sections {}

View File

@ -16,26 +16,25 @@ import createOptions from './options/method';
type Props = I18nProps & {
isError?: boolean,
label?: string,
onChange?: (event: SyntheticEvent<*>, value: Extrinsic) => void,
subject?: rxjs$Subject<Extrinsic>,
onChange: (value: Extrinsic) => void,
type: 'private' | 'public',
value?: Extrinsic
value: Extrinsic
};
function SelectMethod ({ className, isError, label = '', onChange, style, subject, t, type, value }: Props): React$Node {
function SelectMethod ({ className, isError, label = '', onChange, style, t, type, value: { name, section } }: Props): React$Node {
// $FlowFixMe string vs ...
if (!value || !extrinsics[value.section]) {
if (!extrinsics[section]) {
return null;
}
const methods = extrinsics[value.section].methods[type];
const methods = extrinsics[section].methods[type];
const transform = (name: string): Extrinsic =>
methods[name];
const options = createOptions(value.section, type);
const options = createOptions(section, type);
return (
<RxDropdown
className={['ui--InputExtrinsic-SelectMethod', className].join(' ')}
className={['ui--RxDropdownLinked-Items', className].join(' ')}
isError={isError}
label={label || t('input.extrinsic.method', {
defaultValue: 'with the extrinsic'
@ -43,9 +42,8 @@ function SelectMethod ({ className, isError, label = '', onChange, style, subjec
onChange={onChange}
options={options}
style={style}
subject={subject}
transform={transform}
value={value.name}
value={name}
/>
);
}

View File

@ -3,7 +3,7 @@
// of the ISC license. See the LICENSE file for details.
// @flow
import type { StateDb$SectionNames } from '@polkadot/storage/types';
import type { Extrinsic, ExtrinsicSectionName } from '@polkadot/extrinsics/types';
import type { I18nProps } from '../types';
import React from 'react';
@ -13,26 +13,29 @@ import translate from '../translate';
import createOptions from './options/section';
type Props = I18nProps & {
defaultValue?: ExtrinsicSectionName,
isError?: boolean,
label?: string,
subject: rxjs$BehaviorSubject<StateDb$SectionNames>,
type: 'private' | 'public'
onChange: (value: ExtrinsicSectionName) => void,
type: 'private' | 'public',
value: Extrinsic
};
function SelectSection ({ className, isError, label = '', subject, style, t, type }: Props): React$Node {
function SelectSection ({ className, defaultValue, isError, label = '', onChange, style, t, type, value: { section } }: Props): React$Node {
const options = createOptions(type);
return (
<RxDropdown
className={['ui--InputExtrinsic-SelectSection', className].join(' ')}
defaultValue={subject.getValue()}
className={['ui--RxDropdownLinked-Sections', className].join(' ')}
defaultValue={defaultValue}
isError={isError}
label={label || t('input.extrinsic.section', {
defaultValue: 'from extrinsic section'
})}
onChange={onChange}
options={options}
style={style}
subject={subject}
value={section}
/>
);
}

View File

@ -3,17 +3,15 @@
// of the ISC license. See the LICENSE file for details.
// @flow
// TODO: We have a lot shared between this and InputExtrinsic
// TODO: We have a lot shared between this and InputStorage
import type { Extrinsic } from '@polkadot/extrinsics/types';
import type { Extrinsic, ExtrinsicSectionName } from '@polkadot/extrinsics/types';
import type { I18nProps } from '../types';
import './InputExtrinsic.css';
import React from 'react';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import map from '@polkadot/extrinsics-substrate';
import withObservable from '@polkadot/rx-react/with/observable';
import translate from '../translate';
import SelectMethod from './SelectMethod';
@ -21,81 +19,80 @@ import SelectSection from './SelectSection';
import methodOptions from './options/method';
type Props = I18nProps & {
defaultValue: Extrinsic,
isError?: boolean,
isPrivate?: boolean,
labelMethod?: string,
labelSection?: string,
onChange?: (event: SyntheticEvent<*>, value: Extrinsic) => void,
subject: rxjs$BehaviorSubject<Extrinsic>
onChange: (value: Extrinsic) => void
};
class InputExtrinsic extends React.PureComponent<Props> {
sectionSubject: rxjs$Subject<string>;
SelectMethod: React$ComponentType<*>;
subscriptions: Array<rxjs$ISubscription>
type State = {
value: Extrinsic
};
class InputExtrinsic extends React.PureComponent<Props, State> {
state: State;
constructor (props: Props) {
super(props);
this.sectionSubject = new BehaviorSubject(props.subject.getValue().section);
this.SelectMethod = withObservable(props.subject)(SelectMethod);
}
componentWillMount () {
this.subscriptions = [
this.sectionSubject.subscribe((section) => {
const { isPrivate = false, subject } = this.props;
const current = subject.getValue();
if (current.section === section) {
return;
}
const type = isPrivate ? 'private' : 'public';
const options = methodOptions(section, type);
subject.next(
// $FlowFixMe we have string to be generix, but...
map[section].methods[type][options[0].value]
);
})
];
}
componentWillUnmount () {
this.subscriptions.forEach((s) =>
s.unsubscribe()
);
this.state = {
value: this.props.defaultValue
};
}
render (): React$Node {
const { className, isPrivate = false, labelMethod, labelSection, onChange, style, subject } = this.props;
const { className, isPrivate = false, labelMethod, labelSection, style } = this.props;
const { value } = this.state;
const type = isPrivate ? 'private' : 'public';
const SelectMethod = this.SelectMethod;
return (
<div
className={['ui--InputExtrinsic', 'ui--form', className].join(' ')}
className={['ui--RxDropdownLinked', 'ui--form', className].join(' ')}
style={style}
>
<div className='small'>
<SelectSection
label={labelSection}
subject={this.sectionSubject}
onChange={this.onSectionChange}
type={type}
value={value}
/>
</div>
<div className='large'>
<SelectMethod
label={labelMethod}
onChange={onChange}
subject={subject}
onChange={this.onKeyChange}
value={value}
type={type}
/>
</div>
</div>
);
}
onKeyChange = (value: Extrinsic): void => {
const { onChange } = this.props;
this.setState({ value }, () =>
onChange(value)
);
}
onSectionChange = (section: ExtrinsicSectionName): void => {
const { isPrivate = false } = this.props;
if (this.state.value.section === section) {
return;
}
const type = isPrivate ? 'private' : 'public';
const options = methodOptions(section, type);
const value = map[section].methods[type][options[0].value];
this.onKeyChange(value);
}
}
export default translate(InputExtrinsic);

View File

@ -26,17 +26,17 @@ export default function createOptions (sectionName: any, type: 'private' | 'publ
const inputs = Object.keys(params).join(', ');
return {
className: 'ui--InputExtrinsic-SelectMethod-Item',
className: 'ui--RxDropdownLinked-Item',
key: `${sectionName}_${name}`,
text: [
<div
className='ui--InputExtrinsic-SelectMethod-Item-text'
className='ui--RxDropdownLinked-Item-text'
key={`${sectionName}_${name}:text`}
>
{description || name}
</div>,
<div
className='ui--InputExtrinsic-SelectMethod-Item-call'
className='ui--RxDropdownLinked-Item-call'
key={`${sectionName}_${name}:call`}
>
{name}({inputs})

View File

@ -1,40 +0,0 @@
/* Copyright 2017-2018 Jaco Greeff
/* This software may be modified and distributed under the terms
/* of the ISC license. See the LICENSE file for details. */
.ui--InputStorage {}
.ui--InputStorage-SelectKey .text {
box-sizing: border-box;
display: flex !important;
flex-wrap: nowrap;
justify-content: space-between;
position: relative;
width: 100%;
white-space: nowrap;
}
.ui--InputStorage-SelectKey-Item > .text {
padding-left: 1em;
}
.ui--InputStorage-SelectKey-Item-text,
.ui--InputStorage-SelectKey-Item-call {
display: inline-block;
}
.ui--InputStorage-SelectKey-Item-call {
flex: 1;
font-family: monospace;
margin-left: 1rem;
opacity: 0.5;
overflow: hidden;
text-align: right;
text-overflow: ellipsis;
}
.ui--InputStorage-SelectKey-Item-text {
flex: 1 0;
overflow: hidden;
text-overflow: ellipsis;
}

View File

@ -16,25 +16,24 @@ import createOptions from './options/key';
type Props = I18nProps & {
isError?: boolean,
label?: string,
onChange?: (event: SyntheticEvent<*>, value: StorageDef$Key) => void,
subject?: rxjs$Subject<StorageDef$Key>,
value?: StorageDef$Key
onChange: (value: StorageDef$Key) => void,
value: StorageDef$Key
};
function SelectKey ({ className, isError, label = '', onChange, style, subject, t, value }: Props): React$Node {
function SelectKey ({ className, isError, label = '', onChange, style, t, value: { name, section } }: Props): React$Node {
// $FlowFixMe string vs ...
if (!value || !map[value.section]) {
if (!map[section]) {
return null;
}
const keys = map[value.section].keys;
const keys = map[section].keys;
const transform = (name: string): StorageDef$Key =>
keys[name];
const options = createOptions(value.section);
const options = createOptions(section);
return (
<RxDropdown
className={['ui--InputStorage-SelectKey', className].join(' ')}
className={['ui--RxDropdownLinked-Items', className].join(' ')}
isError={isError}
label={label || t('input.storage.key', {
defaultValue: 'with storage key'
@ -42,9 +41,8 @@ function SelectKey ({ className, isError, label = '', onChange, style, subject,
onChange={onChange}
options={options}
style={style}
subject={subject}
transform={transform}
value={value.name}
value={name}
/>
);
}

View File

@ -3,7 +3,7 @@
// of the ISC license. See the LICENSE file for details.
// @flow
import type { StateDb$SectionNames } from '@polkadot/storage/types';
import type { StorageDef$Key, StateDb$SectionNames } from '@polkadot/storage/types';
import type { I18nProps } from '../types';
import React from 'react';
@ -13,25 +13,28 @@ import translate from '../translate';
import createOptions from './options/section';
type Props = I18nProps & {
defaultValue?: StateDb$SectionNames,
isError?: boolean,
label: string,
subject: rxjs$BehaviorSubject<StateDb$SectionNames>
onChange: (value: StateDb$SectionNames) => void,
value: StorageDef$Key
};
const options = createOptions();
function SelectSection ({ className, isError, label, subject, style, t }: Props): React$Node {
function SelectSection ({ className, defaultValue, isError, label, onChange, style, t, value: { section } }: Props): React$Node {
return (
<RxDropdown
className={['ui--InputStorage-SelectSection', className].join(' ')}
defaultValue={subject.getValue()}
className={['ui--RxDropdownLinked-Sections', className].join(' ')}
defaultValue={defaultValue}
isError={isError}
label={label || t('input.storage.section', {
defaultValue: 'storage area'
})}
onChange={onChange}
options={options}
style={style}
subject={subject}
value={section}
/>
);
}

View File

@ -5,14 +5,12 @@
// TODO: We have a lot shared between this and InputExtrinsic
import type { StorageDef$Key } from '@polkadot/storage/types';
import type { StorageDef$Key, StateDb$SectionNames } from '@polkadot/storage/types';
import type { I18nProps } from '../types';
import './InputStorage.css';
import '../InputExtrinsic/InputExtrinsic.css';
import React from 'react';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import withObservable from '@polkadot/rx-react/with/observable';
import map from '@polkadot/storage-substrate/keys';
import translate from '../translate';
@ -21,76 +19,73 @@ import SelectSection from './SelectSection';
import keyOptions from './options/key';
type Props = I18nProps & {
defaultValue: StorageDef$Key,
isError?: boolean,
labelMethod?: string,
labelSection?: string,
onChange?: (event: SyntheticEvent<*>, value: StorageDef$Key) => void,
subject: rxjs$BehaviorSubject<StorageDef$Key>
onChange: (value: StorageDef$Key) => void,
};
class InputStorage extends React.PureComponent<Props> {
sectionSubject: rxjs$Subject<string>;
subscriptions: Array<rxjs$ISubscription>
SelectKey: React$ComponentType<*>;
type State = {
value: StorageDef$Key
};
class InputStorage extends React.PureComponent<Props, State> {
state: State;
constructor (props: Props) {
super(props);
this.sectionSubject = new BehaviorSubject(props.subject.getValue().section);
this.SelectKey = withObservable(props.subject)(SelectKey);
}
componentWillMount () {
this.subscriptions = [
this.sectionSubject.subscribe((section) => {
const { subject } = this.props;
const current = subject.getValue();
if (current.section === section) {
return;
}
const options = keyOptions(section);
subject.next(
// $FlowFixMe we have string to be generix, but...
map[section].keys[options[0].value]
);
})
];
}
componentWillUnmount () {
this.subscriptions.forEach((s) =>
s.unsubscribe()
);
this.state = {
value: this.props.defaultValue
};
}
render (): React$Node {
const { className, labelMethod, labelSection, onChange, style, subject } = this.props;
const SelectKey = this.SelectKey;
const { className, labelMethod, labelSection, style } = this.props;
const { value } = this.state;
return (
<div
className={['ui--InputStorage', 'ui--form', className].join(' ')}
className={['ui--RxDropdownLinked', 'ui--form', className].join(' ')}
style={style}
>
<div className='small'>
<SelectSection
label={labelSection}
subject={this.sectionSubject}
onChange={this.onSectionChange}
value={value}
/>
</div>
<div className='large'>
<SelectKey
label={labelMethod}
onChange={onChange}
subject={subject}
onChange={this.onKeyChange}
value={value}
/>
</div>
</div>
);
}
onKeyChange = (value: StorageDef$Key): void => {
const { onChange } = this.props;
this.setState({ value }, () =>
onChange(value)
);
}
onSectionChange = (section: StateDb$SectionNames): void => {
if (this.state.value.section === section) {
return;
}
const options = keyOptions(section);
const value = map[section].keys[options[0].value];
this.onKeyChange(value);
}
}
export default translate(InputStorage);

View File

@ -29,17 +29,17 @@ export default function createOptions (sectionName: any): Array<*> {
const inputs = Object.keys(params).join(', ');
return {
className: 'ui--InputStorage-SelectKey-Item',
className: 'ui--RxDropdownLinked-Item',
key: `${sectionName}_${name}`,
text: [
<div
className='ui--InputStorage-SelectKey-Item-text'
className='ui--RxDropdownLinked-Item-text'
key={`${sectionName}_${name}:text`}
>
{description || name}
</div>,
<div
className='ui--InputStorage-SelectKey-Item-call'
className='ui--RxDropdownLinked-Item-call'
key={`${sectionName}_${name}:call`}
>
{name}({inputs})

View File

@ -10,15 +10,14 @@ import React from 'react';
import InputAddress from '../../InputAddress';
import Base from './Base';
export default function Account ({ isError, label, subject, t, value: { options: { initValue } = {} } }: Props): React$Node {
const defaultValue = initValue;
// eslint-disable-next-line no-unused-vars
const onChange = (event: SyntheticEvent<*>, value?: Uint8Array): void => {
subject.next({
export default function Account ({ index, isError, label, onChange, t, value: { options: { initValue } = {} } }: Props): React$Node {
// flowlint-next-line unclear-type:off
const defaultValue = ((initValue: any): Uint8Array);
const _onChange = (value?: Uint8Array): void =>
onChange(index, {
isValid: !!value && value.length === 32,
value
});
};
return (
<Base
@ -28,7 +27,7 @@ export default function Account ({ isError, label, subject, t, value: { options:
<InputAddress
defaultValue={defaultValue}
label={label}
onChange={onChange}
onChange={_onChange}
placeholder='0x...'
/>
</Base>

View File

@ -12,11 +12,10 @@ import Input from 'semantic-ui-react/dist/es/elements/Input';
import Base from './Base';
export default function Amount ({ isError, label, subject, t, value: { options: { initValue = 0, maxValue, minValue = 0 } = {} } }: Props): React$Node {
const defaultValue = initValue || minValue;
export default function Amount ({ index, isError, label, onChange, t, value: { options: { initValue = 0, maxValue, minValue = 0 } = {} } }: Props): React$Node {
// eslint-disable-next-line no-unused-vars
const onChange = (event: SyntheticEvent<*>, { value }) =>
subject.next({
const _onChange = (event: SyntheticEvent<*>, { value }) =>
onChange(index, {
isValid: true,
value: new BN(value || minValue || 0)
});
@ -27,11 +26,11 @@ export default function Amount ({ isError, label, subject, t, value: { options:
size='small'
>
<Input
defaultValue={defaultValue}
defaultValue={initValue || minValue}
error={isError}
max={maxValue}
min={minValue}
onChange={onChange}
onChange={_onChange}
type='number'
/>
</Base>

View File

@ -15,9 +15,9 @@ const options = [
{ text: 'Yes', value: true }
];
export default function Bool ({ isError, label, subject, t, value: { options: { initValue = false } = {} } }: Props): React$Node {
const onChange = (event: SyntheticEvent<*>, { value }) =>
subject.next({
export default function Bool ({ index, isError, label, onChange, t, value: { options: { initValue = false } = {} } }: Props): React$Node {
const _onChange = (event: SyntheticEvent<*>, { value }) =>
onChange(index, {
isValid: true,
value
});
@ -32,7 +32,7 @@ export default function Bool ({ isError, label, subject, t, value: { options: {
selection
defaultValue={initValue}
options={options}
onChange={onChange}
onChange={_onChange}
/>
</Base>
);

View File

@ -20,9 +20,9 @@ type Props = BaseProps & {
const defaultValidate = (u8a: Uint8Array): boolean =>
true;
export default function Bytes ({ isError, length = -1, label, size = 'full', subject, validate = defaultValidate }: Props): React$Node {
export default function Bytes ({ index, isError, length = -1, label, onChange, size = 'full', validate = defaultValidate }: Props): React$Node {
// eslint-disable-next-line no-unused-vars
const onChange = (event: SyntheticEvent<*>, { value }) => {
const _onChange = (event: SyntheticEvent<*>, { value }) => {
let u8a;
try {
@ -35,7 +35,7 @@ export default function Bytes ({ isError, length = -1, label, size = 'full', sub
? u8a.length === length
: u8a.length !== 0;
subject.next({
onChange(index, {
isValid: isValidLength && validate(u8a),
value: u8a
});
@ -48,7 +48,7 @@ export default function Bytes ({ isError, length = -1, label, size = 'full', sub
>
<Input
error={isError}
onChange={onChange}
onChange={_onChange}
placeholder='0x...'
type='text'
/>

View File

@ -4,13 +4,9 @@
// @flow
import type { BareProps } from '@polkadot/ui-react-app/types';
import type { RawParam } from '../types';
import React from 'react';
import Dropzone from 'react-dropzone';
import withObservable from '@polkadot/rx-react/with/observable';
import bnToU8a from '@polkadot/util/bn/toU8a';
import u8aConcat from '@polkadot/util/u8a/concat';
import translate from '../../translate';
import Base from './Base';
@ -18,70 +14,82 @@ import Base from './Base';
type Props = BareProps & {
isError?: boolean,
label: string,
onChange: (file?: File, contents?: Uint8Array) => void,
subject: rxjs$BehaviorSubject<RawParam>,
onChange: (contents: Uint8Array) => void,
t: I18Next$Translate
}
type WrappedProps = Props & {
value: RawParam
}
type State = {
file?: {
name: string,
size: number
}
};
function BytesFile ({ className, isError = false, label, onChange, subject, t }: Props): React$Node {
const onDrop = (files: Array<File>) => {
files.forEach((file) => {
const reader = new FileReader();
class BytesFile extends React.PureComponent<Props, State> {
state: State;
reader.onload = () => {
// flowlint-next-line unclear-type:off
const data = new Uint8Array((((reader.result): any): ArrayBuffer));
const vec = u8aConcat(bnToU8a(data.length, 32, true), data);
constructor (props: Props) {
super(props);
onChange(file, vec);
};
reader.onabort = () =>
onChange();
reader.onerror = () =>
onChange();
this.state = {};
}
reader.readAsArrayBuffer(file);
});
};
render (): React$Node {
const { className, isError = false, label, t } = this.props;
const { file } = this.state;
const Component = withObservable(subject)(({ value: { data } = {} }: WrappedProps): React$Node => {
return (
<Base
label={label}
size='full'
>
<Dropzone
className={['ui--Param-File', isError ? 'error' : '', !!data, className].join(' ')}
className={['ui--Param-File', isError ? 'error' : '', className].join(' ')}
multiple={false}
onDrop={onDrop}
onDrop={this.onDrop}
>
<div className='label'>
{
!data
!file
? t('file.dnd', {
defaultValue: 'drag and drop the file here'
})
: t('file.description', {
defaultValue: '{{name}} ({{size}} bytes)',
replace: {
name: data.name,
size: data.size
}
replace: file
})
}
</div>
</Dropzone>
</Base>
);
});
}
return (
<Component />
);
onDrop = (files: Array<File>) => {
const { onChange } = this.props;
files.forEach((file) => {
const reader = new FileReader();
reader.onabort = () => {};
reader.onerror = () => {};
reader.onload = () => {
// flowlint-next-line unclear-type:off
const data = new Uint8Array((((reader.result): any): ArrayBuffer));
onChange(data);
this.setState({
file: {
name: file.name,
size: data.length
}
});
};
reader.readAsArrayBuffer(file);
});
};
}
export default translate(BytesFile);

View File

@ -9,14 +9,15 @@ import React from 'react';
import Bytes from './Bytes';
export default function Hash ({ isError, label, subject, value }: Props): React$Node {
export default function Hash ({ index, isError, label, onChange, value }: Props): React$Node {
return (
<Bytes
index={index}
isError={isError}
label={label}
length={32}
onChange={onChange}
size='medium'
subject={subject}
value={value}
/>
);

View File

@ -15,12 +15,7 @@ type Props = BaseProps & {
t: I18Next$Translate
};
function Unknown ({ label, subject, t, value: { type } }: Props): React$Node {
subject.next({
isValid: false,
value: new Uint8Array([])
});
function Unknown ({ label, t, value: { type } }: Props): React$Node {
return (
<Base
size='full'

View File

@ -17,10 +17,10 @@ const options = [
{ text: 'Simple majority', value: 2 }
];
export default function VoteThreshold ({ label, subject, t, value: { options: { initValue = 0 } = {} } }: Props): React$Node {
export default function VoteThreshold ({ index, label, onChange, t, value: { options: { initValue = 0 } = {} } }: Props): React$Node {
const defaultValue = initValue || 0;
const onChange = (event: SyntheticEvent<*>, { value }) =>
subject.next({
const _onChange = (event: SyntheticEvent<*>, { value }) =>
onChange(index, {
isValid: true,
value
});
@ -34,7 +34,7 @@ export default function VoteThreshold ({ label, subject, t, value: { options: {
selection
defaultValue={defaultValue}
options={options}
onChange={onChange}
onChange={_onChange}
/>
</Base>
);

View File

@ -6,25 +6,24 @@
import type { Props } from '../types';
import React from 'react';
import bnToU8a from '@polkadot/util/bn/toU8a';
import u8aConcat from '@polkadot/util/u8a/concat';
import BytesFile from './File';
export default function Wasm ({ isError, label, subject }: Props): React$Node {
const onChange = (file?: File, value?: Uint8Array) => {
// TODO: Validate that we have actual proper WASM code
subject.next({
data: file,
isValid: !!value && value.length !== 0,
value
export default function Wasm ({ index, isError, label, onChange }: Props): React$Node {
// TODO: Validate that we have actual proper WASM code
const _onChange = (value: Uint8Array) =>
onChange(index, {
isValid: value.length !== 0,
value: u8aConcat(bnToU8a(value.length, 32, true), value)
});
};
return (
<BytesFile
isError={isError}
label={label}
onChange={onChange}
subject={subject}
onChange={_onChange}
/>
);
}

View File

@ -14,35 +14,37 @@ import typeToText from '../typeToText';
import findComponent from './findComponent';
type Props = I18nProps & {
index: number,
overrides?: ComponentMap,
subject: rxjs$BehaviorSubject<RawParam>,
onChange: (value: RawParam) => void,
value: Param & {
name: string
};
};
function ParamComponent ({ className, overrides, style, subject, value: { name, type, options = {} } = {} }: Props): React$Node {
function ParamComponent ({ className, index, onChange, overrides, style, value: { name, type, options = {} } = {} }: Props): React$Node {
if (!type) {
return null;
}
const Component = findComponent(type, overrides);
const renderComponent = (Component: React$ComponentType<*>, index: number = -1) => {
const renderComponent = (Component: React$ComponentType<*>, sub: number = -1) => {
const _type: Param$Type = Array.isArray(type)
? type[index]
? type[sub]
: type;
const text = typeToText(_type);
const labelExtra = index === -1
const labelExtra = sub === -1
? ''
: ` (${index})`;
return (
<Component
className='ui--Param'
index={index}
key={`${name}:${text}:${index}}`}
label={`${name}: ${text}${labelExtra}`}
// FIXME subjects are not for array components (as defined here)
subject={subject}
onChange={onChange}
value={{
options,
type: _type

View File

@ -13,17 +13,19 @@ import React from 'react';
import translate from '../translate';
import Param from './Param';
import createSubjects from './subjects';
import createValues from './values';
type RawParams = Array<RawParam>;
type Props = I18nProps & {
overrides?: ComponentMap,
subject: rxjs$BehaviorSubject<Array<RawParam>>,
value: Extrinsic;
extrinsic: Extrinsic,
onChange: (value: RawParams) => void,
overrides?: ComponentMap
};
type State = {
subjects: Array<rxjs$BehaviorSubject<RawParam>>,
subscriptions: Array<rxjs$ISubscription>
extrinsic: Extrinsic,
values: Array<RawParam>
};
class Params extends React.PureComponent<Props, State> {
@ -32,62 +34,35 @@ class Params extends React.PureComponent<Props, State> {
constructor (props: Props) {
super(props);
this.state = {
subjects: [],
subscriptions: []
};
// wed ont' set anything, getDerivedStateFromProps will update
this.state = ({}: $Shape<State>);
}
static unsubscribe (subscriptions: Array<rxjs$ISubscription>): void {
subscriptions.forEach((s) =>
s.unsubscribe()
);
}
static getDerivedStateFromProps (props: Props, prevState: State): $Shape<State> | null {
const { value: { params = {} } = {}, subject } = props;
if (Object.keys(params).length === 0) {
Params.unsubscribe(prevState.subscriptions);
return {
subjects: [],
subscriptions: []
};
}
const subjects = createSubjects(params, subject);
let subscriptions;
if (subject) {
const onChange = (): void => {
subject.next(
subjects.map((s) => s.getValue())
);
};
subscriptions = subjects.map((s) => s.subscribe(onChange));
}
return {
subjects,
subscriptions
};
}
componentWillUnmount () {
Params.unsubscribe(this.state.subscriptions);
}
render (): React$Node {
const { className, overrides, style, value } = this.props;
const { subjects } = this.state;
if (!value || !subjects.length) {
static getDerivedStateFromProps ({ extrinsic, onChange }: Props, { extrinsic: { name, section } = {} }: State): $Shape<State> | null {
if (name === extrinsic.name && section === extrinsic.section) {
return null;
}
const { name, params = {} } = value;
const { params = {} } = extrinsic;
const values = createValues(params);
onChange(values);
return {
extrinsic,
values: createValues(params)
};
}
render (): React$Node {
const { className, overrides, style, extrinsic } = this.props;
const { values } = this.state;
if (!values.length) {
return null;
}
const { name, params = {} } = extrinsic;
const paramNames = Object.keys(params);
if (paramNames.length === 0) {
@ -105,9 +80,10 @@ class Params extends React.PureComponent<Props, State> {
return (
<Param
index={index}
key={`${name}:${paramName}:${index}`}
onChange={this.onChangeParam}
overrides={overrides}
subject={subjects[index]}
value={{
name: paramName,
options,
@ -120,6 +96,24 @@ class Params extends React.PureComponent<Props, State> {
</div>
);
}
onChangeParam = (at: number, next: RawParam): void => {
this.setState(
(prevState: State): $Shape<State> => ({
values: prevState.values.map((value, index) =>
index === at
? next
: value
)
}),
(): void => {
const { onChange } = this.props;
const { values } = this.state;
onChange(values);
}
);
}
}
export default translate(Params);

View File

@ -7,19 +7,18 @@ import type { Param, Param$Type } from '@polkadot/primitives/param';
import type { BareProps } from '../types';
export type RawParam = {
// flowlint-next-line unclear-type:off
data?: any,
isValid: boolean,
value: mixed,
}
export type BaseProps = BareProps & {
subject: rxjs$BehaviorSubject<RawParam>,
onChange: (index: number, value: RawParam) => void,
value: Param
};
export type Props = BaseProps & {
isError?: boolean,
index: number,
label: string
};

View File

@ -3,24 +3,23 @@
// of the ISC license. See the LICENSE file for details.
// @flow
import type { Params } from '@polkadot/primitives/param';
import type { Param, Params } from '@polkadot/primitives/param';
import type { RawParam } from './types';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import isUndefined from '@polkadot/util/is/undefined';
import getInitValue from './initValue';
export default function subjects (params: Params, subject: rxjs$BehaviorSubject<Array<RawParam>>): Array<rxjs$BehaviorSubject<RawParam>> {
export default function values (params: Params): Array<RawParam> {
return Object
.keys(params)
.map((name: string): rxjs$BehaviorSubject<RawParam> => {
const param = params[name];
.values(params)
// $FlowFixMe yes, we are sure, the type is correct
.map((param: Param): RawParam => {
const value = getInitValue(param);
return new BehaviorSubject({
return {
isValid: !isUndefined(value),
value
});
};
});
}

View File

@ -14,31 +14,24 @@ type Props = BareProps & {
isError?: boolean,
label?: string,
// flowlint-next-line unclear-type:off
onChange?: (event: SyntheticEvent<*>, value: any) => void,
subject?: rxjs$Subject<*>,
onChange: (value: any) => void,
// flowlint-next-line unclear-type:off
transform?: (value: any) => any
};
export default function RxDropdown (props: Props): React$Node {
const onChange = (event: SyntheticEvent<*>, { value }): void => {
const _value = props.transform
? props.transform(value)
: value;
if (props.subject) {
props.subject.next(_value);
}
if (props.onChange) {
props.onChange(event, _value);
}
};
const _onChange = (event: SyntheticEvent<*>, { value }): void =>
props.onChange(
props.transform
? props.transform(value)
: value
);
const _props = {...props};
delete _props.isError;
delete _props.label;
delete _props.onChange;
delete _props.transform;
return [
@ -51,7 +44,7 @@ export default function RxDropdown (props: Props): React$Node {
{..._props}
className={props.className}
error={props.isError}
onChange={onChange}
onChange={_onChange}
/>
];
}

View File

@ -16,7 +16,7 @@ i18n
fallbackLng: 'en',
ns: ['ui'],
defaultNS: 'ui',
debug: true,
debug: false,
interpolation: {
escapeValue: false
},

View File

@ -769,20 +769,20 @@
"@polkadot/util" "^0.19.5"
babel-runtime "^6.26.0"
"@polkadot/rx-api@^0.5.5":
version "0.5.5"
resolved "https://registry.yarnpkg.com/@polkadot/rx-api/-/rx-api-0.5.5.tgz#9301b2e88c734f3882721a4d490524e74e37a88b"
"@polkadot/rx-api@^0.6.1":
version "0.6.1"
resolved "https://registry.yarnpkg.com/@polkadot/rx-api/-/rx-api-0.6.1.tgz#a99d167d7a5046d5cff7bdab07da3e147f1c44a7"
dependencies:
"@polkadot/api" "^0.9.16"
"@polkadot/api-provider" "^0.9.16"
rxjs "^5.5.10"
"@polkadot/rx-react@^0.5.5":
version "0.5.5"
resolved "https://registry.yarnpkg.com/@polkadot/rx-react/-/rx-react-0.5.5.tgz#3910d45ab25979b83521cbc77e9bb392a409553e"
"@polkadot/rx-react@^0.6.1":
version "0.6.1"
resolved "https://registry.yarnpkg.com/@polkadot/rx-react/-/rx-react-0.6.1.tgz#805776ee938df1621153ec3e16e674b6eabdc102"
dependencies:
"@polkadot/primitives" "^0.12.7"
"@polkadot/rx-api" "^0.5.5"
"@polkadot/rx-api" "^0.6.1"
"@polkadot/storage-substrate" "^0.12.7"
"@polkadot/util" "^0.19.5"