Allow setCode file uploads (#10)

* File upload (setCode)

* Cleanup Param types
This commit is contained in:
Jaco Greeff 2018-04-30 11:35:23 +02:00 committed by GitHub
parent 18c1e08a0e
commit 2206d30f69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 306 additions and 135 deletions

View File

@ -1,12 +1,14 @@
[![polkadotjs](https://img.shields.io/badge/polkadot-js-orange.svg?style=flat-square)](https://polkadot.js.org)
![isc](https://img.shields.io/badge/license-ISC-lightgrey.svg?style=flat-square)
[![style](https://img.shields.io/badge/code%20style-semistandard-lightgrey.svg?style=flat-square)](https://github.com/Flet/semistandard)
[![npm](https://img.shields.io/npm/v/@polkadot/portal.svg?style=flat-square)](https://www.npmjs.com/package/@polkadot/portal)
[![travis](https://img.shields.io/travis/polkadot-js/portal.svg?style=flat-square)](https://travis-ci.org/polkadot-js/portal)
[![npm](https://img.shields.io/npm/v/@polkadot/apps.svg?style=flat-square)](https://www.npmjs.com/package/@polkadot/apps)
[![travis](https://img.shields.io/travis/polkadot-js/apps.svg?style=flat-square)](https://travis-ci.org/polkadot-js/apps)
[![greenkeeper](https://img.shields.io/badge/greenkeeper-enabled-brightgreen.svg?style=flat-square)](https://greenkeeper.io/)
[![dependency](https://img.shields.io/david/polkadot-js/portal.svg?style=flat-square)](https://david-dm.org/polkadot-js/portal)
[![devDependency](https://img.shields.io/david/dev/polkadot-js/portal.svg?style=flat-square)](https://david-dm.org/polkadot-js/portal#info=devDependencies)
[![dependency](https://img.shields.io/david/polkadot-js/apps.svg?style=flat-square)](https://david-dm.org/polkadot-js/apps)
[![devDependency](https://img.shields.io/david/dev/polkadot-js/apps.svg?style=flat-square)](https://david-dm.org/polkadot-js/apps#info=devDependencies)
# @polkadot/portal
# @polkadot/apps
A Portal into the Polkadot network. Provides a view and interaction layer from a browser.
This can be accessed as a hosted application via [https://polkadot.js.org/apps/](https://polkadot.js.org/apps/).

5
flow-typed/react-dropzone.js vendored Normal file
View File

@ -0,0 +1,5 @@
// @flow
declare module 'react-dropzone' {
declare module.exports: React$StatelessFunctionalComponent<*>
}

View File

@ -20,6 +20,7 @@
"@polkadot/util-keyring": "^0.19.4",
"react": "^16.3.1",
"react-dom": "^16.3.1",
"react-dropzone": "^4.2.9",
"react-i18next": "^7.5.1",
"semantic-ui-css": "^2.3.1",
"semantic-ui-react": "^0.79.1"

View File

@ -15,11 +15,14 @@ const options = {
const sortByName = (a, b) =>
a.name.localeCompare(b.name);
const addHeader = (shouldAdd, to, description) => {
const addHeader = (shouldAdd, to, name, description) => {
if (shouldAdd) {
to.push(<Dropdown.Divider />);
if (to.length) {
to.push(<Dropdown.Divider key={`${name}:divider`} />);
}
to.push(
<Dropdown.Header>
<Dropdown.Header key={`${name}:header`}>
{description}
</Dropdown.Header>
);
@ -27,8 +30,8 @@ const addHeader = (shouldAdd, to, description) => {
};
extrinsics.sections.sort(sortByName).forEach(({ description, hasPrivate, hasPublic, methods, name }) => {
addHeader(hasPublic, options.public, description);
addHeader(hasPrivate, options.private, description);
addHeader(hasPublic, options.public, name, description);
addHeader(hasPrivate, options.private, name, description);
methods.sort(sortByName).forEach(({ description, name, params, isPrivate }) => {
const inputs = params.map(({ name }) => name).join(', ');
@ -36,10 +39,16 @@ extrinsics.sections.sort(sortByName).forEach(({ description, hasPrivate, hasPubl
options[isPrivate ? 'private' : 'public'].push({
className: 'ui--InputExtrinsic-Item',
text: [
<div className='ui--InputExtrinsic-Item-text' key='name'>
<div
className='ui--InputExtrinsic-Item-text'
key={`${name}:name`}
>
{description || name}
</div>,
<div className='ui--InputExtrinsic-Item-call' key='call'>
<div
className='ui--InputExtrinsic-Item-call'
key={`${name}:call`}
>
{name}({inputs})
</div>
],

View File

@ -1,34 +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$Param } from '@polkadot/extrinsics/types';
import type { BareProps, RawParam } from '../types';
import React from 'react';
import findComponent from './findComponent';
import typeToText from './typeToText';
type Props = BareProps & {
label?: string,
subject: rxjs$BehaviorSubject<RawParam>,
value: Extrinsic$Param
}
export default function Param ({ className, label, style, subject, value }: Props): React$Node {
// flowlint-next-line sketchy-null-string:off
const defaultLabel = label || `${value.name}: ${typeToText(value.type)}`;
const Component = findComponent(value.type);
return (
<Component
className={className}
label={defaultLabel}
style={style}
subject={subject}
value={value}
/>
);
}

View File

@ -3,12 +3,12 @@
// of the ISC license. See the LICENSE file for details.
// @flow
import type { Props } from './types';
import type { Props } from '../types';
import React from 'react';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import BaseAccount from '../Account';
import BaseAccount from '../../Account';
export default function Account ({ isError, label, subject }: Props): React$Node {
const account: rxjs$BehaviorSubject<Uint8Array> = new BehaviorSubject(new Uint8Array([]));

View File

@ -3,7 +3,7 @@
// of the ISC license. See the LICENSE file for details.
// @flow
import type { Props } from './types';
import type { Props } from '../types';
import BN from 'bn.js';
import React from 'react';

View File

@ -3,6 +3,8 @@
// of the ISC license. See the LICENSE file for details.
// @flow
import type { Size } from '../types';
import React from 'react';
import Label from 'semantic-ui-react/dist/es/elements/Label';
@ -10,7 +12,7 @@ type Props = {
className?: string,
children: React$Node,
label?: string,
size?: 'full' | 'large' | 'medium' | 'small',
size?: Size,
style?: {
[string]: string
}

View File

@ -3,7 +3,7 @@
// of the ISC license. See the LICENSE file for details.
// @flow
import type { Props } from './types';
import type { Props } from '../types';
import React from 'react';
import Dropdown from 'semantic-ui-react/dist/es/modules/Dropdown';

View File

@ -3,7 +3,7 @@
// of the ISC license. See the LICENSE file for details.
// @flow
import type { Props } from './types';
import type { Props as BaseProps, Size } from '../types';
import React from 'react';
import Input from 'semantic-ui-react/dist/es/elements/Input';
@ -11,7 +11,16 @@ import hexToU8a from '@polkadot/util/hex/toU8a';
import Base from './Base';
export default function Hash ({ isError, label, subject, t }: Props): React$Node {
type Props = BaseProps & {
length?: number,
size?: Size,
validate?: (u8a: Uint8Array) => boolean
}
const defaultValidate = (u8a: Uint8Array): boolean =>
true;
export default function Bytes ({ isError, length = -1, label, size = 'full', subject, validate = defaultValidate }: Props): React$Node {
// eslint-disable-next-line no-unused-vars
const onChange = (event: SyntheticEvent<*>, { value }) => {
let u8a;
@ -22,8 +31,12 @@ export default function Hash ({ isError, label, subject, t }: Props): React$Node
u8a = new Uint8Array([]);
}
const isValidLength = length !== -1
? u8a.length === length
: u8a.length !== 0;
subject.next({
isValid: u8a.length === 32,
isValid: isValidLength && validate(u8a),
value: u8a
});
};
@ -31,7 +44,7 @@ export default function Hash ({ isError, label, subject, t }: Props): React$Node
return (
<Base
label={label}
size='medium'
size={size}
>
<Input
error={isError}

View File

@ -0,0 +1,83 @@
// 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, RawParam } from '../../types';
import React from 'react';
import Dropzone from 'react-dropzone';
import withObservable from '@polkadot/rx-react/with/observable';
import translate from '../../translate';
import Base from './Base';
type Props = BareProps & {
isError?: boolean,
label: string,
onChange: (file?: File, contents?: Uint8Array) => void,
subject: rxjs$BehaviorSubject<RawParam>,
t: I18Next$Translate
}
type WrappedProps = Props & {
value: RawParam
}
function BytesFile ({ className, isError, label, onChange, subject, t }: Props): React$Node {
const onDrop = (files: Array<File>) => {
files.forEach((file) => {
const reader = new FileReader();
reader.onload = () =>
// flowlint-next-line unclear-type:off
onChange(file, new Uint8Array((((reader.result): any): ArrayBuffer)));
reader.onabort = () =>
onChange();
reader.onerror = () =>
onChange();
reader.readAsArrayBuffer(file);
});
};
const Component = withObservable(subject)(({ value: { data } = {} }: WrappedProps): React$Node => {
// flowlint-next-line sketchy-null-bool:off
const classes = ['extrinsics--Param-File', isError ? 'error' : '', !!data, className].join(' ');
return (
<Base
label={label}
size='full'
>
<Dropzone
className={classes}
multiple={false}
onDrop={onDrop}
>
<div className='label'>
{
!data
? t('file.dnd', {
defaultValue: 'drag and drop the file here'
})
: t('file.description', {
defaultValue: '{{name}} ({{size}} bytes)',
replace: {
name: data.name,
size: data.size
}
})
}
</div>
</Dropzone>
</Base>
);
});
return (
<Component />
);
}
export default translate(BytesFile);

View File

@ -0,0 +1,23 @@
// 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 { Props } from '../types';
import React from 'react';
import Bytes from './Bytes';
export default function Hash ({ isError, label, subject, value }: Props): React$Node {
return (
<Bytes
isError={isError}
label={label}
length={32}
size='medium'
subject={subject}
value={value}
/>
);
}

View File

@ -3,13 +3,13 @@
// of the ISC license. See the LICENSE file for details.
// @flow
import type { EncodedParams } from '../types';
import type { Props } from './types';
import type { EncodedParams } from '../../types';
import type { Props } from '../types';
import React from 'react';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import Extrinsic from '../Extrinsic';
import Extrinsic from '../../Extrinsic';
export default function Proposal ({ isError, label, subject }: Props): React$Node {
const extrinsic: rxjs$BehaviorSubject<EncodedParams> = new BehaviorSubject(({ isValid: false }: $Shape<EncodedParams>));

View File

@ -3,13 +3,13 @@
// of the ISC license. See the LICENSE file for details.
// @flow
import type { Props as BaseProps } from './types';
import type { Props as BaseProps } from '../types';
import React from 'react';
import translate from '../translate';
import translate from '../../translate';
import Base from './Base';
import typeToText from './typeToText';
import typeToText from '../typeToText';
type Props = BaseProps & {
t: I18Next$Translate

View File

@ -3,7 +3,7 @@
// of the ISC license. See the LICENSE file for details.
// @flow
import type { Props } from './types';
import type { Props } from '../types';
import React from 'react';
import Dropdown from 'semantic-ui-react/dist/es/modules/Dropdown';
@ -17,6 +17,8 @@ const options = [
];
export default function VoteThreshold ({ label, subject, t, value: { options: { initValue = 0 } = {} } }: Props): React$Node {
// flowlint-next-line sketchy-null-mixed:off
const defaultValue = initValue || 0;
const onChange = (event: SyntheticEvent<*>, { value }) =>
subject.next({
isValid: true,
@ -30,7 +32,7 @@ export default function VoteThreshold ({ label, subject, t, value: { options: {
>
<Dropdown
selection
defaultValue={initValue || 0}
defaultValue={defaultValue}
options={options}
onChange={onChange}
/>

View File

@ -0,0 +1,30 @@
// 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 { Props } from '../types';
import React from 'react';
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
});
};
return (
<BytesFile
isError={isError}
label={label}
onChange={onChange}
subject={subject}
/>
);
}

View File

@ -9,4 +9,29 @@
padding-left: 2em;
}
.extrinsics--Params-Param {}
.extrinsics--Param {}
.extrinsics--Param-File {
align-items: center;
border: 1px dashed rgba(34, 36, 38, 0.15);
border-radius: 0.28571429rem;
display: flex;
justify-content: center;
font-size: 1.2em;
height: 5em;
margin-bottom: 0.5rem;
width: 100% !important;
}
.extrinsics--Param-File.true {
border-style: solid;
}
.extrinsics--Params-File:hover {
background: rgba(200, 200, 200, 0.8);
cursor: pointer;
}
.extrinsics--Param-File .label {
color: rgba(0, 0, 0, .6);
}

View File

@ -5,27 +5,30 @@
import type { Extrinsic$Type } from '@polkadot/extrinsics/types';
import Account from './Account';
import Amount from './Amount';
import Bool from './Bool';
import Hash from './Hash';
import Proposal from './Proposal';
import Unknown from './Unknown';
import VoteThreshold from './VoteThreshold';
import Account from './Param/Account';
import Amount from './Param/Amount';
import Bool from './Param/Bool';
import Bytes from './Param/Bytes';
import Hash from './Param/Hash';
import Proposal from './Param/Proposal';
import Unknown from './Param/Unknown';
import VoteThreshold from './Param/VoteThreshold';
import Wasm from './Param/Wasm';
const Components = {
'AccountId': Account,
'Balance': Amount,
'BlockNumber': Amount,
'bool': Bool,
'Bytes': Unknown,
'Bytes': Bytes,
'Hash': Hash,
'MisbehaviorReport': Unknown,
'Proposal': Proposal,
'SessionKey': Unknown,
'u32': Amount,
'u64': Amount,
'VoteThreshold': VoteThreshold
'VoteThreshold': VoteThreshold,
'Wasm': Wasm
};
export default function findComponent (type: Extrinsic$Type): React$ComponentType<*> {

View File

@ -9,16 +9,10 @@ import type { BaseProps, RawParam } from '../types';
import './Params.css';
import React from 'react';
// import Label from 'semantic-ui-react/dist/es/elements/Label';
// <Label>
// {t('params.intro', {
// defaultValue: 'with the supplied parameters'
// })}
// </Label>
import translate from '../translate';
import Param from './Param';
import createSubscriber from './subscriber';
import findComponent from './findComponent';
import createSubjects from './subjects';
import typeToText from './typeToText';
type Props = BaseProps & {
@ -31,24 +25,27 @@ function Params ({ className, style, subject, value: { name, params } }: Props):
return null;
}
const subjects = createSubscriber(params, subject);
const subjects = createSubjects(params, subject);
return (
<div
className={['extrinsics--Params', 'extrinsics--split', className].join(' ')}
style={style}
>
<div className='full'>
<div className='extrinsics--Params-Content'>
{params.map((value, index) => (
<Param
className='extrinsics--Params-Param'
key={`${name}:${value.name}:${typeToText(value.type)}`}
value={value}
<div className='extrinsics--Params-Content'>
{params.map((value, index) => {
const Component = findComponent(value.type);
return (
<Component
className='extrinsics--Param'
key={`${name}:${value.name}:${index}`}
label={`${value.name}: ${typeToText(value.type)}`}
subject={subjects[index]}
value={value}
/>
))}
</div>
);
})}
</div>
</div>
);

View File

@ -9,7 +9,13 @@ import type { Extrinsic$Param } from '@polkadot/extrinsics/types';
import BN from 'bn.js';
export default function getInitValue ({ options: { initValue, minValue } = {}, type }: Extrinsic$Param): mixed {
switch (type) {
let inner = type;
if (Array.isArray(type)) {
inner = type[1];
}
switch (inner) {
case 'Balance':
case 'BlockNumber':
case 'u32':

View File

@ -11,8 +11,8 @@ import isUndefined from '@polkadot/util/is/undefined';
import getInitValue from './initValue';
export default function createSubjects (params: Extrinsic$Params): Array<rxjs$BehaviorSubject<RawParam>> {
return params.map((param) => {
export default function subjects (params: Extrinsic$Params, subject: rxjs$BehaviorSubject<Array<RawParam>>): Array<rxjs$BehaviorSubject<RawParam>> {
const subjects = params.map((param): rxjs$BehaviorSubject<RawParam> => {
const value = getInitValue(param);
return new BehaviorSubject({
@ -20,4 +20,16 @@ export default function createSubjects (params: Extrinsic$Params): Array<rxjs$Be
value
});
});
if (subject) {
const onChange = (): void => {
subject.next(
subjects.map((s) => s.getValue())
);
};
subjects.forEach((s) => s.subscribe(onChange));
}
return subjects;
}

View File

@ -1,23 +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$Params } from '@polkadot/extrinsics/types';
import type { RawParam } from '../types';
import createSubjects from './subjects';
export default function subscriber (params: Extrinsic$Params, subject: rxjs$BehaviorSubject<Array<RawParam>>): Array<rxjs$BehaviorSubject<RawParam>> {
const subjects = createSubjects(params);
if (subject) {
const onChange = (): void => {
subject.next(subjects.map((s) => s.getValue()));
};
subjects.forEach((s) => s.subscribe(onChange));
}
return subjects;
}

View File

@ -15,3 +15,5 @@ export type Props = BaseProps & {
isError?: boolean,
label: string
};
export type Size = 'full' | 'large' | 'medium' | 'small';

View File

@ -9,7 +9,6 @@ import React from 'react';
import { Trans } from 'react-i18next';
import Modal from 'semantic-ui-react/dist/es/modules/Modal';
import IdentityIcon from '@polkadot/ui-react/IdentityIcon';
import u8aToHex from '@polkadot/util/u8a/toHex';
import u8aToHexShort from '@polkadot/util/u8a/toHexShort';
import translate from '../translate';
@ -42,7 +41,7 @@ function Decoded ({ className, style, t, value: { data, extrinsic, index, public
defaultValue: 'The encoded parameters contains the data'
})}
</p>
<p className='code'>{u8aToHex(data)}</p>
<p className='code'>{u8aToHexShort(data, 512)}</p>
</div>
</div>
</Modal.Content>

View File

@ -10,6 +10,6 @@
}
.extrinsics--Submission-ButtonRow .button {
margin-right: 0.5rem;
text-align: left;
margin-left: 0.5rem;
}

View File

@ -12,41 +12,40 @@
}
.extrinsics--split {
display:flex;
flex-wrap:wrap;
flex-direction:row;
justify-content:flex-start;
align-items:stretch;
display: flex;
flex-wrap: wrap;
flex-direction: row;
justify-content: flex-start;
align-items: stretch;
}
.extrinsics--split > div {
box-sizing: border-box;
}
.extrinsics--split > div.full {
flex: 0 100%;
min-width: 0;
width: 100%;
}
.extrinsics--split > div.full,
.extrinsics--split > div.large,
.extrinsics--split > div.medium,
.extrinsics--split > div.small {
padding-right: 0.5rem;
}
.extrinsics--split > div.full {
flex: 0 100%;
}
.extrinsics--split > div.large {
flex: 0 75%;
min-width: 0;
}
.extrinsics--split > div.medium {
flex: 0 50%;
min-width: 0;
}
.extrinsics--split > div.small {
flex: 0 25%;
min-width: 0;
}
.hasUpdated {

View File

@ -18,6 +18,8 @@ export type BaseProps = BareProps & {
};
export type RawParam = {
// flowlint-next-line unclear-type:off
data?: any,
isValid: boolean,
value: mixed,
}

View File

@ -22,7 +22,7 @@ module.exports = ({
description: 'Set new code',
index: 0,
params: {
code: { type: 'Bytes' }
code: { type: 'Wasm' }
}
}
}

View File

@ -3,7 +3,7 @@
// of the ISC license. See the LICENSE file for details.
// @flow
export type Extrinsic$TypeName = 'AccountId' | 'Balance' | 'BlockNumber' | 'bool' | 'Bytes' | 'Hash' | 'MisbehaviorReport' | 'Proposal' | 'SessionKey' | 'u32' | 'u64' | 'VoteThreshold';
export type Extrinsic$TypeName = 'AccountId' | 'Balance' | 'BlockNumber' | 'bool' | 'Bytes' | 'Hash' | 'MisbehaviorReport' | 'Proposal' | 'SessionKey' | 'u32' | 'u64' | 'VoteThreshold' | 'Wasm';
export type Extrinsic$Type =
Extrinsic$TypeName |

View File

@ -1174,6 +1174,12 @@ atob@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.0.tgz#ab2b150e51d7b122b9efc8d7340c06b6c41076bc"
attr-accept@^1.0.3:
version "1.1.2"
resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-1.1.2.tgz#6836bfe054e4ef1ee3076fdde56cec9bb3ffead6"
dependencies:
core-js "^2.5.0"
autolinker@~0.15.0:
version "0.15.3"
resolved "https://registry.yarnpkg.com/autolinker/-/autolinker-0.15.3.tgz#342417d8f2f3461b14cf09088d5edf8791dc9832"
@ -8040,7 +8046,7 @@ promise@^7.1.1:
dependencies:
asap "~2.0.3"
prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.6.0:
prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.7, prop-types@^15.6.0:
version "15.6.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca"
dependencies:
@ -8227,6 +8233,13 @@ react-dom@^16.3.1:
object-assign "^4.1.1"
prop-types "^15.6.0"
react-dropzone@^4.2.9:
version "4.2.9"
resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-4.2.9.tgz#98156ce4d4cb018f7c5099eeb2eaff6dc21836fd"
dependencies:
attr-accept "^1.0.3"
prop-types "^15.5.7"
react-error-overlay@5.0.0-next.66cc7a90:
version "5.0.0-next.66cc7a90"
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-5.0.0-next.66cc7a90.tgz#68379b131ebe74112a12197504bfe7fa53119b3b"