Solidity Compiler in UI (#3279)
* Added new Deploy Contract page // Use Brace in React #2276 * Adding Web Wrokers WIP * Compiling Solidity code // Getting mandatory params #2276 * Working editor and deployment #2276 * WIP : displaying source code * Added Solidity hightling, editor component in UI * Re-adding the standard Deploy Modal #2276 * Using MobX in Contract Edition // Save to Localstorage #2276 * User select Solidity version #2276 * Loading Solidity versions and closing worker properly #2276 * Adds export to solidity editor #2276 * Adding Import to Contract Editor #2276 * Persistent Worker => Don't load twice Solidity Code #2276 * UI Fixes * Editor tweaks * Added Details with ABI in Contract view * Adds Save capabilities to contract editor // WIP on Load #3279 * Working Load and Save contracts... #3231 * Adding loader of Snippets // Export with name #3279 * Added snippets / Importing from files and from URL * Fix wrong ID in saved Contract * Fix lint * Fixed Formal errors as warning #3279 * Fixing lint issues * Use NPM Module for valid URL (fixes linting issue too) * Don't clobber tests.
This commit is contained in:
committed by
Jaco Greeff
parent
5d8f74ed57
commit
0e4ef539fc
17
js/src/views/WriteContract/index.js
Normal file
17
js/src/views/WriteContract/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
export default from './writeContract';
|
||||
174
js/src/views/WriteContract/writeContract.css
Normal file
174
js/src/views/WriteContract/writeContract.css
Normal file
@@ -0,0 +1,174 @@
|
||||
/* Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
/* This file is part of Parity.
|
||||
/*
|
||||
/* Parity is free software: you can redistribute it and/or modify
|
||||
/* it under the terms of the GNU General Public License as published by
|
||||
/* the Free Software Foundation, either version 3 of the License, or
|
||||
/* (at your option) any later version.
|
||||
/*
|
||||
/* Parity is distributed in the hope that it will be useful,
|
||||
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
/* GNU General Public License for more details.
|
||||
/*
|
||||
/* You should have received a copy of the GNU General Public License
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
.outer, .page, .editor {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
font-size: 0.75em;
|
||||
margin-left: 1em;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 1em 0;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
|
||||
> * {
|
||||
margin: 0;
|
||||
|
||||
> h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mainEditor {
|
||||
&:global(.ace-solarized-dark) {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
|
||||
:global(.ace_gutter) {
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
:global(.ace_content) {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.big {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.centeredMessage {
|
||||
width: 100%;
|
||||
height: 75%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.resizing * {
|
||||
cursor: ew-resize !important;
|
||||
user-select: none !important;
|
||||
}
|
||||
|
||||
.editor {
|
||||
width: 0;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
.parameters {
|
||||
width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-right: 0.5em;
|
||||
|
||||
.panel {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
padding: 1em;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.compilation {
|
||||
flex: 1 0 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.errors {
|
||||
flex: 1 0 0;
|
||||
overflow: auto;
|
||||
margin-right: -0.5em;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.messageContainer {
|
||||
padding: 0.5em 0;
|
||||
margin-right: 0.5em;
|
||||
|
||||
&:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.errorPosition {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
padding: 0.25em 0.5em;
|
||||
top: 0;
|
||||
position: relative;
|
||||
margin-bottom: 0.25em;
|
||||
font-size: 0.75em;
|
||||
}
|
||||
}
|
||||
|
||||
.message {
|
||||
font-family: monospace;
|
||||
padding: 0.5em;
|
||||
font-size: 0.9em;
|
||||
white-space: pre;
|
||||
overflow: auto;
|
||||
|
||||
&.error {
|
||||
background-color: rgba(244, 67, 54, 0.5);
|
||||
}
|
||||
|
||||
&.warning {
|
||||
background-color: rgba(255, 235, 59, 0.5);
|
||||
}
|
||||
|
||||
&.formal {
|
||||
background-color: rgba(243, 156, 18, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.messagesHeader {
|
||||
margin-bottom: 0.25em;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.sliderContainer {
|
||||
flex: 0 0 .8em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.slider {
|
||||
width: 0.4em;
|
||||
height: 3em;
|
||||
border-radius: 0.75em;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
content: ' ';
|
||||
|
||||
&:hover {
|
||||
cursor: ew-resize;
|
||||
}
|
||||
}
|
||||
}
|
||||
502
js/src/views/WriteContract/writeContract.js
Normal file
502
js/src/views/WriteContract/writeContract.js
Normal file
@@ -0,0 +1,502 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { PropTypes, Component } from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { MenuItem } from 'material-ui';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import CircularProgress from 'material-ui/CircularProgress';
|
||||
import moment from 'moment';
|
||||
|
||||
import ContentClear from 'material-ui/svg-icons/content/clear';
|
||||
import SaveIcon from 'material-ui/svg-icons/content/save';
|
||||
import ListIcon from 'material-ui/svg-icons/action/view-list';
|
||||
import SettingsIcon from 'material-ui/svg-icons/action/settings';
|
||||
import SendIcon from 'material-ui/svg-icons/content/send';
|
||||
|
||||
import { Actionbar, ActionbarExport, ActionbarImport, Button, Editor, Page, Select, Input } from '../../ui';
|
||||
import { DeployContract, SaveContract, LoadContract } from '../../modals';
|
||||
|
||||
import { setupWorker } from '../../redux/providers/compilerActions';
|
||||
|
||||
import WriteContractStore from './writeContractStore';
|
||||
import styles from './writeContract.css';
|
||||
|
||||
@observer
|
||||
class WriteContract extends Component {
|
||||
|
||||
static propTypes = {
|
||||
accounts: PropTypes.object.isRequired,
|
||||
setupWorker: PropTypes.func.isRequired,
|
||||
worker: PropTypes.object
|
||||
};
|
||||
|
||||
store = new WriteContractStore();
|
||||
|
||||
state = {
|
||||
resizing: false,
|
||||
size: 65
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
const { setupWorker, worker } = this.props;
|
||||
setupWorker();
|
||||
|
||||
if (worker) {
|
||||
this.store.setCompiler(worker);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.store.setEditor(this.refs.editor);
|
||||
|
||||
// Wait for editor to be loaded
|
||||
window.setTimeout(() => {
|
||||
this.store.resizeEditor();
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (!this.props.worker && nextProps.worker) {
|
||||
this.store.setCompiler(nextProps.worker);
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { sourcecode } = this.store;
|
||||
const { size, resizing } = this.state;
|
||||
|
||||
const annotations = this.store.annotations
|
||||
.slice()
|
||||
.filter((a) => a.contract === '');
|
||||
|
||||
return (
|
||||
<div className={ styles.outer }>
|
||||
{ this.renderDeployModal() }
|
||||
{ this.renderSaveModal() }
|
||||
{ this.renderLoadModal() }
|
||||
|
||||
{ this.renderActionBar() }
|
||||
<Page className={ styles.page }>
|
||||
<div
|
||||
className={ `${styles.container} ${resizing ? styles.resizing : ''}` }
|
||||
onMouseMove={ this.handleResize }
|
||||
onMouseUp={ this.handleStopResize }
|
||||
onMouseLeave={ this.handleStopResize }
|
||||
>
|
||||
<div
|
||||
className={ styles.editor }
|
||||
style={ { flex: `${size}%` } }
|
||||
>
|
||||
<h2>{ this.renderTitle() }</h2>
|
||||
|
||||
<Editor
|
||||
ref='editor'
|
||||
onChange={ this.store.handleEditSourcecode }
|
||||
onExecute={ this.store.handleCompile }
|
||||
annotations={ annotations }
|
||||
value={ sourcecode }
|
||||
className={ styles.mainEditor }
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={ styles.sliderContainer }>
|
||||
<span
|
||||
className={ styles.slider }
|
||||
onMouseDown={ this.handleStartResize }
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={ styles.parameters }
|
||||
style={ { flex: `${100 - size}%` } }
|
||||
>
|
||||
<h2>Parameters</h2>
|
||||
{ this.renderParameters() }
|
||||
</div>
|
||||
</div>
|
||||
</Page>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderTitle () {
|
||||
const { selectedContract } = this.store;
|
||||
|
||||
if (!selectedContract || !selectedContract.name) {
|
||||
return 'New Solidity Contract';
|
||||
}
|
||||
|
||||
return (
|
||||
<span>
|
||||
{ selectedContract.name }
|
||||
<span
|
||||
className={ styles.timestamp }
|
||||
title={ `saved @ ${(new Date(selectedContract.timestamp)).toISOString()}` }
|
||||
>
|
||||
(saved { moment(selectedContract.timestamp).fromNow() })
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
renderActionBar () {
|
||||
const { sourcecode, selectedContract } = this.store;
|
||||
|
||||
const filename = selectedContract && selectedContract.name
|
||||
? selectedContract.name
|
||||
.replace(/[^a-z0-9]+/gi, '-')
|
||||
.replace(/-$/, '')
|
||||
.toLowerCase()
|
||||
: 'contract.sol';
|
||||
|
||||
const extension = /\.sol$/.test(filename) ? '' : '.sol';
|
||||
|
||||
const buttons = [
|
||||
<Button
|
||||
icon={ <ContentClear /> }
|
||||
label='New'
|
||||
key='newContract'
|
||||
onClick={ this.store.handleNewContract }
|
||||
/>,
|
||||
<Button
|
||||
icon={ <ListIcon /> }
|
||||
label='Load'
|
||||
key='loadContract'
|
||||
onClick={ this.store.handleOpenLoadModal }
|
||||
/>,
|
||||
<Button
|
||||
icon={ <SaveIcon /> }
|
||||
label='Save'
|
||||
key='saveContract'
|
||||
onClick={ this.store.handleSaveContract }
|
||||
/>,
|
||||
<ActionbarExport
|
||||
key='exportSourcecode'
|
||||
content={ sourcecode }
|
||||
filename={ `${filename}${extension}` }
|
||||
/>,
|
||||
<ActionbarImport
|
||||
key='importSourcecode'
|
||||
title='Import Solidity code'
|
||||
onConfirm={ this.store.handleImport }
|
||||
renderValidation={ this.renderImportValidation }
|
||||
/>
|
||||
];
|
||||
|
||||
return (
|
||||
<Actionbar
|
||||
title='Write a Contract'
|
||||
buttons={ buttons }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderImportValidation = (content) => {
|
||||
return (
|
||||
<Editor
|
||||
readOnly
|
||||
value={ content }
|
||||
maxLines={ 20 }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderParameters () {
|
||||
const { compiling, contract, selectedBuild, loading } = this.store;
|
||||
|
||||
if (selectedBuild < 0) {
|
||||
return (
|
||||
<div className={ `${styles.panel} ${styles.centeredMessage}` }>
|
||||
<CircularProgress size={ 80 } thickness={ 5 } />
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
const { longVersion } = this.store.builds[selectedBuild];
|
||||
|
||||
return (
|
||||
<div className={ styles.panel }>
|
||||
<div className={ styles.centeredMessage }>
|
||||
<CircularProgress size={ 80 } thickness={ 5 } />
|
||||
<p>Loading Solidity { longVersion }</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.panel }>
|
||||
<div>
|
||||
<Button
|
||||
icon={ <SettingsIcon /> }
|
||||
label='Compile'
|
||||
onClick={ this.store.handleCompile }
|
||||
primary={ false }
|
||||
disabled={ compiling }
|
||||
/>
|
||||
{
|
||||
contract
|
||||
? <Button
|
||||
icon={ <SendIcon /> }
|
||||
label='Deploy'
|
||||
onClick={ this.store.handleOpenDeployModal }
|
||||
primary={ false }
|
||||
/>
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
{ this.renderSolidityVersions() }
|
||||
{ this.renderCompilation() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderSolidityVersions () {
|
||||
const { builds, selectedBuild } = this.store;
|
||||
|
||||
const buildsList = builds.map((build, index) => (
|
||||
<MenuItem
|
||||
key={ index }
|
||||
value={ index }
|
||||
label={ build.release ? build.version : build.longVersion }
|
||||
>
|
||||
{
|
||||
build.release
|
||||
? (<span className={ styles.big }>{ build.version }</span>)
|
||||
: build.longVersion
|
||||
}
|
||||
</MenuItem>
|
||||
));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Select
|
||||
label='Select a Solidity version'
|
||||
value={ selectedBuild }
|
||||
onChange={ this.store.handleSelectBuild }
|
||||
>
|
||||
{ buildsList }
|
||||
</Select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderDeployModal () {
|
||||
const { showDeployModal, contract, sourcecode } = this.store;
|
||||
|
||||
if (!showDeployModal) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<DeployContract
|
||||
abi={ contract.interface }
|
||||
code={ `0x${contract.bytecode}` }
|
||||
source={ sourcecode }
|
||||
accounts={ this.props.accounts }
|
||||
onClose={ this.store.handleCloseDeployModal }
|
||||
readOnly
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderLoadModal () {
|
||||
const { showLoadModal } = this.store;
|
||||
|
||||
if (!showLoadModal) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<LoadContract
|
||||
onLoad={ this.store.handleLoadContract }
|
||||
onDelete={ this.store.handleDeleteContract }
|
||||
onClose={ this.store.handleCloseLoadModal }
|
||||
contracts={ this.store.savedContracts }
|
||||
snippets={ this.store.snippets }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderSaveModal () {
|
||||
const { showSaveModal, sourcecode } = this.store;
|
||||
|
||||
if (!showSaveModal) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SaveContract
|
||||
sourcecode={ sourcecode }
|
||||
onSave={ this.store.handleSaveNewContract }
|
||||
onClose={ this.store.handleCloseSaveModal }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderCompilation () {
|
||||
const { compiled, contracts, compiling, contractIndex, contract } = this.store;
|
||||
|
||||
if (compiling) {
|
||||
return (
|
||||
<div className={ styles.centeredMessage }>
|
||||
<CircularProgress size={ 80 } thickness={ 5 } />
|
||||
<p>Compiling...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!compiled) {
|
||||
return (
|
||||
<div className={ styles.centeredMessage }>
|
||||
<p>Please compile the source code.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!contracts) {
|
||||
return this.renderErrors();
|
||||
}
|
||||
|
||||
const contractKeys = Object.keys(contracts);
|
||||
|
||||
if (contractKeys.length === 0) {
|
||||
return (
|
||||
<div className={ styles.centeredMessage }>
|
||||
<p>No contract has been found.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const contractsList = contractKeys.map((name, index) => (
|
||||
<MenuItem
|
||||
key={ index }
|
||||
value={ index }
|
||||
label={ name }
|
||||
>
|
||||
{ name }
|
||||
</MenuItem>
|
||||
));
|
||||
|
||||
return (
|
||||
<div className={ styles.compilation }>
|
||||
<Select
|
||||
label='Select a contract'
|
||||
value={ contractIndex }
|
||||
onChange={ this.store.handleSelectContract }
|
||||
>
|
||||
{ contractsList }
|
||||
</Select>
|
||||
{ this.renderContract(contract) }
|
||||
|
||||
<h4 className={ styles.messagesHeader }>Compiler messages</h4>
|
||||
{ this.renderErrors() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderContract (contract) {
|
||||
const { bytecode } = contract;
|
||||
const abi = contract.interface;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
readOnly
|
||||
value={ abi }
|
||||
label='ABI Interface'
|
||||
/>
|
||||
|
||||
<Input
|
||||
readOnly
|
||||
value={ `0x${bytecode}` }
|
||||
label='Bytecode'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderErrors () {
|
||||
const { annotations } = this.store;
|
||||
|
||||
const body = annotations.map((annotation, index) => {
|
||||
const { text, row, column, contract, type, formal } = annotation;
|
||||
const classType = formal ? 'formal' : type;
|
||||
const classes = [ styles.message, styles[classType] ];
|
||||
|
||||
return (
|
||||
<div key={ index } className={ styles.messageContainer }>
|
||||
<div className={ classes.join(' ') }>{ text }</div>
|
||||
<span className={ styles.errorPosition }>
|
||||
{ contract ? `[ ${contract} ] ` : '' }
|
||||
{ row }: { column }
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={ styles.errors }>
|
||||
{ body }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleStartResize = () => {
|
||||
this.setState({ resizing: true });
|
||||
}
|
||||
|
||||
handleStopResize = () => {
|
||||
this.setState({ resizing: false });
|
||||
}
|
||||
|
||||
handleResize = (event) => {
|
||||
if (!this.state.resizing) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { pageX, currentTarget } = event;
|
||||
const { width, left } = currentTarget.getBoundingClientRect();
|
||||
|
||||
const x = pageX - left;
|
||||
|
||||
this.setState({ size: 100 * x / width });
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { accounts } = state.personal;
|
||||
const { worker } = state.compiler;
|
||||
return { accounts, worker };
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({
|
||||
setupWorker
|
||||
}, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(WriteContract);
|
||||
373
js/src/views/WriteContract/writeContractStore.js
Normal file
373
js/src/views/WriteContract/writeContractStore.js
Normal file
@@ -0,0 +1,373 @@
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { action, observable } from 'mobx';
|
||||
import store from 'store';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
const WRITE_CONTRACT_STORE_KEY = '_parity::writeContractStore';
|
||||
|
||||
const SNIPPETS = {
|
||||
snippet0: {
|
||||
name: 'Token.sol',
|
||||
description: 'Standard ERP20 Token Contract',
|
||||
id: 'snippet0', sourcecode: require('raw!../../contracts/snippets/token.sol')
|
||||
},
|
||||
snippet1: {
|
||||
name: 'StandardToken.sol',
|
||||
description: 'Implementation of ERP20 Token Contract',
|
||||
id: 'snippet1', sourcecode: require('raw!../../contracts/snippets/standard-token.sol')
|
||||
},
|
||||
snippet2: {
|
||||
name: 'HumanStandardToken.sol',
|
||||
description: 'Implementation of the Human Token Contract',
|
||||
id: 'snippet2', sourcecode: require('raw!../../contracts/snippets/human-standard-token.sol')
|
||||
}
|
||||
};
|
||||
|
||||
export default class WriteContractStore {
|
||||
|
||||
@observable sourcecode = '';
|
||||
|
||||
@observable compiled = false;
|
||||
@observable compiling = false;
|
||||
@observable loading = true;
|
||||
|
||||
@observable contractIndex = -1;
|
||||
@observable contract = null;
|
||||
@observable contracts = {};
|
||||
|
||||
@observable errors = [];
|
||||
@observable annotations = [];
|
||||
|
||||
@observable builds = [];
|
||||
@observable selectedBuild = -1;
|
||||
|
||||
@observable showDeployModal = false;
|
||||
@observable showSaveModal = false;
|
||||
@observable showLoadModal = false;
|
||||
|
||||
@observable savedContracts = {};
|
||||
@observable selectedContract = {};
|
||||
|
||||
snippets = SNIPPETS;
|
||||
|
||||
constructor () {
|
||||
this.reloadContracts();
|
||||
this.fetchSolidityVersions();
|
||||
|
||||
this.debouncedCompile = debounce(this.handleCompile, 1000);
|
||||
}
|
||||
|
||||
@action setEditor (editor) {
|
||||
this.editor = editor;
|
||||
}
|
||||
|
||||
@action setCompiler (compiler) {
|
||||
this.compiler = compiler;
|
||||
|
||||
this.compiler.onmessage = (event) => {
|
||||
const message = JSON.parse(event.data);
|
||||
|
||||
switch (message.event) {
|
||||
case 'compiled':
|
||||
this.parseCompiled(message.data);
|
||||
break;
|
||||
case 'loading':
|
||||
this.parseLoading(message.data);
|
||||
break;
|
||||
case 'try-again':
|
||||
this.handleCompile();
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fetchSolidityVersions () {
|
||||
fetch('https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/bin/list.json')
|
||||
.then((r) => r.json())
|
||||
.then((data) => {
|
||||
const { builds, releases, latestRelease } = data;
|
||||
let latestIndex = -1;
|
||||
|
||||
this.builds = builds.reverse().map((build, index) => {
|
||||
if (releases[build.version] === build.path) {
|
||||
build.release = true;
|
||||
|
||||
if (build.version === latestRelease) {
|
||||
build.latest = true;
|
||||
this.loadSolidityVersion(build);
|
||||
latestIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
return build;
|
||||
});
|
||||
|
||||
this.selectedBuild = latestIndex;
|
||||
});
|
||||
}
|
||||
|
||||
@action closeWorker = () => {
|
||||
this.compiler.postMessage(JSON.stringify({
|
||||
action: 'close'
|
||||
}));
|
||||
}
|
||||
|
||||
@action handleImport = (sourcecode) => {
|
||||
this.reloadContracts(-1, sourcecode);
|
||||
}
|
||||
|
||||
@action handleSelectBuild = (_, index, value) => {
|
||||
this.selectedBuild = value;
|
||||
this.loadSolidityVersion(this.builds[value]);
|
||||
}
|
||||
|
||||
@action loadSolidityVersion = (build) => {
|
||||
this.compiler.postMessage(JSON.stringify({
|
||||
action: 'load',
|
||||
data: build
|
||||
}));
|
||||
}
|
||||
|
||||
@action handleOpenDeployModal = () => {
|
||||
this.showDeployModal = true;
|
||||
}
|
||||
|
||||
@action handleCloseDeployModal = () => {
|
||||
this.showDeployModal = false;
|
||||
}
|
||||
|
||||
@action handleOpenLoadModal = () => {
|
||||
this.showLoadModal = true;
|
||||
}
|
||||
|
||||
@action handleCloseLoadModal = () => {
|
||||
this.showLoadModal = false;
|
||||
}
|
||||
|
||||
@action handleOpenSaveModal = () => {
|
||||
this.showSaveModal = true;
|
||||
}
|
||||
|
||||
@action handleCloseSaveModal = () => {
|
||||
this.showSaveModal = false;
|
||||
}
|
||||
|
||||
@action handleSelectContract = (_, index, value) => {
|
||||
this.contractIndex = value;
|
||||
this.contract = this.contracts[Object.keys(this.contracts)[value]];
|
||||
}
|
||||
|
||||
@action handleCompile = () => {
|
||||
this.compiled = false;
|
||||
this.compiling = true;
|
||||
|
||||
const build = this.builds[this.selectedBuild];
|
||||
|
||||
if (this.compiler && typeof this.compiler.postMessage === 'function') {
|
||||
this.sendFilesToWorker();
|
||||
|
||||
this.compiler.postMessage(JSON.stringify({
|
||||
action: 'compile',
|
||||
data: {
|
||||
sourcecode: this.sourcecode,
|
||||
build: build
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
parseErrors = (data, formal = false) => {
|
||||
const regex = /^(.*):(\d+):(\d+):\s*([a-z]+):\s*((.|[\r\n])+)$/i;
|
||||
|
||||
return (data || [])
|
||||
.filter((e) => regex.test(e))
|
||||
.map((error, index) => {
|
||||
const match = regex.exec(error);
|
||||
|
||||
const contract = match[1];
|
||||
const row = parseInt(match[2]) - 1;
|
||||
const column = parseInt(match[3]);
|
||||
|
||||
const type = formal ? 'warning' : match[4].toLowerCase();
|
||||
const text = match[5];
|
||||
|
||||
return {
|
||||
contract,
|
||||
row, column,
|
||||
type, text,
|
||||
formal
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@action parseCompiled = (data) => {
|
||||
const { contracts } = data;
|
||||
|
||||
const { errors = [] } = data;
|
||||
const errorAnnotations = this.parseErrors(errors);
|
||||
const formalAnnotations = this.parseErrors(data.formal && data.formal.errors, true);
|
||||
|
||||
const annotations = [].concat(
|
||||
errorAnnotations,
|
||||
formalAnnotations
|
||||
);
|
||||
|
||||
if (annotations.findIndex((a) => /__parity_tryAgain/.test(a.text)) > -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const contractKeys = Object.keys(contracts || {});
|
||||
|
||||
this.contract = contractKeys.length ? contracts[contractKeys[0]] : null;
|
||||
this.contractIndex = contractKeys.length ? 0 : -1;
|
||||
|
||||
this.contracts = contracts;
|
||||
this.errors = errors;
|
||||
this.annotations = annotations;
|
||||
|
||||
this.compiled = true;
|
||||
this.compiling = false;
|
||||
}
|
||||
|
||||
@action parseLoading = (isLoading) => {
|
||||
this.loading = isLoading;
|
||||
|
||||
if (!isLoading) {
|
||||
this.handleCompile();
|
||||
}
|
||||
}
|
||||
|
||||
@action handleEditSourcecode = (value, compile = false) => {
|
||||
this.sourcecode = value;
|
||||
|
||||
const localStore = store.get(WRITE_CONTRACT_STORE_KEY) || {};
|
||||
store.set(WRITE_CONTRACT_STORE_KEY, {
|
||||
...localStore,
|
||||
current: value
|
||||
});
|
||||
|
||||
if (compile) {
|
||||
this.handleCompile();
|
||||
} else {
|
||||
this.debouncedCompile();
|
||||
}
|
||||
}
|
||||
|
||||
@action handleSaveContract = () => {
|
||||
if (this.selectedContract && this.selectedContract.id !== undefined) {
|
||||
return this.handleSaveNewContract({
|
||||
...this.selectedContract,
|
||||
sourcecode: this.sourcecode
|
||||
});
|
||||
}
|
||||
|
||||
return this.handleOpenSaveModal();
|
||||
}
|
||||
|
||||
getId (contracts) {
|
||||
return Object.values(contracts)
|
||||
.map((c) => c.id)
|
||||
.reduce((max, id) => Math.max(max, id), 0) + 1;
|
||||
}
|
||||
|
||||
@action handleSaveNewContract = (data) => {
|
||||
const { name, sourcecode, id } = data;
|
||||
|
||||
const localStore = store.get(WRITE_CONTRACT_STORE_KEY) || {};
|
||||
const savedContracts = localStore.saved || {};
|
||||
const cId = (id !== undefined)
|
||||
? id
|
||||
: this.getId(savedContracts);
|
||||
|
||||
store.set(WRITE_CONTRACT_STORE_KEY, {
|
||||
...localStore,
|
||||
saved: {
|
||||
...savedContracts,
|
||||
[ cId ]: { sourcecode, id: cId, name, timestamp: Date.now() }
|
||||
}
|
||||
});
|
||||
|
||||
this.reloadContracts(cId);
|
||||
}
|
||||
|
||||
@action reloadContracts = (id, sourcecode) => {
|
||||
const localStore = store.get(WRITE_CONTRACT_STORE_KEY) || {};
|
||||
this.savedContracts = localStore.saved || {};
|
||||
|
||||
const cId = id !== undefined ? id : localStore.currentId;
|
||||
|
||||
this.selectedContract = this.savedContracts[cId] || {};
|
||||
this.sourcecode = sourcecode !== undefined
|
||||
? sourcecode
|
||||
: this.selectedContract.sourcecode || localStore.current || '';
|
||||
|
||||
store.set(WRITE_CONTRACT_STORE_KEY, {
|
||||
...localStore,
|
||||
currentId: this.selectedContract ? cId : null,
|
||||
current: this.sourcecode
|
||||
});
|
||||
|
||||
this.handleCompile();
|
||||
this.resizeEditor();
|
||||
}
|
||||
|
||||
@action handleLoadContract = (contract) => {
|
||||
const { sourcecode, id } = contract;
|
||||
this.reloadContracts(id, sourcecode);
|
||||
}
|
||||
|
||||
@action handleDeleteContract = (id) => {
|
||||
const localStore = store.get(WRITE_CONTRACT_STORE_KEY) || {};
|
||||
|
||||
const savedContracts = Object.assign({}, localStore.saved || {});
|
||||
|
||||
if (savedContracts[id]) {
|
||||
delete savedContracts[id];
|
||||
}
|
||||
|
||||
store.set(WRITE_CONTRACT_STORE_KEY, {
|
||||
...localStore,
|
||||
saved: savedContracts
|
||||
});
|
||||
|
||||
this.reloadContracts();
|
||||
}
|
||||
|
||||
@action handleNewContract = () => {
|
||||
this.reloadContracts(-1, '');
|
||||
}
|
||||
|
||||
@action resizeEditor = () => {
|
||||
try {
|
||||
this.editor.refs.brace.editor.resize();
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
sendFilesToWorker = () => {
|
||||
const files = [].concat(
|
||||
Object.values(this.snippets),
|
||||
Object.values(this.savedContracts)
|
||||
);
|
||||
|
||||
this.compiler.postMessage(JSON.stringify({
|
||||
action: 'setFiles',
|
||||
data: files
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user