Update for v1.6.2
This commit is contained in:
Родитель
76ccf24724
Коммит
fefe1744b5
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -2,6 +2,16 @@
|
|||
|
||||
All notable changes to the "azure blockchain" extension will be documented in this file.
|
||||
|
||||
## 1.6.2
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Removed the Smart Contract Interaction options for contracts. A new version of this interaction will be implemented in a future release.
|
||||
|
||||
### Fixes
|
||||
|
||||
### Internal Improvements
|
||||
|
||||
## 1.6.1
|
||||
|
||||
### Enhancements
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"presets": [
|
||||
"@babel/preset-env",
|
||||
"@babel/preset-react"
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-syntax-bigint",
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/plugin-proposal-object-rest-spread",
|
||||
[
|
||||
"@babel/plugin-transform-runtime",
|
||||
{
|
||||
"regenerator": true
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
# EditorConfig is awesome: http://EditorConfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[**.*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
|
@ -1,90 +0,0 @@
|
|||
{
|
||||
"parser": "babel-eslint",
|
||||
"rules": {
|
||||
"indent": ["error", 2, { "SwitchCase": 1 }],
|
||||
"no-param-reassign": "error",
|
||||
"semi": 2,
|
||||
"sort-imports": [
|
||||
2,
|
||||
{
|
||||
"ignoreCase": true,
|
||||
"memberSyntaxSortOrder": ["single", "multiple", "all", "none"]
|
||||
}
|
||||
],
|
||||
"operator-linebreak": "off",
|
||||
"react/display-name": "off",
|
||||
"func-names": "off",
|
||||
"max-len": 0,
|
||||
"prefer-destructuring": 0,
|
||||
"arrow-parens": 0,
|
||||
"import/newline-after-import": 0,
|
||||
"no-prototype-builtins": 0,
|
||||
"lines-around-directive": 0,
|
||||
"no-unsafe-negation": 0,
|
||||
"import/first": 0,
|
||||
"import/order": 0,
|
||||
"import/no-unresolved": 0,
|
||||
"import/no-webpack-loader-syntax": 0,
|
||||
"prefer-spread": 0,
|
||||
"no-underscore-dangle": 0,
|
||||
"no-template-curly-in-string": 0,
|
||||
"class-methods-use-this": 0,
|
||||
"no-confusing-arrow": 0,
|
||||
"no-mixed-operators": 0,
|
||||
"no-undef-init": 0,
|
||||
"no-plusplus": 0,
|
||||
"linebreak-style": 0,
|
||||
"import/prefer-default-export": 0,
|
||||
"no-useless-escape": 0,
|
||||
"no-loop-func": 0,
|
||||
"import/no-extraneous-dependencies": 0,
|
||||
"import/extensions": 0,
|
||||
"no-lonely-if": 0,
|
||||
"comma-dangle": 0,
|
||||
"no-extra-boolean-cast": 0,
|
||||
"no-multi-assign": 0,
|
||||
"newline-per-chained-call": 0,
|
||||
"block-spacing": 0,
|
||||
"object-property-newline": 0,
|
||||
"no-useless-rename": 0,
|
||||
"no-restricted-syntax": 0,
|
||||
"no-useless-return": 0,
|
||||
"no-continue": 0,
|
||||
"no-console": 0,
|
||||
"function-paren-newline": 0,
|
||||
"object-curly-newline": 0,
|
||||
"arrow-body-style": 0,
|
||||
"react/react-in-jsx-scope": 0,
|
||||
"react/prop-types": 1,
|
||||
"react/sort-comp": 2,
|
||||
"no-await-in-loop": "off",
|
||||
"react/jsx-no-bind": [
|
||||
2,
|
||||
{
|
||||
"allowArrowFunctions": true,
|
||||
"allowBind": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"env": {
|
||||
"es6": true,
|
||||
"browser": true,
|
||||
"jest/globals": true
|
||||
},
|
||||
"globals": {
|
||||
"shallow": true,
|
||||
"mount": true,
|
||||
"BigInt": true
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
},
|
||||
"extends": [
|
||||
"airbnb/base",
|
||||
"plugin:react/recommended",
|
||||
"plugin:jest/recommended"
|
||||
],
|
||||
"plugins": ["react", "import", "jest"]
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
/node_modules/
|
||||
/.idea/
|
||||
/build/
|
||||
/dist/
|
||||
|
||||
package-lock.json
|
||||
.DS_Store
|
|
@ -1,57 +0,0 @@
|
|||
{
|
||||
"name": "client",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "cross-env NODE_ENV=production webpack --mode production",
|
||||
"build:dev": "cross-env NODE_ENV=development webpack-dev-server --open",
|
||||
"build:ext": "cross-env NODE_ENV=production EXT_ENV=true webpack --mode production",
|
||||
"build:ext:dev": "cross-env NODE_ENV=development EXT_ENV=true webpack --mode development",
|
||||
"lint": "eslint ./src --ext .js",
|
||||
"lint:fix": "npm run lint -- --fix"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.7.7",
|
||||
"@babel/plugin-proposal-class-properties": "^7.7.4",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.7.7",
|
||||
"@babel/plugin-syntax-bigint": "^7.7.4",
|
||||
"@babel/plugin-transform-runtime": "^7.7.6",
|
||||
"@babel/preset-env": "^7.7.7",
|
||||
"@babel/preset-react": "^7.7.4",
|
||||
"@babel/runtime": "^7.7.7",
|
||||
"babel-eslint": "^10.0.3",
|
||||
"babel-loader": "^8.0.6",
|
||||
"cross-env": "^6.0.3",
|
||||
"css-loader": "^3.4.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-airbnb": "^18.0.1",
|
||||
"eslint-loader": "^3.0.3",
|
||||
"eslint-plugin-import": "^2.19.1",
|
||||
"eslint-plugin-jest": "^23.2.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.2.3",
|
||||
"eslint-plugin-react": "^7.17.0",
|
||||
"eslint-plugin-react-hooks": "^2.3.0",
|
||||
"file-loader": "^5.0.2",
|
||||
"html-loader": "^0.5.5",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"less": "^3.10.3",
|
||||
"less-loader": "^5.0.0",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"string-replace-loader": "^2.2.0",
|
||||
"style-loader": "^1.1.2",
|
||||
"url-loader": "^3.0.0",
|
||||
"webpack": "^4.41.5",
|
||||
"webpack-cli": "^3.3.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"@drizzle/react-components": "^1.5.1",
|
||||
"@drizzle/react-plugin": "^1.5.1",
|
||||
"@drizzle/store": "^1.5.1",
|
||||
"@material-ui/core": "^4.8.2",
|
||||
"@material-ui/icons": "^4.5.1",
|
||||
"@truffle/hdwallet-provider": "^1.0.28",
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0"
|
||||
}
|
||||
}
|
|
@ -1,195 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { Constants } from './constants';
|
||||
import { Container } from '@material-ui/core';
|
||||
import { DrizzleContext } from '@drizzle/react-plugin';
|
||||
import HDWalletProvider from '@truffle/hdwallet-provider';
|
||||
import { IPC } from 'services';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { SingleContractView } from 'views';
|
||||
import { Url } from 'helpers/url';
|
||||
import {
|
||||
ContractSelector,
|
||||
Message
|
||||
} from 'components';
|
||||
import {
|
||||
createMuiTheme,
|
||||
MuiThemeProvider
|
||||
} from '@material-ui/core/styles';
|
||||
import './app.less';
|
||||
|
||||
class App extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
updating: false,
|
||||
contractInstances: [],
|
||||
selectedContractId: undefined,
|
||||
theme: createMuiTheme({
|
||||
palette: {
|
||||
type: 'light'
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
this.target = document.querySelector('body');
|
||||
this.observer = new MutationObserver(this.updateTheme);
|
||||
|
||||
this.observer.observe(this.target, { attributes: true, childList: false });
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
IPC.on('contracts', this.updateContracts);
|
||||
IPC.postMessage('documentReady', true);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const {
|
||||
updating,
|
||||
contractInstances,
|
||||
selectedContractId
|
||||
} = this.state;
|
||||
|
||||
if (
|
||||
!updating
|
||||
&& !selectedContractId
|
||||
&& contractInstances.length !== 0
|
||||
) {
|
||||
const contractInstance = contractInstances[0];
|
||||
this.setContract(contractInstance);
|
||||
}
|
||||
}
|
||||
|
||||
updateContracts = contractInstances => this.setState({
|
||||
contractInstances: contractInstances.reverse()
|
||||
});
|
||||
|
||||
onContractChange = (e) => {
|
||||
const { value } = e.target;
|
||||
const { contractInstances } = this.state;
|
||||
|
||||
this.setContract(contractInstances.find(instance => instance.id === value));
|
||||
}
|
||||
|
||||
setContract = async contractInstance => {
|
||||
// Workaround. Used to prevent drizzle fallback actions.
|
||||
window.showErrors = false;
|
||||
this.setState({ updating: true });
|
||||
|
||||
const { drizzle } = this.props;
|
||||
const { contract, provider } = contractInstance;
|
||||
|
||||
drizzle.deleteAllContracts();
|
||||
|
||||
if (provider) {
|
||||
const { WebsocketProvider } = drizzle.web3.providers;
|
||||
const host = new URL(Url.normalize(provider.host));
|
||||
|
||||
let web3Provider;
|
||||
|
||||
if (
|
||||
Constants.regexps.providerTypes.azure.test(host.toString()) &&
|
||||
provider.options &&
|
||||
provider.options.mnemonic
|
||||
) {
|
||||
host.protocol = 'wss';
|
||||
host.port = 3300;
|
||||
|
||||
web3Provider = new HDWalletProvider(
|
||||
provider.options.mnemonic,
|
||||
new WebsocketProvider(host.toString()),
|
||||
);
|
||||
} else {
|
||||
host.protocol = 'ws';
|
||||
web3Provider = new WebsocketProvider(host.toString());
|
||||
}
|
||||
|
||||
drizzle.web3.setProvider(web3Provider);
|
||||
}
|
||||
|
||||
const address = contractInstance.address;
|
||||
const networkName = contractInstance.network.name;
|
||||
const accounts = await drizzle.web3.eth.getAccounts();
|
||||
|
||||
const contractConfig = {
|
||||
contractName: contract.contractName,
|
||||
web3Contract: new drizzle.web3.eth.Contract(
|
||||
contract.abi,
|
||||
address,
|
||||
{
|
||||
from: accounts[0],
|
||||
data: contract.bytecode,
|
||||
networks: contract.networks,
|
||||
isOnline: !!provider,
|
||||
networkName,
|
||||
enumsInfo: contractInstance.enumsInfo,
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
drizzle.addContract(contractConfig);
|
||||
|
||||
// Workaround. Used to prevent drizzle fallback actions.
|
||||
setTimeout(() => { window.showErrors = true; }, 2000);
|
||||
|
||||
this.setState({
|
||||
selectedContractId: contractInstance.id,
|
||||
updating: false
|
||||
});
|
||||
}
|
||||
|
||||
updateTheme = () => {
|
||||
const type = Array
|
||||
.from(this.target.classList)
|
||||
.includes('vscode-dark') ? 'dark' : 'light';
|
||||
|
||||
if (this.state.theme.palette.type !== type) {
|
||||
this.setState({
|
||||
theme: createMuiTheme({ palette: { type } })
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { contractInstances, selectedContractId, theme } = this.state;
|
||||
const { drizzle: { contractList }, initialized } = this.props;
|
||||
|
||||
if (!initialized) {
|
||||
return <Container className='message container'>
|
||||
<Message message='⚙️ Loading dapp...' />
|
||||
</Container>;
|
||||
}
|
||||
|
||||
if (!contractList || contractList.length === 0) {
|
||||
return <Container className='message container'>
|
||||
<Message message='⚠️ No contracts available' />
|
||||
</Container>;
|
||||
}
|
||||
|
||||
return <MuiThemeProvider theme={theme}>
|
||||
<Container>
|
||||
<ContractSelector
|
||||
selectedContractId={selectedContractId}
|
||||
contractInstances={contractInstances}
|
||||
onChange={this.onContractChange}
|
||||
/>
|
||||
<SingleContractView />
|
||||
</Container>
|
||||
</MuiThemeProvider>;
|
||||
}
|
||||
}
|
||||
|
||||
const HOC = () => <DrizzleContext.Consumer>
|
||||
{props => <App {...props} />}
|
||||
</DrizzleContext.Consumer>;
|
||||
|
||||
export default HOC;
|
||||
|
||||
App.propTypes = {
|
||||
drizzle: PropTypes.any,
|
||||
drizzleState: PropTypes.any,
|
||||
initialized: PropTypes.bool.isRequired,
|
||||
};
|
|
@ -1,50 +0,0 @@
|
|||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body,
|
||||
html {
|
||||
position: relative;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
margin: 0;
|
||||
|
||||
font-family: sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.vscode-dark {
|
||||
background-color: #292929;
|
||||
|
||||
.MuiChip-outlinedSecondary {
|
||||
color: #fff;
|
||||
border: 1px solid #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.vscode-light {
|
||||
background-color: #fdfdfd;
|
||||
}
|
||||
|
||||
#root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
color: #d4d4d4;
|
||||
|
||||
.paper {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.message.container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {
|
||||
Paper,
|
||||
Typography
|
||||
} from '@material-ui/core';
|
||||
|
||||
class BaseViewComponent extends React.PureComponent {
|
||||
render() {
|
||||
const {
|
||||
header,
|
||||
children,
|
||||
className
|
||||
} = this.props;
|
||||
|
||||
return <Paper className={`paper ${className || ''}`}>
|
||||
<Typography
|
||||
variant='h5'
|
||||
component='h3'
|
||||
align='left'
|
||||
>
|
||||
{header}
|
||||
</Typography>
|
||||
{children}
|
||||
</Paper>;
|
||||
}
|
||||
}
|
||||
|
||||
export default BaseViewComponent;
|
||||
|
||||
BaseViewComponent.propTypes = {
|
||||
header: PropTypes.string.isRequired,
|
||||
className: PropTypes.string,
|
||||
children: PropTypes.any
|
||||
};
|
|
@ -1,65 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { BaseViewComponent } from 'components';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {
|
||||
Container,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
Select
|
||||
} from '@material-ui/core';
|
||||
import './contractSelector.less';
|
||||
|
||||
class ContractSelector extends React.Component {
|
||||
renderOption = (contractInstance, index) => {
|
||||
const {
|
||||
id,
|
||||
updateDate
|
||||
} = contractInstance;
|
||||
|
||||
const itemName = (new Date(updateDate)).toGMTString();
|
||||
|
||||
return <MenuItem
|
||||
key={index}
|
||||
value={id}
|
||||
>
|
||||
{itemName}
|
||||
</MenuItem>;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
selectedContractId = '',
|
||||
contractInstances = [],
|
||||
onChange
|
||||
} = this.props;
|
||||
|
||||
return <BaseViewComponent
|
||||
className='selector component'
|
||||
header='Select a contract version'
|
||||
>
|
||||
<Container className='container'>
|
||||
<InputLabel htmlFor='contract-selector'>
|
||||
Contract deployment date
|
||||
</InputLabel>
|
||||
<Select
|
||||
id='contract-selector'
|
||||
value={selectedContractId}
|
||||
onChange={onChange}
|
||||
>
|
||||
{contractInstances.map(this.renderOption)}
|
||||
</Select>
|
||||
</Container>
|
||||
</BaseViewComponent>;
|
||||
}
|
||||
}
|
||||
|
||||
export default ContractSelector;
|
||||
|
||||
ContractSelector.propTypes = {
|
||||
selectedContractId: PropTypes.string,
|
||||
contractInstances: PropTypes.array,
|
||||
onChange: PropTypes.func.isRequired
|
||||
};
|
|
@ -1,24 +0,0 @@
|
|||
.selector.component {
|
||||
@media (min-width: 600px) {
|
||||
margin: 15px 24px 0 24px;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
margin: 15px 32px 0 32px;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
color: white;
|
||||
|
||||
.MuiFormLabel-root {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.MuiSelect-select {
|
||||
min-width: 180px;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { TextField } from '@material-ui/core';
|
||||
|
||||
class EventSection extends React.Component {
|
||||
render() {
|
||||
const { events } = this.props;
|
||||
|
||||
const eventsNotifications = events
|
||||
.reverse()
|
||||
.map(({ event }) => `${event} was called`)
|
||||
.join('\n');
|
||||
|
||||
return <TextField
|
||||
className='metadata text field'
|
||||
disabled
|
||||
multiline
|
||||
fullWidth
|
||||
rows={8}
|
||||
id='contract-address'
|
||||
label='event:'
|
||||
value={eventsNotifications}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
export default EventSection;
|
||||
|
||||
EventSection.propTypes = {
|
||||
events: PropTypes.array.isRequired
|
||||
};
|
|
@ -1,160 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { createInputComponent } from '../factory/InputComponentFactory';
|
||||
import { newContextComponents } from '@drizzle/react-components';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {
|
||||
Button,
|
||||
Container,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
Select
|
||||
} from '@material-ui/core';
|
||||
import './executionSection.less';
|
||||
|
||||
const { ContractForm } = newContextComponents;
|
||||
|
||||
class ExecutionSection extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
executedMethod: '',
|
||||
disabledExecute: false,
|
||||
transactionPending: false,
|
||||
formControlsValidation: {},
|
||||
unsubscribe: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { actions, drizzle } = this.props;
|
||||
|
||||
const unsubscribe = drizzle.store.subscribe(this.updateTransactionStatus);
|
||||
const formControlsValidation = this.createFormControlsList(actions[0]);
|
||||
|
||||
this.setState({
|
||||
executedMethod: actions[0],
|
||||
formControlsValidation,
|
||||
unsubscribe,
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate = () => {
|
||||
const isUnsupportedInputExist = !!document.getElementsByClassName('unsupported-input').length;
|
||||
const validationValues = Object.values(this.state.formControlsValidation);
|
||||
const errors = validationValues.filter(value => value !== '' && value !== undefined);
|
||||
const isDisabled = isUnsupportedInputExist || errors.length > 0;
|
||||
if (isDisabled !== this.state.disabledExecute) {
|
||||
this.setState({ disabledExecute: isDisabled });
|
||||
}
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
this.state.unsubscribe();
|
||||
}
|
||||
|
||||
updateTransactionStatus = () => {
|
||||
const { store } = this.props.drizzle;
|
||||
|
||||
const { transactionStack, transactions } = store.getState();
|
||||
|
||||
const lastTransaction = transactionStack[transactionStack.length - 1];
|
||||
|
||||
if (!!lastTransaction && !!transactions[lastTransaction]) {
|
||||
const transactionPending = transactions[lastTransaction].status !== 'success';
|
||||
|
||||
if (this.state.transactionPending !== transactionPending) {
|
||||
this.setState({
|
||||
transactionPending,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createFormControlsList = (executedMethod) => {
|
||||
const method = this.props.drizzle.contracts[this.props.contractName].abi.find(
|
||||
element => !element.constant && element.name === executedMethod);
|
||||
const formControlsValidation = {};
|
||||
method.inputs.forEach(input => {
|
||||
formControlsValidation[input.name] = '';
|
||||
});
|
||||
return formControlsValidation;
|
||||
};
|
||||
|
||||
onChange = (e) => {
|
||||
const formControlsValidation = this.createFormControlsList(e.target.value);
|
||||
this.setState({
|
||||
executedMethod: e.target.value,
|
||||
formControlsValidation,
|
||||
});
|
||||
};
|
||||
|
||||
updateValidationResult = (name, errorMessage) => {
|
||||
this.setState({ formControlsValidation: { ...this.state.formControlsValidation, [name]: errorMessage } });
|
||||
}
|
||||
|
||||
renderExecutionSection = ({ inputs, handleInputChange, handleSubmit }) => {
|
||||
return <Container className='execution-section'>
|
||||
<Container className='action'>
|
||||
<InputLabel className='input-label'>Contract Action</InputLabel>
|
||||
<Select
|
||||
className='execution-method'
|
||||
value={this.state.executedMethod}
|
||||
onChange={this.onChange}
|
||||
inputProps={{
|
||||
id: 'action-method',
|
||||
name: 'action'
|
||||
}}
|
||||
>
|
||||
{this.props.actions.map((action, index) => {
|
||||
return (
|
||||
<MenuItem key={index} value={action}>{action}</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</Container>
|
||||
<Container className='input-fields'>
|
||||
{inputs.map((item, index) => {
|
||||
return createInputComponent(
|
||||
item,
|
||||
handleInputChange,
|
||||
this.updateValidationResult.bind(this, item.name),
|
||||
index,
|
||||
{
|
||||
executedMethod: this.state.executedMethod,
|
||||
enumsInfo: this.props.drizzle.contracts[this.props.contractName].options.enumsInfo,
|
||||
});
|
||||
})}
|
||||
</Container>
|
||||
<Button
|
||||
className='execute-button'
|
||||
variant='contained'
|
||||
onClick={handleSubmit}
|
||||
disabled={this.state.disabledExecute || this.state.transactionPending}
|
||||
>
|
||||
Execute
|
||||
</Button>
|
||||
</Container>;
|
||||
};
|
||||
|
||||
render() {
|
||||
return <ContractForm
|
||||
key={this.state.executedMethod}
|
||||
contract={this.props.contractName}
|
||||
method={this.state.executedMethod}
|
||||
drizzle={this.props.drizzle}
|
||||
render={this.renderExecutionSection}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
export default ExecutionSection;
|
||||
|
||||
ExecutionSection.propTypes = {
|
||||
actions: PropTypes.any.isRequired,
|
||||
contractName: PropTypes.string.isRequired,
|
||||
drizzle: PropTypes.any.isRequired,
|
||||
};
|
|
@ -1,49 +0,0 @@
|
|||
.execution-section {
|
||||
&.MuiContainer-root {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
padding: 0;
|
||||
|
||||
.input-label {
|
||||
padding-left: 0;
|
||||
flex: initial;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.execution-method {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.input-fields {
|
||||
padding: 0;
|
||||
|
||||
.input-field {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.MuiTextField-root {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.boolean-input,
|
||||
.enum-select {
|
||||
margin-top: 16px;
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
||||
.unsupported-input {
|
||||
margin-top: 16px;
|
||||
padding-left: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.execute-button {
|
||||
margin-top: 24px;
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { Constants } from 'constants';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { TextField } from '@material-ui/core';
|
||||
const { placeholder, validationMessages, validationRegexps } = Constants.executionSection;
|
||||
|
||||
class AddressInput extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
value: '',
|
||||
errorMessage: '',
|
||||
};
|
||||
}
|
||||
|
||||
onChange = (event) => {
|
||||
const { value } = event.target;
|
||||
const errorMessage = this.getErrorMessage(value);
|
||||
this.setState({ value, errorMessage });
|
||||
this.props.updateValidationResult(errorMessage);
|
||||
this.props.handleInputChange(event);
|
||||
};
|
||||
|
||||
getErrorMessage = (value) => {
|
||||
if (value && !value.match(validationRegexps.address)) {
|
||||
return validationMessages.address(this.props.item.name);
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
render() {
|
||||
const { name } = this.props.item;
|
||||
|
||||
return <TextField
|
||||
error={!!this.state.errorMessage}
|
||||
helperText={this.state.errorMessage}
|
||||
className='address-input'
|
||||
label={`${name}:`}
|
||||
name={name}
|
||||
onChange={this.onChange}
|
||||
value={this.state.value}
|
||||
placeholder={placeholder.address}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
export default AddressInput;
|
||||
|
||||
AddressInput.propTypes = {
|
||||
item: PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
}),
|
||||
handleInputChange: PropTypes.func.isRequired,
|
||||
updateValidationResult: PropTypes.func.isRequired,
|
||||
};
|
|
@ -1,72 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { TextField } from '@material-ui/core';
|
||||
|
||||
class ArrayInput extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const value = '';
|
||||
const errorMessage = this.getErrorMessage(value);
|
||||
this.props.updateValidationResult(errorMessage);
|
||||
|
||||
this.state = {
|
||||
value,
|
||||
errorMessage,
|
||||
};
|
||||
}
|
||||
|
||||
onChange = (event) => {
|
||||
const errorMessage = this.getErrorMessage(event.target.value);
|
||||
this.props.updateValidationResult(errorMessage);
|
||||
this.setState({ value: event.target.value, errorMessage });
|
||||
this.props.handleInputChange({
|
||||
target: {
|
||||
name: this.props.item.name,
|
||||
type: this.props.item.type,
|
||||
value: !errorMessage
|
||||
? JSON.parse(event.target.value)
|
||||
: undefined,
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
getErrorMessage = (value) => {
|
||||
try {
|
||||
JSON.parse(value);
|
||||
return '';
|
||||
} catch {
|
||||
return 'Should be a valid JSON';
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return <TextField
|
||||
multiline
|
||||
fullWidth
|
||||
rowsMax={10}
|
||||
error={!!this.state.errorMessage}
|
||||
helperText={this.state.errorMessage}
|
||||
placeholder={this.props.item.type}
|
||||
id={this.props.item.name}
|
||||
label={`${this.props.item.name} :`}
|
||||
name={this.props.item.name}
|
||||
onChange={this.onChange}
|
||||
value={this.state.value}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
export default ArrayInput;
|
||||
|
||||
ArrayInput.propTypes = {
|
||||
item: PropTypes.shape({
|
||||
type: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
handleInputChange: PropTypes.func.isRequired,
|
||||
updateValidationResult: PropTypes.func.isRequired,
|
||||
};
|
|
@ -1,49 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {
|
||||
Container,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
Select
|
||||
} from '@material-ui/core';
|
||||
|
||||
class BooleanInput extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = { value: false };
|
||||
}
|
||||
|
||||
onChange = (event) => {
|
||||
this.setState({ value: event.target.value });
|
||||
this.props.handleInputChange(event);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Container className='boolean-input'>
|
||||
<InputLabel shrink>
|
||||
{this.props.item.name}
|
||||
</InputLabel>
|
||||
<Select
|
||||
id={this.props.item.name}
|
||||
name={this.props.item.name}
|
||||
onChange={this.onChange}
|
||||
value={this.state.value}
|
||||
displayEmpty
|
||||
>
|
||||
<MenuItem key='false' value={false}>False</MenuItem>
|
||||
<MenuItem key='true' value={true}>True</MenuItem>
|
||||
</Select>
|
||||
</Container>;
|
||||
}
|
||||
}
|
||||
|
||||
export default BooleanInput;
|
||||
|
||||
BooleanInput.propTypes = {
|
||||
item: PropTypes.any.isRequired,
|
||||
handleInputChange: PropTypes.func.isRequired,
|
||||
};
|
|
@ -1,62 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {
|
||||
Container,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
Select
|
||||
} from '@material-ui/core';
|
||||
|
||||
class EnumInput extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = { value: undefined };
|
||||
this.onChange = this.onChange.bind(this);
|
||||
}
|
||||
|
||||
onChange(event) {
|
||||
this.setState({ value: event.target.value });
|
||||
this.props.handleInputChange({
|
||||
target: {
|
||||
name: this.props.title,
|
||||
value: event.target.value
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Container className='enum-select'>
|
||||
<InputLabel shrink>
|
||||
{this.props.title}
|
||||
</InputLabel>
|
||||
<Select
|
||||
id={this.props.title}
|
||||
name={this.props.title}
|
||||
onChange={this.onChange}
|
||||
value={this.state.value}
|
||||
inputProps={{
|
||||
id: 'action-method',
|
||||
name: 'action'
|
||||
}}
|
||||
>
|
||||
{this.props.items.map(item => {
|
||||
return (
|
||||
<MenuItem key={item.value} value={item.value}>{item.name}</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</Container>;
|
||||
}
|
||||
}
|
||||
|
||||
export default EnumInput;
|
||||
|
||||
EnumInput.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
items: PropTypes.array.isRequired,
|
||||
handleInputChange: PropTypes.func.isRequired,
|
||||
};
|
|
@ -1,98 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { TextField } from '@material-ui/core';
|
||||
|
||||
class IntegerInput extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
value: 0n,
|
||||
min: BigInt(this.props.min),
|
||||
max: BigInt(this.props.max),
|
||||
isBigInt: true,
|
||||
error: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const min = BigInt(this.props.min);
|
||||
const max = BigInt(this.props.max);
|
||||
|
||||
if (
|
||||
this.state.min !== min ||
|
||||
this.state.max !== max
|
||||
) {
|
||||
this.setState({ min, max });
|
||||
}
|
||||
}
|
||||
|
||||
onChange = (e) => {
|
||||
const { value } = e.target;
|
||||
|
||||
let isBigInt = true;
|
||||
|
||||
try {
|
||||
BigInt(value);
|
||||
} catch (error) {
|
||||
isBigInt = false;
|
||||
}
|
||||
|
||||
const error = this.getErrorMessage(value, isBigInt);
|
||||
this.setState({ value, isBigInt, error });
|
||||
this.props.handleInputChange(e);
|
||||
this.props.updateValidationResult(error);
|
||||
};
|
||||
|
||||
getErrorMessage = (value, isBigInt) => {
|
||||
const { min, max, pow, item } = this.props;
|
||||
|
||||
if (value > max) {
|
||||
return `${item.name} can only safely store up to ${pow} bits`;
|
||||
}
|
||||
|
||||
if (value < min) {
|
||||
if (min === 0n) {
|
||||
return `${item.name} should be a positive`;
|
||||
}
|
||||
|
||||
return `${item.name} can only safely store up to ${pow} bits`;
|
||||
}
|
||||
|
||||
if (!isBigInt) {
|
||||
return `${item.name} required as number`;
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
render() {
|
||||
const { value, error } = this.state;
|
||||
const { item: { name } } = this.props;
|
||||
|
||||
return <TextField
|
||||
error={!!error}
|
||||
helperText={error}
|
||||
className='input-field'
|
||||
id={name}
|
||||
label={`${name}:`}
|
||||
name={name}
|
||||
onChange={this.onChange}
|
||||
value={value}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
export default IntegerInput;
|
||||
|
||||
IntegerInput.propTypes = {
|
||||
item: PropTypes.any.isRequired,
|
||||
handleInputChange: PropTypes.func.isRequired,
|
||||
min: PropTypes.any.isRequired,
|
||||
max: PropTypes.any.isRequired,
|
||||
pow: PropTypes.number.isRequired,
|
||||
updateValidationResult: PropTypes.func.isRequired,
|
||||
};
|
|
@ -1,37 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { TextField } from '@material-ui/core';
|
||||
|
||||
class TextInput extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = { value: '' };
|
||||
}
|
||||
|
||||
onChange = (event) => {
|
||||
this.setState({ value: event.target.value });
|
||||
this.props.handleInputChange(event);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <TextField
|
||||
className='input-field'
|
||||
id={this.props.item.name}
|
||||
label={`${this.props.item.name}:`}
|
||||
name={this.props.item.name}
|
||||
onChange={this.onChange}
|
||||
value={this.state.value}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
export default TextInput;
|
||||
|
||||
TextInput.propTypes = {
|
||||
item: PropTypes.any.isRequired,
|
||||
handleInputChange: PropTypes.func.isRequired,
|
||||
};
|
|
@ -1,21 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Container, FormHelperText, FormLabel } from '@material-ui/core';
|
||||
|
||||
class UnsupportedInput extends React.Component {
|
||||
render() {
|
||||
return <Container className='unsupported-input'>
|
||||
<FormHelperText>{this.props.item.name}</FormHelperText>
|
||||
<FormLabel >Unsupported input type</FormLabel>
|
||||
</Container>;
|
||||
}
|
||||
}
|
||||
|
||||
export default UnsupportedInput;
|
||||
|
||||
UnsupportedInput.propTypes = {
|
||||
item: PropTypes.any.isRequired,
|
||||
};
|
|
@ -1,20 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import AddressInput from './AddressInput';
|
||||
import ArrayInput from './ArrayInput';
|
||||
import BooleanInput from './BooleanInput';
|
||||
import EnumInput from './EnumInput';
|
||||
import IntegerInput from './IntegerInput';
|
||||
import TextInput from './TextInput';
|
||||
import UnsupportedInput from './UnsupportedInput';
|
||||
|
||||
export {
|
||||
AddressInput,
|
||||
ArrayInput,
|
||||
BooleanInput,
|
||||
EnumInput,
|
||||
IntegerInput,
|
||||
TextInput,
|
||||
UnsupportedInput,
|
||||
};
|
|
@ -1,83 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { Constants } from '../../constants';
|
||||
import React from 'react';
|
||||
import {
|
||||
AddressInput,
|
||||
ArrayInput,
|
||||
BooleanInput,
|
||||
EnumInput,
|
||||
IntegerInput,
|
||||
TextInput,
|
||||
UnsupportedInput
|
||||
} from '../executionSection/inputControlsCollection';
|
||||
|
||||
const { arrayTypeRegexp, intInputs, variableTypes } = Constants.executionSection;
|
||||
export function createInputComponent(input, handleInputChange, updateValidationResult, index, extendedData) {
|
||||
if (input.type.match(arrayTypeRegexp)) {
|
||||
return <ArrayInput
|
||||
key={index}
|
||||
item={input}
|
||||
handleInputChange={handleInputChange}
|
||||
updateValidationResult={updateValidationResult}
|
||||
/>;
|
||||
}
|
||||
if (input.type === variableTypes.string) {
|
||||
return <TextInput
|
||||
key={index}
|
||||
item={input}
|
||||
handleInputChange={handleInputChange}
|
||||
/>;
|
||||
}
|
||||
|
||||
if (input.type === variableTypes.address) {
|
||||
return <AddressInput
|
||||
key={index}
|
||||
item={input}
|
||||
handleInputChange={handleInputChange}
|
||||
updateValidationResult={updateValidationResult}
|
||||
/>;
|
||||
}
|
||||
|
||||
if (input.type === variableTypes.bool) {
|
||||
return <BooleanInput
|
||||
key={index}
|
||||
item={input}
|
||||
handleInputChange={handleInputChange}
|
||||
/>;
|
||||
}
|
||||
|
||||
if (!!intInputs[input.type]) {
|
||||
// enums section
|
||||
const relatedEnum = extendedData.enumsInfo
|
||||
.methods[extendedData.executedMethod] &&
|
||||
extendedData.enumsInfo
|
||||
.methods[extendedData.executedMethod][input.name];
|
||||
if (relatedEnum) {
|
||||
return <EnumInput
|
||||
title={input.name}
|
||||
items={relatedEnum}
|
||||
handleInputChange={handleInputChange}
|
||||
/>;
|
||||
}
|
||||
// end of enums section
|
||||
const { min, max, pow } = intInputs[input.type];
|
||||
|
||||
return <IntegerInput
|
||||
key={index}
|
||||
item={input}
|
||||
handleInputChange={handleInputChange}
|
||||
min={min}
|
||||
max={max}
|
||||
pow={pow}
|
||||
updateValidationResult={updateValidationResult}
|
||||
/>;
|
||||
}
|
||||
|
||||
return <UnsupportedInput
|
||||
key={index}
|
||||
item={input}
|
||||
handleInputChange={handleInputChange}
|
||||
/>;
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
ArrayProperty,
|
||||
NotSupportedProperty,
|
||||
SimpleProperty,
|
||||
StructProperty,
|
||||
} from '../stateSection/contractProperty';
|
||||
|
||||
// if it is array - it will fail with string key
|
||||
// but mapping allows any type of keys
|
||||
function IsMappingBehavior(property) {
|
||||
if (property.inputs.length > 0
|
||||
&& property.inputs[0].type.startsWith('uint')
|
||||
&& property.inputs[0].name === '') {
|
||||
let isMappingBehavior = true;
|
||||
const someStringIndex = 'ind_1';
|
||||
try {
|
||||
property.method(someStringIndex).call();
|
||||
} catch {
|
||||
isMappingBehavior = false;
|
||||
}
|
||||
return isMappingBehavior;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export const GetStateComponent = (property, index) => {
|
||||
const unsupportedComponents = property.outputs.find((output) => !!output.components);
|
||||
if (unsupportedComponents) {
|
||||
return (
|
||||
<NotSupportedProperty
|
||||
key={index}
|
||||
property={property}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (property.inputs.length > 0) {
|
||||
if (IsMappingBehavior(property)) {
|
||||
return (
|
||||
<NotSupportedProperty
|
||||
key={index}
|
||||
property={property}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ArrayProperty
|
||||
key={index}
|
||||
property={property}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (property.outputs > 1 || property.outputs[0].name) {
|
||||
return (
|
||||
<StructProperty
|
||||
key={index}
|
||||
property={property}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SimpleProperty
|
||||
key={index}
|
||||
property={property}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -1,26 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import BaseViewComponent from './baseViewComponent/BaseViewComponent';
|
||||
import ContractSelector from './contractSelector/ContractSelector';
|
||||
import EventSection from './eventsSection/EventSection';
|
||||
import ExecutionSection from './executionSection/ExecutionSection';
|
||||
import Interaction from './interaction/Interaction';
|
||||
import LabelWithIcon from './labelWithIcon/LabelWithIcon';
|
||||
import Message from './message/Message';
|
||||
import Metadata from './metadata/Metadata';
|
||||
import StateSection from './stateSection/StateSection';
|
||||
import TextFieldWithTooltip from './textFieldWithTooltip/TextFieldWithTooltip';
|
||||
|
||||
export {
|
||||
BaseViewComponent,
|
||||
ContractSelector,
|
||||
EventSection,
|
||||
ExecutionSection,
|
||||
Interaction,
|
||||
LabelWithIcon,
|
||||
Message,
|
||||
Metadata,
|
||||
StateSection,
|
||||
TextFieldWithTooltip
|
||||
};
|
|
@ -1,117 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { Container } from '@material-ui/core';
|
||||
import { deepEqual } from 'helpers';
|
||||
import { DrizzleContext } from '@drizzle/react-plugin';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {
|
||||
BaseViewComponent,
|
||||
EventSection,
|
||||
ExecutionSection,
|
||||
StateSection
|
||||
} from 'components';
|
||||
import './interaction.less';
|
||||
|
||||
const getExecutableMethods = (contract) => {
|
||||
const isExecutableAbiMethod = (abiEl) => (abiEl.type === 'function' && abiEl.constant === false);
|
||||
const executableAbiMethods = contract.abi.filter(isExecutableAbiMethod).map(el => el.name);
|
||||
|
||||
return Object.keys(contract.methods).filter(m => executableAbiMethods.indexOf(m) !== -1);
|
||||
};
|
||||
|
||||
class Interaction extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
methods: undefined,
|
||||
contractName: undefined,
|
||||
subscription: undefined,
|
||||
events: []
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.componentDidUpdate();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const contract = this.props.drizzle.contractList[0];
|
||||
|
||||
if (!contract) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { contractName } = contract;
|
||||
const methods = Object.keys(contract);
|
||||
|
||||
const state = {
|
||||
...this.state,
|
||||
methods,
|
||||
contractName
|
||||
};
|
||||
|
||||
if (!deepEqual(this.state, state)) {
|
||||
if (!!this.state.subscription) {
|
||||
this.state.subscription.unsubscribe();
|
||||
}
|
||||
|
||||
const subscription = contract.events.allEvents()
|
||||
.on('data', (event) => this.setState({ events: [...this.state.events, event] }));
|
||||
|
||||
this.setState({ ...state, subscription });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { events } = this.state;
|
||||
|
||||
return <BaseViewComponent
|
||||
header='Interaction'
|
||||
className='interaction component'
|
||||
>
|
||||
<Container className='container'>
|
||||
<Container className='input'>
|
||||
<ExecutionSection
|
||||
actions={getExecutableMethods(this.props.drizzle.contractList[0])}
|
||||
contractName={this.props.drizzle.contractList[0].contractName}
|
||||
drizzle={this.props.drizzle}
|
||||
render={this.renderExecutionSection}
|
||||
/>
|
||||
</Container>
|
||||
<Container className='output'>
|
||||
<StateSection
|
||||
contract={this.props.drizzle.contractList[0]}
|
||||
/>
|
||||
<EventSection
|
||||
events={events}
|
||||
/>
|
||||
</Container>
|
||||
</Container>
|
||||
</BaseViewComponent>;
|
||||
}
|
||||
}
|
||||
|
||||
const HOC = () => <DrizzleContext.Consumer>
|
||||
{props => <Interaction {...props} />}
|
||||
</DrizzleContext.Consumer>;
|
||||
|
||||
export default HOC;
|
||||
|
||||
Interaction.propTypes = {
|
||||
drizzle: PropTypes.shape({
|
||||
contractList: PropTypes.arrayOf(PropTypes.shape({
|
||||
contractName: PropTypes.string.isRequired,
|
||||
methods: PropTypes.object.isRequired,
|
||||
events: PropTypes.shape({
|
||||
allEvents: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
options: PropTypes.shape({
|
||||
isOnline: PropTypes.bool.isRequired
|
||||
}).isRequired
|
||||
})).isRequired
|
||||
}).isRequired,
|
||||
drizzleState: PropTypes.any
|
||||
};
|
|
@ -1,21 +0,0 @@
|
|||
.interaction.component {
|
||||
.container {
|
||||
display: flex;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
|
||||
.input {
|
||||
flex: 1;
|
||||
margin-left: 12px;
|
||||
margin-right: 6px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.output {
|
||||
flex: 1;
|
||||
margin-right: 12px;
|
||||
margin-left: 6px;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Tooltip } from '@material-ui/core';
|
||||
import './labelWithIcon.less';
|
||||
|
||||
class LabelWithIcon extends React.Component {
|
||||
render() {
|
||||
const { text, icon } = this.props;
|
||||
|
||||
return <Tooltip title='Copy' placement='top-end'>
|
||||
<div className='label-with-icon'>
|
||||
<span className='text'>{text}</span>
|
||||
{icon}
|
||||
</div>
|
||||
</Tooltip>;
|
||||
}
|
||||
}
|
||||
|
||||
export default LabelWithIcon;
|
||||
|
||||
LabelWithIcon.propTypes = {
|
||||
text: PropTypes.string.isRequired,
|
||||
icon: PropTypes.node.isRequired
|
||||
};
|
|
@ -1,12 +0,0 @@
|
|||
.label-with-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.text {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.MuiSvgIcon-root {
|
||||
font-size: 1em;
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { Chip } from '@material-ui/core';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
class Message extends React.PureComponent {
|
||||
render() {
|
||||
const {
|
||||
className = '',
|
||||
message
|
||||
} = this.props;
|
||||
|
||||
return <Chip
|
||||
className={className}
|
||||
color='secondary'
|
||||
variant='outlined'
|
||||
label={message}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
export default Message;
|
||||
|
||||
Message.propTypes = {
|
||||
className: PropTypes.string,
|
||||
message: PropTypes.string.isRequired
|
||||
};
|
|
@ -1,241 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { DrizzleContext } from '@drizzle/react-plugin';
|
||||
import { FileCopy } from '@material-ui/icons';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {
|
||||
BaseViewComponent,
|
||||
LabelWithIcon,
|
||||
Message,
|
||||
TextFieldWithTooltip
|
||||
} from 'components';
|
||||
import {
|
||||
Container,
|
||||
TextField
|
||||
} from '@material-ui/core';
|
||||
import { copy, deepEqual } from 'helpers';
|
||||
import './metadata.less';
|
||||
|
||||
class Metadata extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
abi: undefined,
|
||||
address: undefined,
|
||||
bytecode: undefined,
|
||||
events: undefined,
|
||||
network: undefined,
|
||||
networkName: undefined,
|
||||
showBytecodeTooltip: false,
|
||||
showABITooltip: false,
|
||||
transaction: {
|
||||
from: '',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.componentDidUpdate();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const contract = this.props.drizzle.contractList[0];
|
||||
|
||||
if (!contract) {
|
||||
return;
|
||||
}
|
||||
|
||||
const abi = JSON.stringify(contract.abi);
|
||||
const address = contract.address;
|
||||
const bytecode = contract.options.data;
|
||||
const events = this.getEvents();
|
||||
const network = this.getNetwork();
|
||||
const networkName = contract.options.networkName;
|
||||
|
||||
const state = {
|
||||
...this.state,
|
||||
abi,
|
||||
address,
|
||||
bytecode,
|
||||
events,
|
||||
network,
|
||||
networkName
|
||||
};
|
||||
|
||||
if (!deepEqual(this.state, state)) {
|
||||
this.getTransactionInfo(state.network.transactionHash)
|
||||
.then((transaction) => this.setState({ ...state, transaction }))
|
||||
.catch(() => this.setState(state));
|
||||
}
|
||||
}
|
||||
|
||||
copyBytecodeToClipboard = () => {
|
||||
copy(this.state.bytecode);
|
||||
this.showBytecodeTooltip();
|
||||
};
|
||||
|
||||
copyABIToClipboard = () => {
|
||||
copy(this.state.abi);
|
||||
this.showABITooltip();
|
||||
}
|
||||
|
||||
getEvents = () => {
|
||||
const { drizzle } = this.props;
|
||||
const { events } = drizzle.contractList[0];
|
||||
|
||||
return Object.keys(events)
|
||||
.find(key => key.match(/0x(\d|\S)+/g)) || '';
|
||||
}
|
||||
|
||||
getNetwork = () => {
|
||||
const { drizzle } = this.props;
|
||||
const contract = drizzle.contractList[0];
|
||||
const { networks } = contract.options;
|
||||
|
||||
const networkKey = Object.keys(networks)
|
||||
.find((key) => networks[key].address === contract.address);
|
||||
|
||||
return Object.assign(networks[networkKey], { id: networkKey });
|
||||
}
|
||||
|
||||
getTransactionInfo = (transactionHash) => {
|
||||
const { drizzle } = this.props;
|
||||
const { getTransaction } = drizzle.contractList[0].web3.eth;
|
||||
|
||||
return getTransaction(transactionHash);
|
||||
}
|
||||
|
||||
showBytecodeTooltip = () => this.setState({ showBytecodeTooltip: true });
|
||||
|
||||
showABITooltip = () => this.setState({ showABITooltip: true });
|
||||
|
||||
hideBytecodeTooltip = () => this.setState({ showBytecodeTooltip: false });
|
||||
|
||||
hideABITooltip = () => this.setState({ showABITooltip: false });
|
||||
|
||||
render() {
|
||||
const {
|
||||
abi,
|
||||
address,
|
||||
events,
|
||||
network,
|
||||
bytecode,
|
||||
transaction,
|
||||
networkName,
|
||||
showABITooltip,
|
||||
showBytecodeTooltip
|
||||
} = this.state;
|
||||
|
||||
if (!abi || !bytecode) {
|
||||
return <BaseViewComponent header='Metadata' className='metadata component'>
|
||||
<Message message='Contract not loaded (ABI or bytecode not defined)' />
|
||||
</BaseViewComponent>;
|
||||
}
|
||||
|
||||
const byteCodeLabel = <LabelWithIcon text='Bytecode:' icon={<FileCopy />} />;
|
||||
const ABILabel = <LabelWithIcon text='ABI:' icon={<FileCopy />} />;
|
||||
|
||||
return <BaseViewComponent
|
||||
header='Metadata'
|
||||
className='metadata component'
|
||||
>
|
||||
<Container className='list'>
|
||||
<TextField
|
||||
disabled
|
||||
fullWidth
|
||||
id='deployed-location'
|
||||
label='Deployed Location:'
|
||||
value={networkName || network.id}
|
||||
className='text field'
|
||||
/>
|
||||
<TextField
|
||||
disabled
|
||||
fullWidth
|
||||
id='contract-address'
|
||||
label='Contract Address:'
|
||||
value={address}
|
||||
className='text field'
|
||||
/>
|
||||
<TextField
|
||||
disabled
|
||||
fullWidth
|
||||
id='creator-account'
|
||||
label='Creator Account:'
|
||||
value={transaction && transaction.from || ''}
|
||||
className='text field'
|
||||
/>
|
||||
<TextFieldWithTooltip
|
||||
open={showBytecodeTooltip}
|
||||
title='Bytecode copied to clipboard!'
|
||||
leaveDelay={900}
|
||||
onClose={this.hideBytecodeTooltip}
|
||||
disabled
|
||||
fullWidth
|
||||
id='bytecode'
|
||||
label={byteCodeLabel}
|
||||
onClick={this.copyBytecodeToClipboard}
|
||||
value={bytecode}
|
||||
className='text field'
|
||||
/>
|
||||
<TextFieldWithTooltip
|
||||
open={showABITooltip}
|
||||
title='ABI copied to clipboard!'
|
||||
leaveDelay={900}
|
||||
onClose={this.hideABITooltip}
|
||||
disabled
|
||||
fullWidth
|
||||
id='abi'
|
||||
label={ABILabel}
|
||||
onClick={this.copyABIToClipboard}
|
||||
value={abi}
|
||||
className='text field'
|
||||
/>
|
||||
<TextField
|
||||
disabled
|
||||
fullWidth
|
||||
id='tx-history'
|
||||
label='TX History:'
|
||||
value={network.transactionHash}
|
||||
className='text field'
|
||||
/>
|
||||
<TextField
|
||||
disabled
|
||||
fullWidth
|
||||
id='events'
|
||||
label='Events:'
|
||||
value={events}
|
||||
className='text field'
|
||||
/>
|
||||
</Container>
|
||||
</BaseViewComponent>;
|
||||
}
|
||||
}
|
||||
|
||||
const HOC = () => <DrizzleContext.Consumer>
|
||||
{props => <Metadata {...props} />}
|
||||
</DrizzleContext.Consumer>;
|
||||
|
||||
export default HOC;
|
||||
|
||||
Metadata.propTypes = {
|
||||
drizzle: PropTypes.shape({
|
||||
contractList: PropTypes.arrayOf(PropTypes.shape({
|
||||
abi: PropTypes.array.isRequired,
|
||||
address: PropTypes.string.isRequired,
|
||||
events: PropTypes.object.isRequired,
|
||||
options: PropTypes.shape({
|
||||
data: PropTypes.string.isRequired,
|
||||
networks: PropTypes.object.isRequired,
|
||||
networkName: PropTypes.string,
|
||||
}).isRequired,
|
||||
web3: PropTypes.shape({
|
||||
eth: PropTypes.shape({
|
||||
getTransaction: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
})).isRequired
|
||||
}).isRequired
|
||||
};
|
|
@ -1,12 +0,0 @@
|
|||
.metadata.component {
|
||||
.list {
|
||||
.text.field {
|
||||
margin-bottom: 12px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.MuiContainer-root {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { Box } from '@material-ui/core';
|
||||
import { GetStateComponent } from '../factory/StateComponentFactory';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
class StateSection extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
stateObjects: this.getStateObjects(),
|
||||
};
|
||||
}
|
||||
|
||||
getStateObjects() {
|
||||
const abi = this.props.contract.abi;
|
||||
const methods = this.props.contract.methods;
|
||||
|
||||
return abi
|
||||
.filter(element => element.constant)
|
||||
.map((constant) => Object.assign(
|
||||
constant,
|
||||
{
|
||||
method: methods[constant.name],
|
||||
value: '',
|
||||
enumsInfo: this.props.contract.options.enumsInfo,
|
||||
}));
|
||||
}
|
||||
|
||||
renderStateObjects() {
|
||||
return this.state.stateObjects.map((stateObject, index) => {
|
||||
return GetStateComponent(stateObject, index);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Box
|
||||
className='state-section'>
|
||||
{this.renderStateObjects()}
|
||||
</Box>;
|
||||
}
|
||||
}
|
||||
|
||||
export default StateSection;
|
||||
|
||||
StateSection.propTypes = {
|
||||
contract: PropTypes.shape({
|
||||
abi: PropTypes.array.isRequired,
|
||||
methods: PropTypes.object.isRequired,
|
||||
options: PropTypes.shape({
|
||||
enumsInfo: PropTypes.object.isRequired
|
||||
}).isRequired
|
||||
}).isRequired
|
||||
};
|
|
@ -1,119 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import ComplexField from './ComplexField';
|
||||
import { deepEqual } from '../../../helpers';
|
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {
|
||||
Container,
|
||||
ExpansionPanel,
|
||||
ExpansionPanelDetails,
|
||||
ExpansionPanelSummary,
|
||||
TextField,
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import '../stateSection.less';
|
||||
|
||||
class ArrayProperty extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
property: {
|
||||
name: '',
|
||||
value: [],
|
||||
outputs: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getArrayPropertyInfo();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.getArrayPropertyInfo();
|
||||
}
|
||||
|
||||
getArrayValues = async () => {
|
||||
const valueArray = [];
|
||||
|
||||
try {
|
||||
let counter = 0;
|
||||
const maxDisplayedItems = 8000;
|
||||
|
||||
while (true) { // eslint-disable-line
|
||||
if (counter > maxDisplayedItems) {
|
||||
valueArray.push(`... more than ${maxDisplayedItems} items`);
|
||||
break;
|
||||
}
|
||||
const elementValue = await this.props.property.method(counter).call();
|
||||
valueArray.push(elementValue);
|
||||
counter++;
|
||||
}
|
||||
return valueArray;
|
||||
} catch {
|
||||
return valueArray;
|
||||
}
|
||||
};
|
||||
|
||||
getArrayPropertyInfo = () => {
|
||||
this.getArrayValues().then(result => {
|
||||
if (!deepEqual(this.state.property.value, result)) {
|
||||
this.setState({
|
||||
property: {
|
||||
name: this.props.property.name,
|
||||
value: result,
|
||||
outputs: this.props.property.outputs,
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
renderArrayElements = () => {
|
||||
return this.state.property.value.map((value, index) => {
|
||||
if (value instanceof Object) {
|
||||
return <ComplexField
|
||||
key={index}
|
||||
fields={this.state.property.outputs}
|
||||
values={value}
|
||||
name={`[${index}] : `}
|
||||
/>;
|
||||
}
|
||||
return (
|
||||
<TextField
|
||||
className='metadata text field'
|
||||
disabled
|
||||
fullWidth
|
||||
key={index}
|
||||
label={`[${index}] : `}
|
||||
value={value}
|
||||
/>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return <ExpansionPanel>
|
||||
<ExpansionPanelSummary
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
>
|
||||
<Typography>
|
||||
{`${this.state.property.name} [ ] :`}
|
||||
</Typography>
|
||||
</ExpansionPanelSummary>
|
||||
<ExpansionPanelDetails>
|
||||
<Container>{this.renderArrayElements()}</Container>
|
||||
</ExpansionPanelDetails>
|
||||
</ExpansionPanel>;
|
||||
}
|
||||
}
|
||||
|
||||
export default ArrayProperty;
|
||||
|
||||
ArrayProperty.propTypes = {
|
||||
property: PropTypes.object.isRequired,
|
||||
};
|
|
@ -1,55 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {
|
||||
Container,
|
||||
ExpansionPanel,
|
||||
ExpansionPanelDetails,
|
||||
ExpansionPanelSummary,
|
||||
TextField,
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import '../stateSection.less';
|
||||
|
||||
class ComplexField extends React.Component {
|
||||
renderFields = () => {
|
||||
return this.props.fields.map((field, index) => {
|
||||
return (
|
||||
<TextField
|
||||
className='metadata text field'
|
||||
disabled
|
||||
fullWidth
|
||||
key={index}
|
||||
label={`${field.name}: `}
|
||||
value={this.props.values[field.name]}
|
||||
/>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ExpansionPanel>
|
||||
<ExpansionPanelSummary expandIcon={<ExpandMoreIcon />} href={''}>
|
||||
<Typography>
|
||||
{this.props.name}
|
||||
</Typography>
|
||||
</ExpansionPanelSummary>
|
||||
<ExpansionPanelDetails>
|
||||
<Container>{this.renderFields()}</Container>
|
||||
</ExpansionPanelDetails>
|
||||
</ExpansionPanel>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ComplexField;
|
||||
|
||||
ComplexField.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
values: PropTypes.object.isRequired,
|
||||
fields: PropTypes.array.isRequired,
|
||||
};
|
|
@ -1,26 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { TextField } from '@material-ui/core';
|
||||
|
||||
class NotSupportedProperty extends React.Component {
|
||||
render() {
|
||||
const { property } = this.props;
|
||||
|
||||
return <TextField
|
||||
className='metadata text field'
|
||||
disabled
|
||||
fullWidth
|
||||
label={property.name}
|
||||
value={'Sorry! Complex structures has not supported yet!'}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
export default NotSupportedProperty;
|
||||
|
||||
NotSupportedProperty.propTypes = {
|
||||
property: PropTypes.object.isRequired,
|
||||
};
|
|
@ -1,67 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { TextField } from '@material-ui/core';
|
||||
|
||||
class SimpleProperty extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
property: {
|
||||
name: '',
|
||||
value: '',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getPropertyInfo();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.getPropertyInfo();
|
||||
}
|
||||
|
||||
getPropertyInfo = () => {
|
||||
this.props.property.method().call().then(result => {
|
||||
let newValue = result;
|
||||
|
||||
const relatedEnum = this.props.property.enumsInfo.fields[this.state.property.name];
|
||||
if (relatedEnum !== undefined) {// replace uint value to enum named instance
|
||||
const enumValue = relatedEnum.find(i => i.value === +result);
|
||||
if (enumValue) {
|
||||
newValue = enumValue.name;
|
||||
}
|
||||
}
|
||||
|
||||
if (newValue !== this.state.property.value || this.props.property.name !== this.state.property.name) {
|
||||
this.setState({
|
||||
property: {
|
||||
name: this.props.property.name,
|
||||
value: newValue
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { property } = this.state;
|
||||
return <TextField
|
||||
className='metadata text field'
|
||||
disabled
|
||||
fullWidth
|
||||
label={property.name}
|
||||
value={property.value}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
export default SimpleProperty;
|
||||
|
||||
SimpleProperty.propTypes = {
|
||||
property: PropTypes.object.isRequired,
|
||||
};
|
|
@ -1,58 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import ComplexField from './ComplexField';
|
||||
import { deepEqual } from '../../../helpers';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import '../stateSection.less';
|
||||
|
||||
class StructProperty extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
property: {
|
||||
name: '',
|
||||
value: {},
|
||||
outputs: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getStructPropertyInfo();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.getStructPropertyInfo();
|
||||
}
|
||||
|
||||
getStructPropertyInfo = () => {
|
||||
this.props.property.method().call().then(result => {
|
||||
if (!deepEqual(this.state.property.value, result)) {
|
||||
this.setState({
|
||||
property: {
|
||||
name: this.props.property.name,
|
||||
value: result,
|
||||
outputs: this.props.property.outputs
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return <ComplexField
|
||||
fields={this.state.property.outputs}
|
||||
values={this.state.property.value}
|
||||
name={`${this.state.property.name}:`}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
export default StructProperty;
|
||||
|
||||
StructProperty.propTypes = {
|
||||
property: PropTypes.object.isRequired,
|
||||
};
|
|
@ -1,11 +0,0 @@
|
|||
import ArrayProperty from './ArrayProperty';
|
||||
import NotSupportedProperty from './NotSupportedProperty';
|
||||
import SimpleProperty from './SimpleProperty';
|
||||
import StructProperty from './StructProperty';
|
||||
|
||||
export {
|
||||
ArrayProperty,
|
||||
NotSupportedProperty,
|
||||
SimpleProperty,
|
||||
StructProperty,
|
||||
};
|
|
@ -1,5 +0,0 @@
|
|||
.state-section {
|
||||
&.MuiContainer-root {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {
|
||||
TextField,
|
||||
Tooltip
|
||||
} from '@material-ui/core';
|
||||
|
||||
class TextFieldWithTooltip extends React.PureComponent {
|
||||
render() {
|
||||
const {
|
||||
id,
|
||||
open,
|
||||
title,
|
||||
leaveDelay,
|
||||
onClose,
|
||||
onClick,
|
||||
disabled = false,
|
||||
className = '',
|
||||
fullWidth = false,
|
||||
label,
|
||||
value,
|
||||
placement = 'top'
|
||||
} = this.props;
|
||||
|
||||
return <Tooltip
|
||||
open={open}
|
||||
title={title}
|
||||
leaveDelay={leaveDelay}
|
||||
placement={placement}
|
||||
onClose={onClose}
|
||||
>
|
||||
<TextField
|
||||
id={id}
|
||||
disabled={disabled}
|
||||
className={className}
|
||||
fullWidth={fullWidth}
|
||||
onClick={onClick}
|
||||
label={label}
|
||||
value={value}
|
||||
/>
|
||||
</Tooltip>;
|
||||
}
|
||||
}
|
||||
|
||||
export default TextFieldWithTooltip;
|
||||
|
||||
TextFieldWithTooltip.propTypes = {
|
||||
open: PropTypes.bool.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
leaveDelay: PropTypes.number.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
className: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
fullWidth: PropTypes.bool,
|
||||
id: PropTypes.string,
|
||||
label: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
value: PropTypes.any,
|
||||
placement: PropTypes.string
|
||||
};
|
|
@ -1,61 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { BigIntMath } from 'helpers';
|
||||
|
||||
const intInputs = Array(32)
|
||||
.fill(0)
|
||||
.map((_, index) => (index + 1) * 8)
|
||||
.reduce((acc, pow) => {
|
||||
const uintMax = BigIntMath.pow(2n, pow) - 1n;
|
||||
const intMax = BigIntMath.pow(2n, pow - 1) - 1n;
|
||||
const intMin = -intMax;
|
||||
|
||||
acc[`int${pow}`] = {
|
||||
min: intMin,
|
||||
max: intMax,
|
||||
pow
|
||||
};
|
||||
|
||||
acc[`uint${pow}`] = {
|
||||
min: 0n,
|
||||
max: uintMax,
|
||||
pow
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const getMessageInvalidAddress = (inputName) => {
|
||||
return `The first characters of ${inputName} must be '0x'. `
|
||||
+ 'Address should have letter from a to z, A to Z, digits. Length must be 42 characters.';
|
||||
};
|
||||
|
||||
export class Constants {
|
||||
static executionSection = {
|
||||
variableTypes: {
|
||||
address: 'address',
|
||||
string: 'string',
|
||||
bool: 'bool',
|
||||
},
|
||||
|
||||
intInputs,
|
||||
|
||||
validationMessages: {
|
||||
address: getMessageInvalidAddress,
|
||||
},
|
||||
validationRegexps: {
|
||||
address: /^(0x)[a-zA-Z0-9]{40}$/g,
|
||||
},
|
||||
placeholder: {
|
||||
address: '0x0000000000000000000000000000000000000000',
|
||||
},
|
||||
arrayTypeRegexp: /\w*\[\d*\]/g,
|
||||
};
|
||||
|
||||
static regexps = {
|
||||
providerTypes: {
|
||||
azure: /blockchain.azure.com/i
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
export const BigIntMath = {
|
||||
// eval required because babel translate ** to Math.pow
|
||||
/* eslint-disable no-eval */
|
||||
pow: (value, pow) => BigInt(eval(`BigInt(${value}) ** BigInt(${pow})`)),
|
||||
|
||||
log2: (bigIntValue) => {
|
||||
let count = 0;
|
||||
let value = bigIntValue;
|
||||
|
||||
while (value > 0n) {
|
||||
value /= 2n;
|
||||
count += 1;
|
||||
}
|
||||
|
||||
return count;
|
||||
},
|
||||
|
||||
sqrt: (value) => {
|
||||
if (value < 0n) {
|
||||
throw Error('square root of negative numbers is not supported');
|
||||
}
|
||||
|
||||
if (value < 2n) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value === 4n) {
|
||||
return 2n;
|
||||
}
|
||||
|
||||
const newtonIteration = (n, x0) => {
|
||||
/* eslint-disable no-bitwise */
|
||||
const x1 = ((n / x0) + x0) >> 1n;
|
||||
|
||||
if (x0 === x1 || x0 === (x1 - 1n)) {
|
||||
return x0;
|
||||
}
|
||||
|
||||
return newtonIteration(n, x1);
|
||||
};
|
||||
|
||||
return newtonIteration(value, 1n);
|
||||
}
|
||||
};
|
|
@ -1,23 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
export const copy = (text) => {
|
||||
const span = document.createElement('span');
|
||||
span.textContent = text;
|
||||
span.style.whiteSpace = 'pre';
|
||||
|
||||
document.body.appendChild(span);
|
||||
|
||||
const selection = window.getSelection();
|
||||
const range = document.createRange();
|
||||
|
||||
selection.removeAllRanges();
|
||||
range.selectNode(span);
|
||||
selection.addRange(range);
|
||||
|
||||
document.execCommand('copy');
|
||||
|
||||
selection.removeAllRanges();
|
||||
|
||||
document.body.removeChild(span);
|
||||
};
|
|
@ -1,41 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
export const deepEqual = (first, second) => {
|
||||
if (first === second) return true;
|
||||
|
||||
if (
|
||||
first && second
|
||||
&& typeof first === 'object' && typeof second === 'object'
|
||||
) {
|
||||
if (first.constructor !== second.constructor) return false;
|
||||
|
||||
if (Array.isArray(first) && Array.isArray(second)) {
|
||||
if (first.length !== second.length) return false;
|
||||
|
||||
for (let i = 0; i < first.length; i++) {
|
||||
if (!deepEqual(first[i], second[i])) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const firstKeys = Object.keys(first);
|
||||
const secondKeys = Object.keys(second);
|
||||
|
||||
if (firstKeys.length !== secondKeys.length) return false;
|
||||
/* eslint-disable no-unused-vars */
|
||||
for (const key of firstKeys) {
|
||||
if (!second.hasOwnProperty(key)) return false;
|
||||
}
|
||||
|
||||
for (const key of firstKeys) {
|
||||
if (!deepEqual(first[key], second[key])) return false;
|
||||
}
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
|
@ -1,12 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { BigIntMath } from './bigIntMath';
|
||||
import { copy } from './copy';
|
||||
import { deepEqual } from './deepEqual';
|
||||
|
||||
export {
|
||||
BigIntMath,
|
||||
copy,
|
||||
deepEqual,
|
||||
};
|
|
@ -1,15 +0,0 @@
|
|||
const normalize = (sourceUrl) => {
|
||||
let url = sourceUrl.slice();
|
||||
|
||||
const protocol = url.match(/\:\/\//);
|
||||
|
||||
if (!protocol) {
|
||||
url = `http://${url}`;
|
||||
}
|
||||
|
||||
return url;
|
||||
};
|
||||
|
||||
export const Url = {
|
||||
normalize,
|
||||
};
|
|
@ -1,12 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Client</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,56 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import App from './App';
|
||||
import { contractEventNotifier } from 'middlewares';
|
||||
import { DrizzleContext } from '@drizzle/react-plugin';
|
||||
import { LocalStorage } from 'polyfills/localStorage';
|
||||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { Drizzle, generateStore } from '@drizzle/store';
|
||||
|
||||
const storage = new LocalStorage();
|
||||
|
||||
window.ls = new Proxy(storage, {
|
||||
set: (_, prop, value) => {
|
||||
if (LocalStorage.prototype.hasOwnProperty(prop)) {
|
||||
storage[prop] = value;
|
||||
} else {
|
||||
storage.setItem(prop, value);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
get: (_, name) => {
|
||||
if (LocalStorage.prototype.hasOwnProperty(name)) {
|
||||
return storage[name];
|
||||
}
|
||||
if (storage.values.has(name)) {
|
||||
return storage.getItem(name);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
});
|
||||
|
||||
Drizzle.prototype.deleteAllContracts = function () {
|
||||
Object.keys(this.contracts)
|
||||
.forEach(contractName => this.deleteContract(contractName));
|
||||
};
|
||||
|
||||
const options = { contracts: [] };
|
||||
|
||||
const store = generateStore({
|
||||
drizzleOptions: options,
|
||||
appMiddlewares: [contractEventNotifier]
|
||||
});
|
||||
|
||||
const drizzle = new Drizzle(options, store);
|
||||
|
||||
render(
|
||||
<DrizzleContext.Provider drizzle={drizzle}>
|
||||
<App />
|
||||
</DrizzleContext.Provider>,
|
||||
document.getElementById('root')
|
||||
);
|
|
@ -1,28 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { EventActions } from '@drizzle/store';
|
||||
import { Notifications } from 'services';
|
||||
|
||||
export const contractEventNotifier = () => next => action => {
|
||||
let message;
|
||||
|
||||
// Workaround. Used to prevent drizzle fallback actions.
|
||||
const showErrors = Boolean(window.showErrors) || false;
|
||||
|
||||
if (!showErrors) {
|
||||
return next(action);
|
||||
}
|
||||
|
||||
if (action.type === EventActions.EVENT_ERROR) {
|
||||
message = action.event.returnValues._message;
|
||||
} else if (!!action.error) {
|
||||
message = action.error.message;
|
||||
}
|
||||
|
||||
if (!!message) {
|
||||
Notifications.showNotification({ message, type: Notifications.types.error, });
|
||||
}
|
||||
|
||||
return next(action);
|
||||
};
|
|
@ -1,4 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
export * from './drizzle';
|
|
@ -1,36 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
export class LocalStorage {
|
||||
constructor() {
|
||||
this.values = new Map();
|
||||
}
|
||||
|
||||
getItem(key) {
|
||||
const stringKey = String(key);
|
||||
if (this.values.has(stringKey)) {
|
||||
return String(this.values.get(stringKey));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
setItem(key, value) {
|
||||
this.values.set(String(key), String(value));
|
||||
}
|
||||
|
||||
removeItem(key) {
|
||||
this.values.delete(String(key));
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.values.clear();
|
||||
}
|
||||
|
||||
key(i) {
|
||||
return Array.from(this.values.keys())[i];
|
||||
}
|
||||
|
||||
get length() {
|
||||
return this.values.size;
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
export * from './ipc';
|
||||
export * from './notifications';
|
|
@ -1,57 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
const getCommandList = commands => commands
|
||||
.trim()
|
||||
.split(' ')
|
||||
.map(command => command.trim());
|
||||
|
||||
const execute = (listeners, command, data) => {
|
||||
if (listeners.has(command)) {
|
||||
const handlers = listeners.get(command);
|
||||
|
||||
handlers.forEach(handler => handler(data));
|
||||
}
|
||||
};
|
||||
|
||||
const addToListeners = (listeners, commands, handler) => {
|
||||
const commandsList = getCommandList(commands);
|
||||
|
||||
commandsList.forEach(command => {
|
||||
if (!listeners.has(command)) {
|
||||
listeners.set(command, []);
|
||||
}
|
||||
|
||||
const handlers = listeners.get(command);
|
||||
|
||||
handlers.push(handler);
|
||||
});
|
||||
};
|
||||
|
||||
const removeFromListeners = (listeners, commands, handler) => {
|
||||
const commandsList = getCommandList(commands);
|
||||
|
||||
commandsList.forEach(command => {
|
||||
if (listeners.has(command)) {
|
||||
if (!handler) {
|
||||
listeners.delete(command);
|
||||
return;
|
||||
}
|
||||
|
||||
let handlers = listeners.get(command);
|
||||
|
||||
handlers = handlers.filter(element => element !== handler);
|
||||
|
||||
listeners.set(command, handlers);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const removeAllListers = (listeners, command) => listeners.delete(command);
|
||||
|
||||
export const Helpers = {
|
||||
execute,
|
||||
addToListeners,
|
||||
removeFromListeners,
|
||||
removeAllListers
|
||||
};
|
|
@ -1,39 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { Helpers } from './helpers';
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const vscode = acquireVsCodeApi();
|
||||
|
||||
const listeners = new Map();
|
||||
const onceListeners = new Map();
|
||||
|
||||
const on = (commands, handler) => Helpers.addToListeners(listeners, commands, handler);
|
||||
|
||||
const once = (commands, handler) => Helpers.addToListeners(onceListeners, commands, handler);
|
||||
|
||||
const off = (commands, handler) => {
|
||||
Helpers.removeFromListeners(listeners, commands, handler);
|
||||
Helpers.removeFromListeners(onceListeners, commands, handler);
|
||||
};
|
||||
|
||||
const postMessage = (command, value) => vscode.postMessage({ command, value });
|
||||
|
||||
const messageReceived = (event) => {
|
||||
const { command, value: data } = event.data;
|
||||
|
||||
Helpers.execute(listeners, command, data);
|
||||
Helpers.execute(onceListeners, command, data);
|
||||
|
||||
Helpers.removeAllListers(onceListeners, command);
|
||||
};
|
||||
|
||||
window.addEventListener('message', messageReceived, false);
|
||||
|
||||
export const IPC = {
|
||||
on,
|
||||
once,
|
||||
off,
|
||||
postMessage
|
||||
};
|
|
@ -1,19 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { IPC } from '../ipc';
|
||||
|
||||
const types = {
|
||||
error: 'error',
|
||||
info: 'info',
|
||||
warning: 'warning',
|
||||
};
|
||||
|
||||
const showNotification = ({ message, type }) => {
|
||||
IPC.postMessage('notification', { message, type });
|
||||
};
|
||||
|
||||
export const Notifications = {
|
||||
showNotification,
|
||||
types
|
||||
};
|
|
@ -1,8 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import SingleContractView from './singleContractView/SingleContractView';
|
||||
|
||||
export {
|
||||
SingleContractView
|
||||
};
|
|
@ -1,45 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { Container } from '@material-ui/core';
|
||||
import { DrizzleContext } from '@drizzle/react-plugin';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {
|
||||
Interaction,
|
||||
Message,
|
||||
Metadata,
|
||||
} from 'components';
|
||||
import './singleContractView.less';
|
||||
|
||||
class SingleContractView extends React.PureComponent {
|
||||
render() {
|
||||
const { drizzle } = this.props;
|
||||
const contract = drizzle.contractList[0];
|
||||
const { isOnline } = contract.options;
|
||||
|
||||
if (!contract) {
|
||||
return <Message message='No contract'/>;
|
||||
}
|
||||
|
||||
if (!isOnline) {
|
||||
return <Message className='red-error' message='Connection error' />;
|
||||
}
|
||||
|
||||
return <Container className='single contract view'>
|
||||
<Interaction/>
|
||||
<Metadata />
|
||||
</Container>;
|
||||
}
|
||||
}
|
||||
|
||||
const HOC = () => <DrizzleContext.Consumer>
|
||||
{ props => <SingleContractView {...props} /> }
|
||||
</DrizzleContext.Consumer>;
|
||||
|
||||
export default HOC;
|
||||
|
||||
SingleContractView.propTypes = {
|
||||
drizzle: PropTypes.any,
|
||||
drizzleState: PropTypes.any
|
||||
};
|
|
@ -1,18 +0,0 @@
|
|||
.single.contract.view {
|
||||
& > * {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.MuiChip-outlinedSecondary.red-error {
|
||||
background: #f44336;
|
||||
width: 94.7%;
|
||||
color: #fff !important;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #f44336 !important;
|
||||
margin-top: 15px;
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
const path = require('path');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const webpack = require('webpack');
|
||||
|
||||
module.exports = () => {
|
||||
const ENV = process.env.NODE_ENV || 'development';
|
||||
const EXT = !!process.env.EXT_ENV || false;
|
||||
const PATH = EXT ? path.join(__dirname, '../resources/drizzle') : path.resolve(__dirname, 'dist');
|
||||
const PUBLIC_PATH = EXT ? '{{root}}/resources/drizzle' : '/';
|
||||
|
||||
const config = {
|
||||
target: 'web',
|
||||
entry: {
|
||||
app: './src/index.js'
|
||||
},
|
||||
output: {
|
||||
path: PATH,
|
||||
filename: '[name].js',
|
||||
chunkFilename: '[name].chunk.js',
|
||||
publicPath: PUBLIC_PATH
|
||||
},
|
||||
devServer: {
|
||||
contentBase: PATH,
|
||||
historyApiFallback: true
|
||||
},
|
||||
performance: {
|
||||
hints: false
|
||||
},
|
||||
stats: {
|
||||
colors: true,
|
||||
chunks: false,
|
||||
modules: false,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
components: path.join(__dirname, './src/components'),
|
||||
constants: path.join(__dirname, './src/constants'),
|
||||
helpers: path.join(__dirname, './src/helpers'),
|
||||
polyfills: path.join(__dirname, './src/polyfills'),
|
||||
services: path.join(__dirname, './src/services'),
|
||||
views: path.join(__dirname, './src/views'),
|
||||
middlewares: path.join(__dirname, './src/middlewares'),
|
||||
}
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: [/\.js$/, /\.jsx$/],
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
{
|
||||
test: /\.less$/,
|
||||
use: ['style-loader', 'css-loader', 'less-loader']
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
loader: ['style-loader', 'css-loader']
|
||||
},
|
||||
{
|
||||
test: /\.jpe?g$|\.gif$|\.png$|\.ttf$|\.eot$|\.svg$/,
|
||||
use: 'file-loader?name=[name].[ext]?[hash]'
|
||||
},
|
||||
{
|
||||
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||
loader: 'url-loader?limit=10000&mimetype=application/fontwoff'
|
||||
},
|
||||
{
|
||||
test: [/\.js$/, /\.jsx$/],
|
||||
exclude: /node_modules/,
|
||||
loader: 'eslint-loader'
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'string-replace-loader',
|
||||
options: {
|
||||
search: 'localStorage',
|
||||
replace: 'ls',
|
||||
flags: 'g'
|
||||
},
|
||||
exclude: path.join(__dirname, 'src', 'index.js'),
|
||||
include: [
|
||||
path.resolve(__dirname, 'node_modules')
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
alwaysWriteToDisk: true,
|
||||
template: './src/index.html',
|
||||
inject: 'body',
|
||||
hash: ENV !== 'development',
|
||||
chunks: ['app'],
|
||||
filename: 'index.html'
|
||||
}),
|
||||
new MiniCssExtractPlugin({
|
||||
filename: '[name].css',
|
||||
allChunks: true,
|
||||
}),
|
||||
new webpack.optimize.AggressiveMergingPlugin(),
|
||||
]
|
||||
};
|
||||
|
||||
switch (ENV) {
|
||||
case 'production':
|
||||
config.devtool = false;
|
||||
config.optimization = { minimize: true };
|
||||
break;
|
||||
case 'development':
|
||||
config.watch = !EXT;
|
||||
config.devtool = 'source-map';
|
||||
break;
|
||||
case 'test':
|
||||
config.devtool = 'source-map';
|
||||
delete config.entry;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return config;
|
||||
};
|
19
package.json
19
package.json
|
@ -5,7 +5,7 @@
|
|||
"publisher": "AzBlockchain",
|
||||
"preview": false,
|
||||
"icon": "images/blockchain-service-logo.png",
|
||||
"version": "1.6.1",
|
||||
"version": "1.6.2",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Microsoft/vscode-azure-blockchain-ethereum"
|
||||
|
@ -48,7 +48,6 @@
|
|||
"onCommand:azureBlockchainService.generateEventPublishingWorkflows",
|
||||
"onCommand:azureBlockchainService.generateDataPublishingWorkflows",
|
||||
"onCommand:azureBlockchainService.connectProject",
|
||||
"onCommand:azureBlockchainService.showSmartContractPage",
|
||||
"onCommand:azureBlockchainService.copyRPCEndpointAddress",
|
||||
"onCommand:azureBlockchainService.createProject",
|
||||
"onCommand:azureBlockchainService.disconnectProject",
|
||||
|
@ -160,11 +159,6 @@
|
|||
"title": "Connect to network",
|
||||
"category": "Blockchain"
|
||||
},
|
||||
{
|
||||
"command": "azureBlockchainService.showSmartContractPage",
|
||||
"title": "Show Smart Contract Interaction Page",
|
||||
"category": "Blockchain"
|
||||
},
|
||||
{
|
||||
"command": "azureBlockchainService.copyRPCEndpointAddress",
|
||||
"title": "Copy RPC Endpoint Address",
|
||||
|
@ -241,10 +235,6 @@
|
|||
],
|
||||
"menus": {
|
||||
"commandPalette": [
|
||||
{
|
||||
"when": "false",
|
||||
"command": "azureBlockchainService.showSmartContractPage"
|
||||
},
|
||||
{
|
||||
"when": "false",
|
||||
"command": "azureBlockchainService.refresh"
|
||||
|
@ -392,11 +382,6 @@
|
|||
"command": "azureBlockchainService.buildContracts",
|
||||
"group": "8_buildContractGroup"
|
||||
},
|
||||
{
|
||||
"when": "resourceLangId == solidity",
|
||||
"command": "azureBlockchainService.showSmartContractPage",
|
||||
"group": "8_buildContractGroup"
|
||||
},
|
||||
{
|
||||
"when": "resourceLangId == json",
|
||||
"command": "azureBlockchainService.deployContracts",
|
||||
|
@ -541,7 +526,7 @@
|
|||
"scripts": {
|
||||
"package": "npx vsce package",
|
||||
"publish": "npx vsce publish",
|
||||
"vscode:prepublish": "npm i && npm run webpack:prod && cd ./drizzleUI && npm i && npm run build:ext",
|
||||
"vscode:prepublish": "npm i && npm run webpack:prod",
|
||||
"compile": "npm run clean && tsc -p ./",
|
||||
"webpack:prod": "webpack --mode production",
|
||||
"watch": "tsc -watch -p ./",
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
import { commands, ExtensionContext, Uri, window, workspace } from 'vscode';
|
||||
import {
|
||||
ContractCommands,
|
||||
DebuggerCommands,
|
||||
GanacheCommands,
|
||||
InfuraCommands,
|
||||
|
@ -104,7 +103,7 @@ export async function activate(context: ExtensionContext) {
|
|||
const copyRPCEndpointAddress = commands.registerCommand('azureBlockchainService.copyRPCEndpointAddress',
|
||||
async (viewItem: NetworkNodeView) => {
|
||||
await tryExecute(() => TruffleCommands.writeRPCEndpointAddressToBuffer(viewItem));
|
||||
});
|
||||
});
|
||||
const getPrivateKeyFromMnemonic = commands.registerCommand('azureBlockchainService.getPrivateKey', async () => {
|
||||
await tryExecute(() => TruffleCommands.getPrivateKeyFromMnemonic());
|
||||
});
|
||||
|
@ -140,17 +139,12 @@ export async function activate(context: ExtensionContext) {
|
|||
//#endregion
|
||||
|
||||
//#region contract commands
|
||||
const showSmartContractPage = commands.registerCommand(
|
||||
'azureBlockchainService.showSmartContractPage',
|
||||
async (contractPath: Uri) => {
|
||||
await tryExecute(() => ContractCommands.showSmartContractPage(context, contractPath));
|
||||
});
|
||||
const createNewBDMApplication = commands.registerCommand('azureBlockchainService.createNewBDMApplication',
|
||||
async (viewItem: ProjectView) => {
|
||||
await tryExecute(() => ServiceCommands.createNewBDMApplication(viewItem));
|
||||
});
|
||||
});
|
||||
const deleteBDMApplication = commands.registerCommand('azureBlockchainService.deleteBDMApplication',
|
||||
async (viewItem: NetworkNodeView) => await tryExecute(() => ServiceCommands.deleteBDMApplication(viewItem)));
|
||||
async (viewItem: NetworkNodeView) => await tryExecute(() => ServiceCommands.deleteBDMApplication(viewItem)));
|
||||
//#endregion
|
||||
|
||||
//#region open zeppelin commands
|
||||
|
@ -199,7 +193,6 @@ export async function activate(context: ExtensionContext) {
|
|||
const subscriptions = [
|
||||
showWelcomePage,
|
||||
showRequirementsPage,
|
||||
showSmartContractPage,
|
||||
refresh,
|
||||
newSolidityProject,
|
||||
buildContracts,
|
||||
|
|
|
@ -1,257 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { commands, ExtensionContext, Uri, window } from 'vscode';
|
||||
import {
|
||||
ContractCommands,
|
||||
DebuggerCommands,
|
||||
GanacheCommands,
|
||||
InfuraCommands,
|
||||
LogicAppCommands,
|
||||
OpenZeppelinCommands,
|
||||
ProjectCommands,
|
||||
ServiceCommands,
|
||||
TruffleCommands,
|
||||
} from './commands';
|
||||
import { Constants } from './Constants';
|
||||
import { CommandContext, isWorkspaceOpen, openZeppelinHelper, required, setCommandContext } from './helpers';
|
||||
import { CancellationEvent } from './Models';
|
||||
import { Output } from './Output';
|
||||
import { ChangelogPage, RequirementsPage, WelcomePage } from './pages';
|
||||
import {
|
||||
AdapterType,
|
||||
ContractDB,
|
||||
GanacheService,
|
||||
InfuraServiceClient,
|
||||
MnemonicRepository,
|
||||
TreeManager,
|
||||
TreeService,
|
||||
} from './services';
|
||||
import { Telemetry } from './TelemetryClient';
|
||||
import { NetworkNodeView, ProjectView } from './ViewItems';
|
||||
|
||||
import { DebuggerConfiguration } from './debugAdapter/configuration/debuggerConfiguration';
|
||||
|
||||
export async function activate(context: ExtensionContext) {
|
||||
if (process.env.CODE_TEST) {
|
||||
return;
|
||||
}
|
||||
|
||||
Constants.initialize(context);
|
||||
DebuggerConfiguration.initialize(context);
|
||||
await ContractDB.initialize(AdapterType.IN_MEMORY);
|
||||
await InfuraServiceClient.initialize(context.globalState);
|
||||
MnemonicRepository.initialize(context.globalState);
|
||||
TreeManager.initialize(context.globalState);
|
||||
TreeService.initialize('AzureBlockchain');
|
||||
|
||||
setCommandContext(CommandContext.Enabled, true);
|
||||
setCommandContext(CommandContext.IsWorkspaceOpen, isWorkspaceOpen());
|
||||
|
||||
const welcomePage = new WelcomePage(context);
|
||||
const requirementsPage = new RequirementsPage(context);
|
||||
const changelogPage = new ChangelogPage(context);
|
||||
|
||||
await welcomePage.checkAndShow();
|
||||
await changelogPage.checkAndShow();
|
||||
|
||||
//#region azureBlockchain extension commands
|
||||
const refresh = commands.registerCommand('azureBlockchainService.refresh', (element) => {
|
||||
TreeService.refresh(element);
|
||||
});
|
||||
const showWelcomePage = commands.registerCommand('azureBlockchainService.showWelcomePage', async () => {
|
||||
return welcomePage.show();
|
||||
});
|
||||
const showRequirementsPage = commands.registerCommand('azureBlockchainService.showRequirementsPage',
|
||||
async (checkShowOnStartup: boolean) => {
|
||||
return checkShowOnStartup ? await requirementsPage.checkAndShow() : await requirementsPage.show();
|
||||
});
|
||||
//#endregion
|
||||
|
||||
//#region Ganache extension commands
|
||||
const startGanacheServer = commands.registerCommand('azureBlockchainService.startGanacheServer',
|
||||
async (viewItem?: ProjectView) => {
|
||||
await tryExecute(() => GanacheCommands.startGanacheCmd(viewItem));
|
||||
});
|
||||
|
||||
const stopGanacheServer = commands.registerCommand('azureBlockchainService.stopGanacheServer',
|
||||
async (viewItem?: ProjectView) => {
|
||||
await tryExecute(() => GanacheCommands.stopGanacheCmd(viewItem));
|
||||
});
|
||||
//#endregion
|
||||
|
||||
//#region truffle commands
|
||||
const newSolidityProject = commands.registerCommand('truffle.newSolidityProject', async () => {
|
||||
await tryExecute(() => ProjectCommands.newSolidityProject());
|
||||
});
|
||||
const buildContracts = commands.registerCommand('truffle.buildContracts', async () => {
|
||||
await tryExecute(() => TruffleCommands.buildContracts());
|
||||
});
|
||||
const deployContracts = commands.registerCommand('truffle.deployContracts', async () => {
|
||||
await tryExecute(() => TruffleCommands.deployContracts());
|
||||
});
|
||||
const copyByteCode = commands.registerCommand('contract.copyByteCode', async (uri: Uri) => {
|
||||
await tryExecute(() => TruffleCommands.writeBytecodeToBuffer(uri));
|
||||
});
|
||||
const copyDeployedByteCode = commands.registerCommand('contract.copyDeployedByteCode', async (uri: Uri) => {
|
||||
await tryExecute(() => TruffleCommands.writeDeployedBytecodeToBuffer(uri));
|
||||
});
|
||||
const copyABI = commands.registerCommand('contract.copyABI', async (uri: Uri) => {
|
||||
await tryExecute(() => TruffleCommands.writeAbiToBuffer(uri));
|
||||
});
|
||||
const copyRPCEndpointAddress = commands.registerCommand('azureBlockchainService.copyRPCEndpointAddress',
|
||||
async (viewItem: NetworkNodeView) => {
|
||||
await tryExecute(() => TruffleCommands.writeRPCEndpointAddressToBuffer(viewItem));
|
||||
});
|
||||
const getPrivateKeyFromMnemonic = commands.registerCommand('azureBlockchainService.getPrivateKey', async () => {
|
||||
await tryExecute(() => TruffleCommands.getPrivateKeyFromMnemonic());
|
||||
});
|
||||
//#endregion
|
||||
|
||||
//#region services with dialog
|
||||
const createProject = commands.registerCommand('azureBlockchainService.createProject', async () => {
|
||||
await tryExecute(() => ServiceCommands.createProject());
|
||||
});
|
||||
const connectProject = commands.registerCommand('azureBlockchainService.connectProject', async () => {
|
||||
await tryExecute(() => ServiceCommands.connectProject());
|
||||
});
|
||||
const disconnectProject = commands.registerCommand('azureBlockchainService.disconnectProject',
|
||||
async (viewItem: ProjectView) => {
|
||||
await tryExecute(() => ServiceCommands.disconnectProject(viewItem));
|
||||
});
|
||||
const openAtAzurePortal = commands.registerCommand('azureBlockchainService.openAtAzurePortal',
|
||||
async (viewItem: NetworkNodeView) => ServiceCommands.openAtAzurePortal(viewItem));
|
||||
//#endregion
|
||||
|
||||
//#region Infura commands
|
||||
const signInToInfuraAccount = commands.registerCommand('azureBlockchainService.signInToInfuraAccount', async () => {
|
||||
await tryExecute(() => InfuraCommands.signIn());
|
||||
});
|
||||
const signOutOfInfuraAccount = commands.registerCommand('azureBlockchainService.signOutOfInfuraAccount', async () => {
|
||||
await tryExecute(() => InfuraCommands.signOut());
|
||||
});
|
||||
const showProjectsFromInfuraAccount = commands.registerCommand(
|
||||
'azureBlockchainService.showProjectsFromInfuraAccount',
|
||||
async () => {
|
||||
await tryExecute(() => InfuraCommands.showProjectsFromAccount());
|
||||
});
|
||||
//#endregion
|
||||
|
||||
//#region contract commands
|
||||
const showSmartContractPage = commands.registerCommand(
|
||||
'azureBlockchainService.showSmartContractPage',
|
||||
async (contractPath: Uri) => {
|
||||
await tryExecute(() => ContractCommands.showSmartContractPage(context, contractPath));
|
||||
});
|
||||
const createNewBDMApplication = commands.registerCommand('azureBlockchainService.createNewBDMApplication',
|
||||
async (viewItem: ProjectView) => {
|
||||
await tryExecute(() => ServiceCommands.createNewBDMApplication(viewItem));
|
||||
});
|
||||
const deleteBDMApplication = commands.registerCommand('azureBlockchainService.deleteBDMApplication',
|
||||
async (viewItem: NetworkNodeView) => await tryExecute(() => ServiceCommands.deleteBDMApplication(viewItem)));
|
||||
//#endregion
|
||||
|
||||
//#region open zeppelin commands
|
||||
const openZeppelinAddCategory = commands.registerCommand('openZeppelin.addCategory', async () => {
|
||||
await tryExecute(() => OpenZeppelinCommands.addCategory());
|
||||
});
|
||||
//#endregion
|
||||
|
||||
//#region logic app commands
|
||||
const generateMicroservicesWorkflows = commands.registerCommand(
|
||||
'azureBlockchainService.generateMicroservicesWorkflows',
|
||||
async (filePath: Uri | undefined) => {
|
||||
await tryExecute(async () => await LogicAppCommands.generateMicroservicesWorkflows(filePath));
|
||||
});
|
||||
const generateDataPublishingWorkflows = commands.registerCommand(
|
||||
'azureBlockchainService.generateDataPublishingWorkflows',
|
||||
async (filePath: Uri | undefined) => {
|
||||
await tryExecute(async () => await LogicAppCommands.generateDataPublishingWorkflows(filePath));
|
||||
});
|
||||
const generateEventPublishingWorkflows = commands.registerCommand(
|
||||
'azureBlockchainService.generateEventPublishingWorkflows',
|
||||
async (filePath: Uri | undefined) => {
|
||||
await tryExecute(async () => await LogicAppCommands.generateEventPublishingWorkflows(filePath));
|
||||
});
|
||||
const generateReportPublishingWorkflows = commands.registerCommand(
|
||||
'azureBlockchainService.generateReportPublishingWorkflows',
|
||||
async (filePath: Uri | undefined) => {
|
||||
await tryExecute(async () => await LogicAppCommands.generateReportPublishingWorkflows(filePath));
|
||||
});
|
||||
//#endregion
|
||||
|
||||
//#region debugger commands
|
||||
const startDebugger = commands.registerCommand('extension.truffle.debugTransaction', async () => {
|
||||
await tryExecute(() => DebuggerCommands.startSolidityDebugger());
|
||||
});
|
||||
//#endregion
|
||||
|
||||
const subscriptions = [
|
||||
showWelcomePage,
|
||||
showRequirementsPage,
|
||||
showSmartContractPage,
|
||||
refresh,
|
||||
newSolidityProject,
|
||||
buildContracts,
|
||||
deployContracts,
|
||||
createNewBDMApplication,
|
||||
createProject,
|
||||
connectProject,
|
||||
deleteBDMApplication,
|
||||
disconnectProject,
|
||||
copyByteCode,
|
||||
copyDeployedByteCode,
|
||||
copyABI,
|
||||
copyRPCEndpointAddress,
|
||||
startGanacheServer,
|
||||
stopGanacheServer,
|
||||
generateMicroservicesWorkflows,
|
||||
generateDataPublishingWorkflows,
|
||||
generateEventPublishingWorkflows,
|
||||
generateReportPublishingWorkflows,
|
||||
getPrivateKeyFromMnemonic,
|
||||
startDebugger,
|
||||
signInToInfuraAccount,
|
||||
signOutOfInfuraAccount,
|
||||
showProjectsFromInfuraAccount,
|
||||
openZeppelinAddCategory,
|
||||
openAtAzurePortal,
|
||||
];
|
||||
context.subscriptions.push(...subscriptions);
|
||||
|
||||
required.checkAllApps();
|
||||
|
||||
Telemetry.sendEvent(Constants.telemetryEvents.extensionActivated);
|
||||
|
||||
checkAndUpgradeOpenZeppelinAsync();
|
||||
}
|
||||
|
||||
export async function deactivate(): Promise<void> {
|
||||
// This method is called when your extension is deactivated
|
||||
// To dispose of all extensions, vscode provides 5 sec.
|
||||
// Therefore, please, call important dispose functions first and don't use await
|
||||
// For more information see https://github.com/Microsoft/vscode/issues/47881
|
||||
GanacheService.dispose();
|
||||
ContractDB.dispose();
|
||||
Telemetry.dispose();
|
||||
TreeManager.dispose();
|
||||
Output.dispose();
|
||||
}
|
||||
|
||||
async function tryExecute(func: () => Promise<any>, errorMessage: string | null = null): Promise<void> {
|
||||
try {
|
||||
await func();
|
||||
} catch (error) {
|
||||
if (error instanceof CancellationEvent) {
|
||||
return;
|
||||
}
|
||||
window.showErrorMessage(errorMessage || error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkAndUpgradeOpenZeppelinAsync(): Promise<void> {
|
||||
if (await openZeppelinHelper.shouldUpgradeOpenZeppelinAsync()) {
|
||||
await openZeppelinHelper.upgradeOpenZeppelinUserSettingsAsync();
|
||||
await openZeppelinHelper.upgradeOpenZeppelinContractsAsync();
|
||||
}
|
||||
}
|
|
@ -1,151 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as path from 'path';
|
||||
import rewire = require('rewire');
|
||||
import * as sinon from 'sinon';
|
||||
import { commands, ExtensionContext, Uri, window } from 'vscode';
|
||||
import { ContractCommands } from '../../src/commands';
|
||||
import { Constants } from '../../src/Constants';
|
||||
import { CancellationEvent } from '../../src/Models';
|
||||
import * as contractUI from '../../src/pages/ContractUI';
|
||||
import { ContractDB, ContractInstanceWithMetadata } from '../../src/services';
|
||||
import { Contract } from '../../src/services/contract/Contract';
|
||||
import { Network } from '../../src/services/contract/Network';
|
||||
|
||||
describe('ContractCommands tests', () => {
|
||||
const fileUri = {
|
||||
fsPath: path.join(__dirname, 'testData', 'TestContract.json'),
|
||||
} as Uri;
|
||||
|
||||
const contractInstance = new ContractInstanceWithMetadata(
|
||||
new Contract({
|
||||
contractName: 'contractName',
|
||||
networks: [{}],
|
||||
updatedAt: '01-01-2019',
|
||||
}),
|
||||
{ id: 'testnetwork' } as Network,
|
||||
null,
|
||||
);
|
||||
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it('showSmartContractPage throws error when contract does not exist and deploy can`t create contract', async () => {
|
||||
// Arrange
|
||||
sinon.stub(ContractDB, 'getContractInstances').returns(Promise.resolve([]));
|
||||
sinon.stub(window, 'showWarningMessage').returns(Promise.resolve(undefined));
|
||||
|
||||
// Act
|
||||
const action = async () => {
|
||||
await ContractCommands
|
||||
.showSmartContractPage({} as ExtensionContext, fileUri);
|
||||
};
|
||||
|
||||
// Assert
|
||||
await assert.rejects(
|
||||
action,
|
||||
Error,
|
||||
Constants.errorMessageStrings.CompiledContractIsMissing);
|
||||
});
|
||||
|
||||
it('showSmartContractPage throws cancellationEvent when contract does not exist and deploy can create contract, ' +
|
||||
'but user click cancel button',
|
||||
async () => {
|
||||
// Arrange
|
||||
sinon.stub(ContractDB, 'getContractInstances').returns(Promise.resolve([]));
|
||||
sinon.stub(window, 'showWarningMessage')
|
||||
.returns(Promise.resolve({ title: Constants.informationMessage.cancelButton }));
|
||||
|
||||
// Act
|
||||
const action = async () => {
|
||||
await ContractCommands
|
||||
.showSmartContractPage({} as ExtensionContext, fileUri);
|
||||
};
|
||||
|
||||
// Assert
|
||||
await assert.rejects(action, CancellationEvent);
|
||||
});
|
||||
|
||||
it('showSmartContractPage executes deploy contract when contract does not exist, deploy can create contract ' +
|
||||
'and user click deploy button',
|
||||
async () => {
|
||||
// Arrange
|
||||
sinon.stub(ContractDB, 'getContractInstances')
|
||||
.onCall(0)
|
||||
.returns(Promise.resolve([]))
|
||||
.onCall(1)
|
||||
.returns(Promise.resolve([
|
||||
contractInstance,
|
||||
]));
|
||||
sinon.replace(
|
||||
window,
|
||||
'showWarningMessage',
|
||||
sinon.stub().returns(Promise.resolve(Constants.informationMessage.deployButton)),
|
||||
);
|
||||
const executeCommandSpy = sinon.stub(commands, 'executeCommand').callsFake(() => Promise.resolve());
|
||||
sinon.stub(contractUI, 'ContractUI').returns({
|
||||
postMessage: () => undefined,
|
||||
show: () => undefined,
|
||||
});
|
||||
|
||||
// Act
|
||||
await ContractCommands
|
||||
.showSmartContractPage({} as ExtensionContext, fileUri);
|
||||
|
||||
// Assert
|
||||
assert.strictEqual(executeCommandSpy.calledOnce, true, 'showSmartContract should execute deploy contract');
|
||||
});
|
||||
|
||||
it('showSmartContractPage throws error when contract exist without networks and deploy can`t create contract',
|
||||
async () => {
|
||||
// Arrange
|
||||
sinon.stub(ContractDB, 'getContractInstances')
|
||||
.onCall(0)
|
||||
.returns(Promise.resolve([]))
|
||||
.onCall(1)
|
||||
.returns(Promise.resolve([
|
||||
contractInstance,
|
||||
]));
|
||||
sinon.replace(
|
||||
window,
|
||||
'showWarningMessage',
|
||||
sinon.stub().returns(Promise.resolve(Constants.informationMessage.deployButton)),
|
||||
);
|
||||
sinon.stub(commands, 'executeCommand').callsFake(() => Promise.resolve());
|
||||
|
||||
// Act
|
||||
const action = async () => {
|
||||
await ContractCommands
|
||||
.showSmartContractPage({} as ExtensionContext, fileUri);
|
||||
};
|
||||
|
||||
// Assert
|
||||
await assert.rejects(
|
||||
action,
|
||||
Error,
|
||||
Constants.errorMessageStrings.CompiledContractIsMissing);
|
||||
});
|
||||
|
||||
it('showSmartContractPage should show page for contract', async () => {
|
||||
// Arrange
|
||||
sinon.stub(ContractDB, 'getContractInstances')
|
||||
.returns(Promise.resolve([
|
||||
contractInstance,
|
||||
]));
|
||||
const contractCommandsRewire = rewire('../../src/commands/ContractCommands');
|
||||
const showStub = sinon.stub().returns(() => undefined);
|
||||
sinon.stub(contractUI, 'ContractUI').returns({
|
||||
show: showStub,
|
||||
});
|
||||
|
||||
// Act
|
||||
await contractCommandsRewire.ContractCommands
|
||||
.showSmartContractPage({} as ExtensionContext, fileUri);
|
||||
|
||||
// Assert
|
||||
assert.strictEqual(showStub.calledOnce, true, 'showSmartContractPage should create drizzle page');
|
||||
});
|
||||
});
|
Загрузка…
Ссылка в новой задаче