Allow ABI removal on upload (#3869)

This commit is contained in:
Jaco Greeff 2020-10-14 09:15:13 +02:00 committed by GitHub
parent d8846f50b4
commit ee7a4de77d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 111 additions and 169 deletions

View File

@ -71,7 +71,6 @@ function Add ({ onClose }: Props): React.ReactElement {
isValid={isAbiValid}
onChange={onChangeAbi}
onRemove={onRemoveAbi}
withLabel
/>
</Modal.Content>
<Modal.Actions onCancel={onClose}>

View File

@ -9,8 +9,7 @@ import { Button, Card, Forget } from '@polkadot/react-components';
import { useApi, useCall, useToggle } from '@polkadot/react-hooks';
import { Option } from '@polkadot/types';
import { ABI, CodeRow } from '../shared';
import RemoveABI from '../RemoveABI';
import { CodeRow, Messages } from '../shared';
import store from '../store';
import useAbi from '../useAbi';
import { useTranslation } from '../translate';
@ -26,8 +25,7 @@ function Code ({ className, code, onShowDeploy }: Props): React.ReactElement<Pro
const { api } = useApi();
const optCode = useCall<Option<Codec>>(api.query.contracts.codeStorage, [code.json.codeHash]);
const [isForgetOpen, toggleIsForgetOpen] = useToggle();
const [isRemoveABIOpen, toggleIsRemoveABIOpen] = useToggle();
const { contractAbi, isAbiError, isAbiSupplied, isAbiValid, onChangeAbi, onRemoveAbi } = useAbi([code.json.abi, code.contractAbi], code.json.codeHash, true);
const { contractAbi } = useAbi([code.json.abi, code.contractAbi], code.json.codeHash, true);
const _onShowDeploy = useCallback(
() => onShowDeploy(code.json.codeHash, 0),
@ -78,27 +76,13 @@ function Code ({ className, code, onShowDeploy }: Props): React.ReactElement<Pro
</CodeRow>
</Forget>
)}
{isRemoveABIOpen && (
<RemoveABI
code={code}
key='modal-remove-abi'
onClose={toggleIsRemoveABIOpen}
onRemove={onRemoveAbi}
/>
)}
</Card>
</td>
<td className='all top'>
<ABI
<Messages
contractAbi={contractAbi}
isError={isAbiError}
isFull
isSupplied={isAbiSupplied}
isValid={isAbiValid}
onChange={onChangeAbi}
onRemove={toggleIsRemoveABIOpen}
onSelectConstructor={_onDeployConstructor}
withMessages={false}
withConstructors
/>
</td>
<td className='start together'>

View File

@ -97,7 +97,6 @@ function Upload ({ onClose }: Props): React.ReactElement {
isValid={isAbiValid}
onChange={onChangeAbi}
onRemove={onRemoveAbi}
withLabel
/>
</Modal.Content>
<Modal.Actions onCancel={onClose}>

View File

@ -92,12 +92,11 @@ function Add ({ onClose }: Props): React.ReactElement {
<ABI
contractAbi={contractAbi}
errorText={errorText}
isError={isAbiError}
isError={isAbiError || !isAbiValid}
isSupplied={isAbiSupplied}
isValid={isAbiValid}
onChange={onChangeAbi}
onRemove={onRemoveAbi}
withLabel
/>
</AddressRow>
</Modal.Content>

View File

@ -131,7 +131,6 @@ function Deploy ({ codeHash, constructorIndex = 0, onClose, setConstructorIndex
isValid={isAbiValid}
onChange={onChangeAbi}
onRemove={onRemoveAbi}
withLabel
/>
)}
{contractAbi && (

View File

@ -3,7 +3,7 @@
import React from 'react';
import { Abi } from '@polkadot/api-contract';
import { InputFile, Labelled } from '@polkadot/react-components';
import { IconLink, InputFile, Labelled } from '@polkadot/react-components';
import Messages from './Messages';
import { useTranslation } from '../translate';
@ -23,62 +23,54 @@ interface Props {
onRemove?: () => void;
onRemoved?: () => void;
onSelect?: () => void;
onSelectConstructor?: (constructorIndex?: number) => void;
onSelectConstructor?: (index?: number) => void;
withConstructors?: boolean;
withLabel?: boolean;
withMessages?: boolean;
}
function renderMessages ({ contractAbi, onRemove, onSelectConstructor, withConstructors = true, withLabel, withMessages = true }: Props): React.ReactNode {
return (
<Messages
contractAbi={contractAbi}
isLabelled={withLabel}
onRemove={onRemove}
onSelectConstructor={onSelectConstructor}
withConstructors={withConstructors}
withMessages={withMessages}
/>
);
}
const NOOP = (): void => undefined;
function ABI (props: Props): React.ReactElement<Props> {
const { className, contractAbi, errorText, isDisabled, isError, isFull, isValid, onChange, withLabel } = props;
function ABI ({ className, contractAbi, errorText, isDisabled, isError, isFull, isValid, onChange, onRemove = NOOP, onSelectConstructor, withConstructors = true, withLabel = true, withMessages = true }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const help = t('The ABI for the WASM code. The ABI is required and stored for future operations such as sending messages.');
const label = t('contract ABI');
return (
<div className={className}>
{
(contractAbi && isValid)
? (
withLabel
? (
<Labelled
help={help}
label={label}
>
{renderMessages(props)}
</Labelled>
)
: renderMessages(props)
)
: (
<InputFile
help={help}
isDisabled={isDisabled}
isError={isError}
isFull={isFull}
label={label}
onChange={onChange}
placeholder={errorText || t<string>('click to select or drag and drop a JSON ABI file')}
/>
)
}
</div>
);
return (contractAbi && isValid)
? (
<Labelled
className={className}
help={t<string>('This is the ABI as supplied. Any calls to the contract will use this information for encoding.')}
label={t<string>('contract ABI')}
labelExtra={onRemove && (
<IconLink
icon='trash'
label={t<string>('Remove ABI')}
onClick={onRemove}
/>
)}
withLabel={withLabel}
>
<Messages
contractAbi={contractAbi}
isLabelled={withLabel}
onSelectConstructor={onSelectConstructor}
withConstructors={withConstructors}
withMessages={withMessages}
/>
</Labelled>
)
: (
<div className={className}>
<InputFile
help={t<string>('The ABI for the WASM code. The ABI is required and stored for future operations such as sending messages.')}
isDisabled={isDisabled}
isError={isError}
isFull={isFull}
label={t<string>('contract ABI')}
onChange={onChange}
placeholder={errorText || t<string>('click to select or drag and drop a JSON ABI file')}
/>
</div>
);
}
export default React.memo(ABI);

View File

@ -8,7 +8,7 @@ import { ThemeProps } from '@polkadot/react-components/types';
import React, { useCallback, useEffect, useState } from 'react';
import styled from 'styled-components';
import { Abi, ContractPromise } from '@polkadot/api-contract';
import { Expander, IconLink } from '@polkadot/react-components';
import { Expander } from '@polkadot/react-components';
import { useApi, useCall } from '@polkadot/react-hooks';
import { Option } from '@polkadot/types';
@ -20,16 +20,13 @@ export interface Props {
contract?: ContractPromise;
contractAbi: Abi;
isLabelled?: boolean;
isRemovable?: boolean;
isWatching?: boolean;
onRemove?: () => void;
onSelect?: (messageIndex: number, resultCb: (messageIndex: number, result?: ContractCallOutcome) => void) => void;
onSelectConstructor?: (constructorIndex: number) => void;
withConstructors?: boolean;
withMessages?: boolean;
}
const NOOP = (): void => undefined;
const READ_ADDR = '0x'.padEnd(66, '0');
function sortMessages (messages: AbiMessage[]): [AbiMessage, number][] {
@ -44,7 +41,7 @@ function sortMessages (messages: AbiMessage[]): [AbiMessage, number][] {
);
}
function Messages ({ className = '', contract, contractAbi: { constructors, messages }, isLabelled, isRemovable, isWatching, onRemove = NOOP, onSelect, onSelectConstructor, withConstructors, withMessages } : Props): React.ReactElement<Props> {
function Messages ({ className = '', contract, contractAbi: { constructors, messages }, isLabelled, isWatching, onSelect, onSelectConstructor, withConstructors, withMessages } : Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const optInfo = useCall<Option<ContractInfo>>(contract && api.query.contracts.contractInfoOf, [contract?.address]);
@ -86,7 +83,7 @@ function Messages ({ className = '', contract, contractAbi: { constructors, mess
);
return (
<div className={`ui--Messages ${className} ${isLabelled ? 'labelled' : ''}`}>
<div className={`ui--Messages ${className}${isLabelled ? ' isLabelled' : ''}`}>
{withConstructors && (
<Expander summary={t<string>('Constructors ({{count}})', { replace: { count: constructors.length } })}>
{sortMessages(constructors).map(([message, index]) => (
@ -115,14 +112,6 @@ function Messages ({ className = '', contract, contractAbi: { constructors, mess
))}
</Expander>
)}
{isRemovable && (
<IconLink
className='remove-abi'
icon='remove'
label={t<string>('Remove ABI')}
onClick={onRemove}
/>
)}
</div>
);
}
@ -130,16 +119,7 @@ function Messages ({ className = '', contract, contractAbi: { constructors, mess
export default React.memo(styled(Messages)(({ theme }: ThemeProps) => `
padding-bottom: 0.75rem !important;
.remove-abi {
float: right;
margin-top: 0.5rem;
&:hover, &:hover :not(i) {
text-decoration: underline;
}
}
&.labelled {
&.isLabelled {
background: ${theme.bgInput};
box-sizing: border-box;
border: 1px solid rgba(34,36,38,.15);

View File

@ -38,10 +38,7 @@ export default function useAbi (initialValue: [string | null | undefined, Abi |
try {
setAbi([json, new Abi(json), true, true]);
codeHash && store.saveCode(
codeHash,
{ abi: json }
);
codeHash && store.saveCode(codeHash, { abi: json });
} catch (error) {
console.error(error);
@ -57,10 +54,7 @@ export default function useAbi (initialValue: [string | null | undefined, Abi |
setAbi([null, null, false, false]);
setError([false, null]);
codeHash && store.saveCode(
codeHash,
{ abi: null }
);
codeHash && store.saveCode(codeHash, { abi: null });
},
[codeHash]
);

View File

@ -66,46 +66,44 @@ export {
AddressRow
};
export default React.memo(
styled(AddressRow)`
button.u.ui--Icon.editButton {
padding: 0em .3em .3em .3em;
color: #2e86ab;
background: none;
/*trick to let the button in the flow but keep the content centered regardless*/
margin-left: -2em;
position: relative;
right: -2.3em;
z-index: 1;
export default React.memo(styled(AddressRow)`
button.u.ui--Icon.editButton {
padding: 0em .3em .3em .3em;
color: #2e86ab;
background: none;
/*trick to let the button in the flow but keep the content centered regardless*/
margin-left: -2em;
position: relative;
right: -2.3em;
z-index: 1;
}
.editSpan {
white-space: nowrap;
&:before {
content: '';
}
}
.editSpan {
white-space: nowrap;
.ui--AddressRow-balances {
display: flex;
.column {
display: block;
&:before {
content: '';
label,
.result {
display: inline-block;
vertical-align: middle;
}
}
.ui--AddressRow-balances {
display: flex;
.column {
display: block;
label,
.result {
display: inline-block;
vertical-align: middle;
}
}
> span {
text-align: left;
}
> span {
text-align: left;
}
}
.ui--AddressRow-placeholder {
opacity: 0.5;
}
`
);
.ui--AddressRow-placeholder {
opacity: 0.5;
}
`);

View File

@ -25,20 +25,14 @@ function IconLink ({ className = '', href, icon, label, onClick, rel, target }:
rel={rel}
target={target}
>
{icon && <Icon className={icon} />}
{icon && <Icon icon={icon} />}
{label}
</a>
);
}
export default React.memo(styled(IconLink)`
font-size: 0.9rem !important;
&:hover {
text-decoration: underline;
i {
text-decoration: none;
}
.ui--Icon {
margin-right: 0.5em;
}
`);

View File

@ -103,26 +103,30 @@ export default function useAccountInfo (value: string | null, isContract = false
useEffect((): void => {
if (value) {
const accountOrAddress = keyring.getAccount(value) || keyring.getAddress(value);
const isOwned = isAccount(value);
const isInContacts = isAddress(value);
try {
const accountOrAddress = keyring.getAccount(value) || keyring.getAddress(value);
const isOwned = isAccount(value);
const isInContacts = isAddress(value);
setGenesisHash(accountOrAddress?.meta.genesisHash || null);
setFlags((flags): AddressFlags => ({
...flags,
isDevelopment: accountOrAddress?.meta.isTesting || false,
isEditable: !!(!identity?.display && (isInContacts || accountOrAddress?.meta.isMultisig || (accountOrAddress && !(accountOrAddress.meta.isInjected || accountOrAddress.meta.isHardware)))) || false,
isExternal: !!accountOrAddress?.meta.isExternal || false,
isHardware: !!accountOrAddress?.meta.isHardware || false,
isInContacts,
isInjected: !!accountOrAddress?.meta.isInjected || false,
isMultisig: !!accountOrAddress?.meta.isMultisig || false,
isOwned,
isProxied: !!accountOrAddress?.meta.isProxied || false
}));
setMeta(accountOrAddress?.meta);
setName(accountOrAddress?.meta.name || '');
setSortedTags(accountOrAddress?.meta.tags ? (accountOrAddress.meta.tags as string[]).sort() : []);
setGenesisHash(accountOrAddress?.meta.genesisHash || null);
setFlags((flags): AddressFlags => ({
...flags,
isDevelopment: accountOrAddress?.meta.isTesting || false,
isEditable: !!(!identity?.display && (isInContacts || accountOrAddress?.meta.isMultisig || (accountOrAddress && !(accountOrAddress.meta.isInjected || accountOrAddress.meta.isHardware)))) || false,
isExternal: !!accountOrAddress?.meta.isExternal || false,
isHardware: !!accountOrAddress?.meta.isHardware || false,
isInContacts,
isInjected: !!accountOrAddress?.meta.isInjected || false,
isMultisig: !!accountOrAddress?.meta.isMultisig || false,
isOwned,
isProxied: !!accountOrAddress?.meta.isProxied || false
}));
setMeta(accountOrAddress?.meta);
setName(accountOrAddress?.meta.name || '');
setSortedTags(accountOrAddress?.meta.tags ? (accountOrAddress.meta.tags as string[]).sort() : []);
} catch (error) {
// ignore
}
}
}, [identity, isAccount, isAddress, value]);