UI support for v2 ABI (#1602)

* ui support for v2 ABI

* drop v1 support

* update ABI display

* changes

* buttons

* update ABI display

* ABI v1 error message

* fix key warning

* Bump deps

* Bump deps

* Updates for merge

* default gas and endowment
This commit is contained in:
kwingram25 2019-10-10 21:37:07 +02:00 committed by Jaco Greeff
parent 161eed0e60
commit 81c925f3d7
29 changed files with 1133 additions and 795 deletions

View File

@ -1,4 +1,4 @@
name: CI
name: PR
on: [pull_request]
jobs:

24
.github/workflows/push-master.yml vendored Normal file
View File

@ -0,0 +1,24 @@
name: Master
on:
push:
branches:
- master
jobs:
merge:
name: Merge
if: "! contains(github.event.head_commit.message, '[CI Skip]')"
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x]
steps:
- uses: actions/checkout@v1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: build
run: |
yarn install --frozen-lockfile
yarn build

View File

@ -10,10 +10,10 @@
"packages/*"
],
"resolutions": {
"@polkadot/api": "^0.95.0-beta.0",
"@polkadot/api-contract": "^0.95.0-beta.0",
"@polkadot/api": "^0.95.0-beta.2",
"@polkadot/api-contract": "^0.95.0-beta.2",
"@polkadot/keyring": "^1.5.1",
"@polkadot/types": "^0.95.0-beta.0",
"@polkadot/types": "^0.95.0-beta.2",
"@polkadot/util": "^1.5.1",
"@polkadot/util-crypto": "^1.5.1",
"@types/styled-components": "4.1.8",
@ -34,9 +34,9 @@
"devDependencies": {
"@babel/core": "^7.6.2",
"@babel/runtime": "^7.6.2",
"@polkadot/dev-react": "^0.32.0-beta.3",
"@polkadot/ts": "^0.1.77",
"autoprefixer": "^9.6.1",
"@polkadot/dev-react": "^0.32.0-beta.5",
"@polkadot/ts": "^0.1.79",
"autoprefixer": "^9.6.4",
"empty": "^0.10.1",
"gh-pages": "^2.1.1",
"html-loader": "^0.5.5",

View File

@ -12,12 +12,12 @@
"dependencies": {
"@babel/runtime": "^7.6.2",
"@polkadot/react-components": "^0.36.0-beta.68",
"@polkadot/react-qr": "^0.46.0-beta.5",
"@polkadot/react-qr": "^0.46.0-beta.6",
"@types/file-saver": "^2.0.0",
"@types/yargs": "^13.0.2",
"babel-plugin-module-resolver": "^3.1.1",
"detect-browser": "^4.7.0",
"file-saver": "^2.0.0",
"yargs": "^14.0.0"
"yargs": "^14.2.0"
}
}

View File

@ -11,7 +11,7 @@
"license": "Apache-2.0",
"dependencies": {
"@babel/runtime": "^7.6.2",
"@polkadot/api-contract": "^0.95.0-beta.0",
"@polkadot/api-contract": "^0.95.0-beta.2",
"@polkadot/react-components": "^0.36.0-beta.68"
}
}

View File

@ -15,6 +15,7 @@ import translate from './translate';
interface Props extends I18nProps {
className?: string;
contractAbi?: Abi | null;
errorText?: string | null;
help?: React.ReactNode;
isError?: boolean;
isDisabled?: boolean;
@ -24,10 +25,12 @@ interface Props extends I18nProps {
onRemove?: () => void;
onRemoved?: () => void;
onSelect?: () => void;
onSelectConstructor?: (constructorIndex?: number) => void;
}
interface State {
contractAbi: Abi | null;
errorText: string | null;
isAbiValid: boolean;
isEmpty: boolean;
isError: boolean;
@ -36,6 +39,7 @@ interface State {
class ABI extends React.PureComponent<Props, State> {
public state: State = {
contractAbi: null,
errorText: null,
isAbiValid: false,
isEmpty: true,
isError: false
@ -49,6 +53,7 @@ class ABI extends React.PureComponent<Props, State> {
this.state = {
contractAbi: contractAbi || null,
errorText: null,
isAbiValid,
isEmpty: !isAbiValid,
isError: isError || (isRequired && !isAbiValid) || false
@ -83,7 +88,7 @@ class ABI extends React.PureComponent<Props, State> {
private renderInputFile (): React.ReactNode {
const { className, help, isDisabled, isRequired, label, t } = this.props;
const { isAbiValid, isEmpty, isError } = this.state;
const { isAbiValid, isEmpty, isError, errorText } = this.state;
return (
<div className={className}>
@ -95,7 +100,17 @@ class ABI extends React.PureComponent<Props, State> {
onChange={this.onChange}
placeholder={
!isEmpty && !isAbiValid
? t('invalid ABI file selected')
? (
<>
{t('invalid ABI file selected')}
{!!errorText && (
<>
{' — '}
{t(errorText)}
</>
)}
</>
)
: t('click to select or drag and drop a JSON ABI file')
}
/>
@ -104,7 +119,7 @@ class ABI extends React.PureComponent<Props, State> {
}
private renderMessages (): React.ReactNode {
const { help, isDisabled, label, onRemove } = this.props;
const { help, isDisabled, label, onRemove, onSelectConstructor } = this.props;
const { contractAbi } = this.state;
if (!contractAbi) {
@ -122,17 +137,24 @@ class ABI extends React.PureComponent<Props, State> {
onRemove={onRemove || this.onRemove}
isLabelled={!!label}
isRemovable={!isDisabled}
onSelectConstructor={onSelectConstructor}
withConstructors
/>
</Labelled>
);
}
private onChange = (u8a: Uint8Array): void => {
const { onChange } = this.props;
const { onChange, t } = this.props;
const json = u8aToString(u8a);
try {
const contractAbi = new Abi(JSON.parse(json));
const abi = JSON.parse(json);
if (abi.deploy || abi.messages) {
throw new Error(t('You are using an ABI with an outdated format. Please generate a new one.'));
}
const contractAbi = new Abi(abi);
this.setState({
contractAbi,
@ -146,7 +168,8 @@ class ABI extends React.PureComponent<Props, State> {
this.setState({
isAbiValid: false,
isEmpty: false,
isError: true
isError: true,
errorText: error
}, (): void => onChange(null, null));
}
}

View File

@ -19,28 +19,41 @@ import translate from '../translate';
interface Props extends I18nProps, RouteComponentProps<{}> {
code: CodeStored;
showDeploy: (codeHash?: string) => () => void;
showDeploy: (codeHash?: string, constructorIndex?: number) => () => void;
}
interface State {
isAbiOpen: boolean;
isForgetOpen: boolean;
isRemoveABIOpen: boolean;
}
const CodeCard = styled(Card)`
&& {
min-height: 13rem;
max-width: 100%;
min-width: 100%;
}
`;
class Contract extends React.PureComponent<Props, State> {
class Code extends React.PureComponent<Props, State> {
public state: State = {
isAbiOpen: false,
isForgetOpen: false,
isRemoveABIOpen: false
};
public render (): React.ReactNode {
const { code, code: { contractAbi } } = this.props;
const { code, code: { contractAbi }, t } = this.props;
const { isAbiOpen } = this.state;
const abi = (
<ABI
contractAbi={contractAbi}
onChange={this.onChangeABI}
onRemove={this.toggleRemoveABI}
onSelectConstructor={this.onDeployConstructor}
/>
);
return (
<CodeCard>
@ -51,11 +64,18 @@ class Contract extends React.PureComponent<Props, State> {
isEditable
withTags
>
<ABI
contractAbi={contractAbi}
onChange={this.onChangeABI}
onRemove={this.toggleRemoveABI}
/>
{contractAbi
? (
<details
onClick={this.toggleAbi}
open={isAbiOpen}
>
<summary>{t('ABI')}</summary>
{abi}
</details>
)
: abi
}
</CodeRow>
</CodeCard>
);
@ -121,6 +141,17 @@ class Contract extends React.PureComponent<Props, State> {
return modals;
}
private toggleAbi = (event: React.MouseEvent): () => void => {
return (): void => {
event.preventDefault();
const { isAbiOpen } = this.state;
this.setState({
isAbiOpen: !isAbiOpen
});
};
}
private toggleForget = (): void => {
const { isForgetOpen } = this.state;
@ -137,6 +168,12 @@ class Contract extends React.PureComponent<Props, State> {
});
}
private onDeployConstructor = (constructorIndex = 0): void => {
const { code: { json: { codeHash } }, showDeploy } = this.props;
codeHash && showDeploy && showDeploy(codeHash, constructorIndex)();
}
private onForget = (): void => {
const { code: { json: { codeHash } } } = this.props;
@ -153,14 +190,19 @@ class Contract extends React.PureComponent<Props, State> {
}
}
private onChangeABI = async (abi: string | null = null): Promise<void> => {
private onChangeABI = (abi: string | null = null): void => {
const { code: { json: { codeHash } } } = this.props;
await contracts.saveCode(
codeHash,
{ abi }
this.setState(
{ isAbiOpen: true },
(): void => {
contracts.saveCode(
codeHash,
{ abi }
);
}
);
}
}
export default translate(withRouter(Contract));
export default translate(withRouter(Code));

View File

@ -15,6 +15,7 @@ import { compactAddLength } from '@polkadot/util';
import ContractModal, { ContractModalProps, ContractModalState } from '../Modal';
import store from '../store';
import translate from '../translate';
import { GAS_LIMIT } from '../constants';
interface Props extends ContractModalProps, ApiProps {}
@ -30,6 +31,7 @@ class Upload extends ContractModal<Props, State> {
this.defaultState = {
...this.defaultState,
gasLimit: new BN(GAS_LIMIT),
isWasmValid: false,
wasm: null
};

View File

@ -13,9 +13,11 @@ import { Abi } from '@polkadot/api-contract';
import { Button, Dropdown, InputAddress, InputBalance, InputNumber, Modal, TxButton, TxComponent } from '@polkadot/react-components';
import { getContractAbi } from '@polkadot/react-components/util';
import { withApi, withMulti } from '@polkadot/react-api';
import { displayType } from '@polkadot/types';
import translate from '../translate';
import Params from '../Params';
import { GAS_LIMIT } from '../constants';
interface Props extends BareProps, I18nProps, ApiProps, RouteComponentProps<{}> {
address: string | null;
@ -41,7 +43,7 @@ class Call extends TxComponent<Props, State> {
address: null,
accountId: null,
endowment: new BN(0),
gasLimit: new BN(0),
gasLimit: new BN(GAS_LIMIT),
method: null,
isAddressValid: false,
isBusy: false,
@ -107,7 +109,7 @@ class Call extends TxComponent<Props, State> {
const [address, contractAbi, method] = this.getCallProps();
const isEndowValid = true;
const isGasValid = !gasLimit.isZero();
const isGasValid = gasLimit.gtn(0);
if (!address || !contractAbi) {
return null;
@ -116,8 +118,8 @@ class Call extends TxComponent<Props, State> {
const methodOptions = contractAbi
? Object.keys(contractAbi.messages).map((key): { key: string; text: string; value: string } => {
const fn = contractAbi.messages[key];
const type = fn.type ? `: ${fn.type}` : '';
const args = fn.args.map(({ name, type }): string => `${name}: ${type}`);
const type = fn.type ? `: ${displayType(fn.type)}` : '';
const args = fn.args.map(({ name, type }): string => `${name}: ${displayType(type)}`);
const text = `${key}(${args.join(', ')})${type}`;
return {
@ -169,10 +171,12 @@ class Call extends TxComponent<Props, State> {
onChange={this.onChangeEndowment}
/>
<InputNumber
defaultValue={gasLimit}
help={t('The maximum amount of gas that can be used by this deployment, if the code requires more, the deployment will fail.')}
isError={!isGasValid}
label={t('maximum gas allowed')}
onChange={this.onChangeGas}
value={gasLimit}
onEnter={this.sendTx}
/>
</div>
@ -267,7 +271,7 @@ class Call extends TxComponent<Props, State> {
}
private onChangeGas = (gasLimit: BN | undefined): void => {
this.setState({ gasLimit: gasLimit || new BN(0) });
this.setState({ gasLimit: gasLimit || new BN(GAS_LIMIT) });
}
private onChangeMethod = (method: string | null): void => {

View File

@ -6,6 +6,7 @@ import { ActionStatus } from '@polkadot/react-components/Status/types';
import { I18nProps } from '@polkadot/react-components/types';
import React from 'react';
import styled from 'styled-components';
import { RouteComponentProps } from 'react-router';
import { withRouter } from 'react-router-dom';
import keyring from '@polkadot/ui-keyring';
@ -26,6 +27,13 @@ interface State {
isPasswordOpen: boolean;
}
const ContractCard = styled(Card)`
&& {
min-width: 100%;
max-width: 100%;
}
`;
class Contract extends React.PureComponent<Props, State> {
public state: State = {
isBackupOpen: false,
@ -34,7 +42,7 @@ class Contract extends React.PureComponent<Props, State> {
};
public render (): React.ReactNode {
const { address, onCall } = this.props;
const { address, onCall, t } = this.props;
const contractAbi = getContractAbi(address);
@ -43,7 +51,7 @@ class Contract extends React.PureComponent<Props, State> {
}
return (
<Card>
<ContractCard>
{this.renderModals()}
<AddressRow
buttons={this.renderButtons()}
@ -55,14 +63,17 @@ class Contract extends React.PureComponent<Props, State> {
withNonce={false}
withTags
>
<Messages
address={address}
contractAbi={contractAbi}
isRemovable={false}
onSelect={onCall}
/>
<details>
<summary>{t('Messages')}</summary>
<Messages
address={address}
contractAbi={contractAbi}
isRemovable={false}
onSelect={onCall}
/>
</details>
</AddressRow>
</Card>
</ContractCard>
);
}

View File

@ -9,6 +9,7 @@ import React from 'react';
import { RouteComponentProps } from 'react-router';
import { withRouter } from 'react-router-dom';
import { Button, CardGrid } from '@polkadot/react-components';
import { stringCamelCase } from '@polkadot/util';
import translate from '../translate';
import Add from './Add';
@ -104,7 +105,7 @@ class Contracts extends React.PureComponent<Props, State> {
this.setState({
isCallOpen: true,
callAddress: callAddress || null,
callMethod: callMethod || null
callMethod: callMethod ? stringCamelCase(callMethod) : null
});
}

View File

@ -3,7 +3,6 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { AccountId } from '@polkadot/types/interfaces';
import { TypeDef } from '@polkadot/types/types';
import { ApiProps } from '@polkadot/react-api/types';
import { I18nProps } from '@polkadot/react-components/types';
@ -16,22 +15,25 @@ import { Abi } from '@polkadot/api-contract';
import { withApi, withMulti } from '@polkadot/react-api';
import keyring from '@polkadot/ui-keyring';
import { Button, Dropdown, InputBalance, TxButton } from '@polkadot/react-components';
import { getTypeDef } from '@polkadot/types';
import createValues from '@polkadot/react-params/values';
import { displayType } from '@polkadot/types';
import ContractModal, { ContractModalProps, ContractModalState } from './Modal';
import Params from './Params';
import store from './store';
import translate from './translate';
import { GAS_LIMIT, ENDOWMENT } from './constants';
type ConstructOptions = { key: string; text: string; value: string }[];
interface Props extends ContractModalProps, ApiProps, I18nProps, RouteComponentProps {
codeHash?: string;
constructorIndex?: number;
}
interface State extends ContractModalState {
codeHash?: string;
constructorIndex: number;
constructOptions: ConstructOptions;
endowment: BN;
isHashValid: boolean;
@ -48,8 +50,10 @@ class Deploy extends ContractModal<Props, State> {
this.defaultState = {
...this.defaultState,
constructorIndex: -1,
constructOptions: [],
endowment: new BN(0),
endowment: new BN(ENDOWMENT),
gasLimit: new BN(GAS_LIMIT),
isHashValid: false,
params: [],
...Deploy.getCodeState(props.codeHash)
@ -59,44 +63,35 @@ class Deploy extends ContractModal<Props, State> {
public static getDerivedStateFromProps (props: Props, state: State): Pick<State, never> {
if (props.codeHash && (!state.codeHash || state.codeHash !== props.codeHash)) {
return Deploy.getCodeState(props.codeHash);
return Deploy.getCodeState(props.codeHash, Math.max(props.constructorIndex || 0));
}
return {};
}
private static getContractAbiState = (abi: string | null | undefined, contractAbi: Abi | null = null): Partial<State> => {
private static getContractAbiState = (abi: string | null | undefined, contractAbi: Abi | null = null, constructorIndex = 0): Partial<State> => {
if (contractAbi) {
const args = contractAbi.deploy.args.map(({ name, type }): string => `${name}: ${type}`);
const text = `deploy(${args.join(', ')})`;
return {
abi,
constructOptions: [{
key: 'deploy',
text,
value: 'deploy'
}],
constructorIndex,
contractAbi,
isAbiValid: !!contractAbi,
params: createValues(
contractAbi.deploy.args.map(({ name, type }): { type: TypeDef } => ({
type: getTypeDef(type, name)
}))
)
...Deploy.getConstructorState(contractAbi, Math.max(constructorIndex, 0))
};
} else {
return {
constructorIndex: -1,
constructOptions: [] as ConstructOptions,
abi: null,
contractAbi: null,
isAbiSupplied: false,
isAbiValid: false,
params: [] as unknown[]
...Deploy.getConstructorState()
};
}
}
private static getCodeState = (codeHash: string | null = null): Pick<State, never> => {
private static getCodeState = (codeHash: string | null = null, constructorIndex = 0): Pick<State, never> => {
if (codeHash) {
const code = store.getCode(codeHash);
@ -109,7 +104,7 @@ class Deploy extends ContractModal<Props, State> {
name: `${json.name} (instance)`,
isHashValid: true,
isNameValid: true,
...Deploy.getContractAbiState(json.abi, contractAbi)
...Deploy.getContractAbiState(json.abi, contractAbi, Math.max(constructorIndex, 0))
};
}
}
@ -117,9 +112,40 @@ class Deploy extends ContractModal<Props, State> {
return {};
}
private static getConstructorState = (contractAbi: Abi | null = null, ci = 0): Pick<State, never> => {
const constructorIndex = Math.max(ci, 0);
if (!contractAbi || constructorIndex < 0 || constructorIndex >= contractAbi.constructors.length) {
return {
constructorIndex: -1,
constructOptions: [],
params: []
};
}
const { abi: { contract: { constructors } } } = contractAbi;
const constructor = constructors[constructorIndex];
const constructOptions: ConstructOptions = constructors.map(
(constr) => {
const { name, args } = constr;
const textArgs = args.map(({ name, type }): string => `${name}: ${displayType(type)}`);
return {
key: `${constructorIndex}`,
text: `${name}(${textArgs.join(', ')})`,
value: `${constructorIndex}`
};
});
return {
constructorIndex,
constructOptions,
params: createValues(constructor.args)
};
}
protected renderContent = (): React.ReactNode => {
const { t } = this.props;
const { codeHash, constructOptions, contractAbi, endowment, isAbiSupplied, isBusy, isHashValid } = this.state;
const { codeHash, constructorIndex, constructOptions, contractAbi, endowment, isAbiSupplied, isBusy, isHashValid } = this.state;
const isEndowValid = !endowment.isZero();
const codeOptions = store.getAllCode().map(({ json: { codeHash, name } }): { text: string; value: string } => ({
@ -154,13 +180,14 @@ class Deploy extends ContractModal<Props, State> {
contractAbi
? (
<Dropdown
defaultValue='deploy'
defaultValue={`${constructorIndex}`}
help={t('The deployment constructor information for this contract, as provided by the ABI.')}
isDisabled
isDisabled={contractAbi.abi.contract.constructors.length <= 1}
label={t('constructor')}
onChange={this.onChangeConstructorIndex}
options={constructOptions}
style={{ fontFamily: 'monospace' }}
value='deploy'
value={`${constructorIndex}`}
/>
)
: null
@ -170,8 +197,8 @@ class Deploy extends ContractModal<Props, State> {
onChange={this.onChangeParams}
onEnter={this.sendTx}
params={
contractAbi
? contractAbi.deploy.args
contractAbi && constructorIndex >= 0
? contractAbi.abi.contract.constructors[constructorIndex].args
: []
}
/>
@ -224,13 +251,13 @@ class Deploy extends ContractModal<Props, State> {
}
private constructCall = (): any[] => {
const { codeHash, contractAbi, endowment, gasLimit, params } = this.state;
const { codeHash, constructorIndex, contractAbi, endowment, gasLimit, params } = this.state;
if (!contractAbi) {
if (!contractAbi || constructorIndex < 0) {
return [];
}
return [endowment, gasLimit, codeHash, contractAbi.deploy(...params)];
return [endowment, gasLimit, codeHash, contractAbi.constructors[constructorIndex](...params)];
}
protected onAddAbi = (abi: string | null | undefined, contractAbi?: Abi | null): void => {
@ -245,6 +272,15 @@ class Deploy extends ContractModal<Props, State> {
);
}
private onChangeConstructorIndex = (constructorIndexString: string): void => {
const { contractAbi } = this.state;
const constructorIndex = Math.max(0, parseInt(constructorIndexString, 10) || 0);
this.setState(
Deploy.getConstructorState(contractAbi, constructorIndex)
);
};
private onChangeEndowment = (endowment?: BN | null): void => {
this.setState({ endowment: endowment || new BN(0) });
}

View File

@ -8,7 +8,6 @@ import { RawParams } from '@polkadot/react-params/types';
import React, { useEffect, useState } from 'react';
import UIParams from '@polkadot/react-params';
import { getTypeDef } from '@polkadot/types';
interface Props {
isDisabled?: boolean;
@ -27,12 +26,7 @@ export default function Params ({ isDisabled, onChange, onEnter, params: propPar
useEffect((): void => {
if (propParams) {
setParams(
propParams.map(({ name, type }): ParamDef => ({
name,
type: getTypeDef(type, name)
}))
);
setParams(propParams);
}
}, [propParams]);

View File

@ -0,0 +1,6 @@
// Copyright 2017-2019 @polkadot/app-staking authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
export const ENDOWMENT = 1000;
export const GAS_LIMIT = 500000;

View File

@ -28,6 +28,7 @@ interface Props extends AppProps, I18nProps, RouteComponentProps {
interface State {
codeHash?: string;
constructorIndex: number;
hasContracts: boolean;
isDeployOpen: boolean;
updated: number;
@ -35,6 +36,7 @@ interface State {
class App extends React.PureComponent<Props, State> {
public state: State = {
constructorIndex: 0,
hasContracts: false,
isDeployOpen: false,
updated: 0
@ -62,7 +64,7 @@ class App extends React.PureComponent<Props, State> {
public render (): React.ReactNode {
const { basePath, t } = this.props;
const { codeHash, isDeployOpen } = this.state;
const { codeHash, constructorIndex, isDeployOpen } = this.state;
const hidden: string[] = [];
return (
@ -93,6 +95,7 @@ class App extends React.PureComponent<Props, State> {
<Deploy
basePath={basePath}
codeHash={codeHash}
constructorIndex={constructorIndex}
isOpen={isDeployOpen}
onClose={this.hideDeploy}
/>
@ -103,6 +106,7 @@ class App extends React.PureComponent<Props, State> {
private renderComponent (Component: React.ComponentType<ComponentProps>): () => React.ReactNode {
return (): React.ReactNode => {
const { accounts, basePath, contracts, onStatusChange } = this.props;
const { updated } = this.state;
if (!contracts) {
return null;
@ -116,15 +120,17 @@ class App extends React.PureComponent<Props, State> {
hasCode={store.hasCode}
onStatusChange={onStatusChange}
showDeploy={this.showDeploy}
updated={updated}
/>
);
};
}
private showDeploy = (codeHash?: string): () => void =>
private showDeploy = (codeHash?: string, constructorIndex = 0): () => void =>
(): void => {
this.setState({
codeHash: codeHash || undefined,
constructorIndex,
isDeployOpen: true
});
}

View File

@ -74,10 +74,11 @@ class Store extends EventEmitter {
private addCode (json: CodeJson): void {
try {
const abi = json.abi ? JSON.parse(json.abi) : null;
this.allCode[json.codeHash] = {
json,
contractAbi: json.abi
? new Abi(JSON.parse(json.abi))
contractAbi: abi
? new Abi(abi)
: undefined
};

View File

@ -12,7 +12,8 @@ export interface ComponentProps extends AppProps {
accounts: SubjectInfo[];
contracts: SubjectInfo[];
hasCode: boolean;
showDeploy: (codeHash?: string) => () => void;
showDeploy: (codeHash?: string, constructorIndex?: number) => () => void;
updated: number;
}
export interface CodeJson {

View File

@ -16,7 +16,7 @@
"@babel/runtime": "^7.6.2",
"@polkadot/react-components": "^0.36.0-beta.68",
"@polkadot/react-signer": "^0.36.0-beta.68",
"@polkadot/ui-assets": "^0.46.0-beta.5",
"@polkadot/ui-assets": "^0.46.0-beta.6",
"query-string": "^6.8.3"
}
}

View File

@ -31,8 +31,8 @@
"homepage": "https://github.com/polkadot-js/ui/tree/master/packages/ui-reactive#readme",
"dependencies": {
"@babel/runtime": "^7.6.2",
"@polkadot/api": "^0.95.0-beta.0",
"@polkadot/extension-dapp": "^0.13.0-beta.6",
"@polkadot/api": "^0.95.0-beta.2",
"@polkadot/extension-dapp": "^0.13.0-beta.7",
"edgeware-node-types": "^1.0.10",
"rxjs-compat": "^6.5.3"
}

View File

@ -13,31 +13,29 @@
"@babel/runtime": "^7.6.2",
"@polkadot/keyring": "^1.5.1",
"@polkadot/react-api": "^0.36.0-beta.68",
"@polkadot/react-identicon": "^0.46.0-beta.5",
"@polkadot/react-identicon": "^0.46.0-beta.6",
"@polkadot/react-query": "^0.36.0-beta.68",
"@polkadot/ui-keyring": "^0.46.0-beta.5",
"@polkadot/ui-settings": "^0.46.0-beta.5",
"@types/chart.js": "^2.8.5",
"@polkadot/ui-keyring": "^0.46.0-beta.6",
"@polkadot/ui-settings": "^0.46.0-beta.6",
"@types/chart.js": "^2.8.7",
"@types/i18next": "^13.0.0",
"@types/react-copy-to-clipboard": "^4.2.6",
"@types/react-copy-to-clipboard": "^4.3.0",
"@types/react-router-dom": "^5.1.0",
"@types/react-tooltip": "^3.9.3",
"@types/styled-components": "4.1.8",
"@types/styled-theming": "2.2.1",
"chart.js": "^2.8.0",
"codeflask": "^1.4.0",
"i18next": "^17.0.16",
"i18next-browser-languagedetector": "^3.0.3",
"i18next": "^17.0.18",
"i18next-browser-languagedetector": "^3.1.1",
"i18next-xhr-backend": "^3.2.0",
"react": "^16.8.2",
"react-chartjs-2": "^2.8.0",
"react-copy-to-clipboard": "^5.0.1",
"react-dom": "^16.8.2",
"react-dropzone": "^10.1.9",
"react-i18next": "^10.13.0",
"react-dropzone": "^10.1.10",
"react-i18next": "^10.13.1",
"react-markdown": "^4.2.2",
"react-router": "^5.1.0",
"react-router-dom": "^5.1.0",
"react-router": "^5.1.2",
"react-router-dom": "^5.1.2",
"react-tooltip": "^3.11.1",
"semantic-ui-css": "^2.3.1",
"semantic-ui-react": "^0.88.1",

View File

@ -231,7 +231,7 @@ function renderBalances (props: Props): React.ReactNode {
</div>
</>
)}
</>
</>
);
}

View File

@ -50,9 +50,9 @@ export default function BondedDisplay (props: Props): React.ReactElement<Props>
return bonded
? (
<>
{renderProvided(props)}
</>
<>
{renderProvided(props)}
</>
)
: (
<Bonded

View File

@ -115,6 +115,10 @@ const Wrapper = styled.div`
padding-left: 1.45rem;
padding-top: 2rem;
}
.ui--Messages {
padding-bottom: 2rem;
}
}
}
`;

View File

@ -2,13 +2,17 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { ContractABIMethod } from '@polkadot/api-contract/types';
import { I18nProps } from '@polkadot/react-components/types';
import { Button } from '@polkadot/react-components';
import React from 'react';
import styled from 'styled-components';
import { Abi } from '@polkadot/api-contract';
import { displayType } from '@polkadot/types';
import Icon from './Icon';
import Tooltip from './Tooltip';
import translate from './translate';
import { classes } from './util';
@ -19,65 +23,158 @@ export interface Props extends I18nProps {
isRemovable: boolean;
onRemove?: () => void;
onSelect?: (callAddress?: string, callMethod?: string) => void;
onSelectConstructor?: (index: number) => void;
withConstructors?: boolean;
}
const NOOP = (): void => {};
function onSelect (props: Props, index: number): () => void {
return function (): void {
const { address: callAddress, contractAbi: { abi: { messages } }, onSelect } = props;
const { address: callAddress, contractAbi: { abi: { contract: { messages } } }, onSelect } = props;
if (!callAddress || !messages || !messages[index]) {
return;
}
const { name: callMethod } = messages[index];
const { name } = messages[index];
onSelect && onSelect(callAddress, callMethod);
onSelect && onSelect(callAddress, name);
};
}
function onSelectConstructor (props: Props, index: number): () => void {
return function (): void {
const { contractAbi: { abi: { contract: { constructors } } }, onSelectConstructor } = props;
if (!constructors || !constructors[index]) {
return;
}
console.log(onSelectConstructor);
onSelectConstructor && onSelectConstructor(index);
};
}
function renderItem (props: Props, { args, docs = [], mutates, name, returnType }: ContractABIMethod, index: number, asConstructor = false): React.ReactNode {
return (
<div
key={name}
className={classes('message', !onSelect && 'exempt-hover', asConstructor && 'constructor')}
>
<div className="info">
<div className="signature">
<span className="name">
{name}
</span>
(
{args.map(({ name, type }, index): React.ReactNode => {
return (
<React.Fragment key={`${name}-args-${index}`}>
{name}:
{' '}
<span className='type'>
{displayType(type)}
</span>
{index < args.length - 1 && ', '}
</React.Fragment>
);
})}
)
{(!asConstructor && returnType) && (
<>
:
{' '}
<span className="return-type">
{displayType(returnType)}
</span>
</>
)}
{mutates && (
<>
<Icon
data-tip
data-for={`mutates-${name}`}
name="database"
className="mutates"
/>
<Tooltip
trigger={`mutates-${name}`}
text={props.t('Mutates contract state')}
/>
</>
)}
</div>
<details className="docs">
<summary>
{
docs
.filter((line) => line !== '')
.map((line, index) => ((
<React.Fragment key={`${name}-docs-${index}`}>
<span>{line}</span>
<br />
</React.Fragment>
)))
}
</summary>
</details>
</div>
{!asConstructor && props.onSelect && (
<div className="accessory">
<Button
className="execute"
icon="play"
onClick={onSelect(props, index)}
tooltip={props.t('Call this message')}
/>
</div>
)}
{asConstructor && props.onSelectConstructor && (
<div className="accessory">
<Button
className="execute"
icon="cloud upload"
onClick={onSelectConstructor(props, index)}
tooltip={props.t('Deploy with this constructor')}
/>
</div>
)}
</div>
);
}
function renderConstructor (props: Props, index: number): React.ReactNode {
const { contractAbi: { abi: { contract: { constructors } } } } = props;
if (!constructors[index]) {
return null;
}
return renderItem(props, constructors[index], index, true);
}
function renderMessage (props: Props, index: number): React.ReactNode {
const { contractAbi: { abi: { messages } }, onSelect: onSelectProp } = props;
const { contractAbi: { abi: { contract: { messages } } } } = props;
if (!messages[index]) {
return null;
}
const { args, name, return_type: returnType } = messages[index];
return (
<Button
key={name}
className={classes('message', !onSelect && 'exempt-hover')}
isDisabled={!onSelectProp}
onClick={onSelect(props, index)}
isPrimary={!!onSelectProp}
icon='info'
>
{name}
(
{args.map(({ name, type }): string => `${name}: ${type}`).join(', ')}
)
{returnType && `: ${returnType}`}
</Button>
);
return renderItem(props, messages[index], index);
}
function Messages (props: Props): React.ReactElement<Props> {
const { className, contractAbi: { abi: { messages } }, isLabelled, isRemovable, onRemove = NOOP, onSelect, t } = props;
const { className, contractAbi: { abi: { contract: { constructors, messages } } }, isLabelled, isRemovable, onRemove = NOOP, withConstructors } = props;
return (
<div className={classes(className, 'ui--Messages', isLabelled && 'labelled', onSelect && 'select')}>
<div className={classes(className, 'ui--Messages', isLabelled && 'labelled')}>
{withConstructors && constructors.map((_, index): React.ReactNode => renderConstructor(props, index))}
{messages.map((_, index): React.ReactNode => renderMessage(props, index))}
{isRemovable && (
<Button
className='iconButton'
icon='remove'
<a
className='remove-abi'
onClick={onRemove}
size='tiny'
isNegative
tooltip={t('Remove ABI')}
/>
>
<Icon name='remove' />
Remove ABI
</a>
)}
</div>
);
@ -85,13 +182,16 @@ function Messages (props: Props): React.ReactElement<Props> {
export default translate(styled(Messages)`
font-size: 0.9rem;
min-height: 3.5rem;
padding: 0;
margin: 0;
display: inline-flex;
justify-content: flex-start;
align-items: center;
flex-wrap: wrap;
.remove-abi {
float: right;
&:hover {
text-decoration: underline;
}
}
&.labelled {
background: white;
@ -102,22 +202,82 @@ export default translate(styled(Messages)`
width: 100%;
}
& > .message {
font-family: monospace;
font-weight: normal;
margin-bottom: 0;
margin-right: 0;
& .message {
width: calc(100% - 1rem);
background: #f8f8f8;
display: inline-flex;
margin-bottom: 0.5rem;
padding: 0.5rem;
margin: 0;
border-radius: 0.7rem;
transition: all 0.2s;
&.constructor {
background: #e8f4ff;
}
&:hover {
.accessory .execute {
display: block;
color: rgba(0, 0, 0, 0.2);
&:hover {
color: #2e86ab;
}
}
}
.info {
flex: 1 1;
.signature {
font-family: monospace;
font-weight: normal;
.name {
color: #2f8ddb;
font-weight: bold;
}
.type {
color: #21a2b2;
}
.return-type {
color: #ff8600;
}
}
.docs {
font-size: 0.8rem;
font-weight: normal;
}
.mutates {
color: #ff8600;
margin-left: 0.5rem;
opacity: 0.6;
}
}
.accessory {
width: 3rem;
display: flex;
align-items: center;
justify-content: center;
.execute {
display: none;
background: transparent;
font-size: 1.5rem;
margin: 0;
padding: 0;
}
}
&.disabled {
opacity: 1 !important;
background: #eee !important;
color: #555 !important;
}
&:not(:last-of-type) {
margin-right: 0.5rem;
}
}
`);

View File

@ -99,6 +99,10 @@ export default createGlobalStyle`
&[open] > summary {
white-space: normal;
br, br + * {
display: block;
}
}
> summary {
@ -106,6 +110,10 @@ export default createGlobalStyle`
overflow: hidden;
white-space: nowrap;
outline: none;
br, br + * {
display: none;
}
}
}

View File

@ -15,7 +15,8 @@ export default function getContractAbi (address: string | null): Abi | null {
const meta = getAddressMeta(address, 'contract');
try {
abi = meta.contract && new Abi(JSON.parse(meta.contract.abi));
const data = meta.contract && JSON.parse(meta.contract.abi);
abi = new Abi(data);
} catch (error) {
// invalid address, maybe
}

View File

@ -85,6 +85,9 @@ export default function findComponent (def: TypeDef, overrides: ComponentMap = {
return 'Struct';
case TypeDefInfo.Tuple:
if (components[type] === Account) {
return type;
}
return 'Tuple';
case TypeDefInfo.Vec:

View File

@ -12,6 +12,6 @@
"dependencies": {
"@babel/runtime": "^7.6.2",
"@polkadot/react-components": "^0.36.0-beta.68",
"@polkadot/react-qr": "^0.46.0-beta.5"
"@polkadot/react-qr": "^0.46.0-beta.6"
}
}

1289
yarn.lock

File diff suppressed because it is too large Load Diff