Merge pull request #3912 from ethcore/ng-contract-dev
Fixing Contract Development
This commit is contained in:
commit
854cc604d9
@ -117,6 +117,7 @@
|
||||
"react-hot-loader": "3.0.0-beta.6",
|
||||
"react-intl-aggregate-webpack-plugin": "0.0.1",
|
||||
"rucksack-css": "0.9.1",
|
||||
"serviceworker-webpack-plugin": "0.1.7",
|
||||
"sinon": "1.17.6",
|
||||
"sinon-as-promised": "4.0.2",
|
||||
"sinon-chai": "2.8.0",
|
||||
@ -156,6 +157,7 @@
|
||||
"mobx-react-devtools": "4.2.10",
|
||||
"moment": "2.17.0",
|
||||
"phoneformat.js": "1.0.3",
|
||||
"promise-worker": "1.1.1",
|
||||
"push.js": "0.0.11",
|
||||
"qs": "6.3.0",
|
||||
"react": "15.4.1",
|
||||
@ -182,7 +184,6 @@
|
||||
"valid-url": "1.0.9",
|
||||
"validator": "6.2.0",
|
||||
"web3": "0.17.0-beta",
|
||||
"whatwg-fetch": "2.0.1",
|
||||
"worker-loader": "0.7.1"
|
||||
"whatwg-fetch": "2.0.1"
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,26 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import CompilerWorker from 'worker-loader!./compilerWorker.js';
|
||||
import PromiseWorker from 'promise-worker';
|
||||
import runtime from 'serviceworker-webpack-plugin/lib/runtime';
|
||||
|
||||
let workerRegistration;
|
||||
|
||||
// Setup the Service Worker
|
||||
if ('serviceWorker' in navigator) {
|
||||
workerRegistration = runtime
|
||||
.register()
|
||||
.then(() => navigator.serviceWorker.ready)
|
||||
.then((registration) => {
|
||||
const _worker = registration.active;
|
||||
_worker.controller = registration.active;
|
||||
const worker = new PromiseWorker(_worker);
|
||||
|
||||
return worker;
|
||||
});
|
||||
} else {
|
||||
workerRegistration = Promise.reject('Service Worker is not available in your browser.');
|
||||
}
|
||||
|
||||
export function setWorker (worker) {
|
||||
return {
|
||||
@ -23,6 +42,13 @@ export function setWorker (worker) {
|
||||
};
|
||||
}
|
||||
|
||||
export function setError (error) {
|
||||
return {
|
||||
type: 'setError',
|
||||
error
|
||||
};
|
||||
}
|
||||
|
||||
export function setupWorker () {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
@ -31,7 +57,13 @@ export function setupWorker () {
|
||||
return;
|
||||
}
|
||||
|
||||
const worker = new CompilerWorker();
|
||||
dispatch(setWorker(worker));
|
||||
workerRegistration
|
||||
.then((worker) => {
|
||||
dispatch(setWorker(worker));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('sw', error);
|
||||
dispatch(setWorker(null));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -17,13 +17,18 @@
|
||||
import { handleActions } from 'redux-actions';
|
||||
|
||||
const initialState = {
|
||||
worker: null
|
||||
worker: undefined,
|
||||
error: null
|
||||
};
|
||||
|
||||
export default handleActions({
|
||||
setWorker (state, action) {
|
||||
const { worker } = action;
|
||||
|
||||
return Object.assign({}, state, { worker });
|
||||
},
|
||||
|
||||
setError (state, action) {
|
||||
const { error } = action;
|
||||
return Object.assign({}, state, { error });
|
||||
}
|
||||
}, initialState);
|
||||
|
@ -1,177 +0,0 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (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 solc from 'solc/browser-wrapper';
|
||||
import { isWebUri } from 'valid-url';
|
||||
|
||||
self.solcVersions = {};
|
||||
self.files = {};
|
||||
self.lastCompile = {
|
||||
sourcecode: '',
|
||||
result: '',
|
||||
version: ''
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
onmessage = (event) => {
|
||||
const message = JSON.parse(event.data);
|
||||
|
||||
switch (message.action) {
|
||||
case 'compile':
|
||||
compile(message.data);
|
||||
break;
|
||||
case 'load':
|
||||
load(message.data);
|
||||
break;
|
||||
case 'setFiles':
|
||||
setFiles(message.data);
|
||||
break;
|
||||
case 'close':
|
||||
close();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
function setFiles (files) {
|
||||
const prevFiles = self.files;
|
||||
const nextFiles = files.reduce((obj, file) => {
|
||||
obj[file.name] = file.sourcecode;
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
self.files = {
|
||||
...prevFiles,
|
||||
...nextFiles
|
||||
};
|
||||
}
|
||||
|
||||
function findImports (path) {
|
||||
if (self.files[path]) {
|
||||
if (self.files[path].error) {
|
||||
return { error: self.files[path].error };
|
||||
}
|
||||
|
||||
return { contents: self.files[path] };
|
||||
}
|
||||
|
||||
if (isWebUri(path)) {
|
||||
console.log('[worker] fetching', path);
|
||||
|
||||
fetch(path)
|
||||
.then((r) => r.text())
|
||||
.then((c) => {
|
||||
console.log('[worker]', 'got content at ' + path);
|
||||
self.files[path] = c;
|
||||
|
||||
postMessage(JSON.stringify({
|
||||
event: 'try-again'
|
||||
}));
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error('[worker]', 'fetching', path, e);
|
||||
self.files[path] = { error: e };
|
||||
});
|
||||
|
||||
return { error: '__parity_tryAgain' };
|
||||
}
|
||||
|
||||
console.log(`[worker] path ${path} not found...`);
|
||||
return { error: 'File not found' };
|
||||
}
|
||||
|
||||
function compile (data, optimized = 1) {
|
||||
const { sourcecode, build } = data;
|
||||
const { longVersion } = build;
|
||||
|
||||
if (self.lastCompile.sourcecode === sourcecode && self.lastCompile.longVersion === longVersion) {
|
||||
return postMessage(JSON.stringify({
|
||||
event: 'compiled',
|
||||
data: self.lastCompile.result
|
||||
}));
|
||||
}
|
||||
|
||||
fetchSolc(build)
|
||||
.then((compiler) => {
|
||||
const input = {
|
||||
'': sourcecode
|
||||
};
|
||||
|
||||
const compiled = compiler.compile({ sources: input }, optimized, findImports);
|
||||
|
||||
self.lastCompile = {
|
||||
version: longVersion, result: compiled,
|
||||
sourcecode
|
||||
};
|
||||
|
||||
postMessage(JSON.stringify({
|
||||
event: 'compiled',
|
||||
data: compiled
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
function load (build) {
|
||||
postMessage(JSON.stringify({
|
||||
event: 'loading',
|
||||
data: true
|
||||
}));
|
||||
|
||||
fetchSolc(build)
|
||||
.then(() => {
|
||||
postMessage(JSON.stringify({
|
||||
event: 'loading',
|
||||
data: false
|
||||
}));
|
||||
})
|
||||
.catch(() => {
|
||||
postMessage(JSON.stringify({
|
||||
event: 'loading',
|
||||
data: false
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
function fetchSolc (build) {
|
||||
const { path, longVersion } = build;
|
||||
|
||||
if (self.solcVersions[path]) {
|
||||
return Promise.resolve(self.solcVersions[path]);
|
||||
}
|
||||
|
||||
const URL = `https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/bin/${path}`;
|
||||
console.log(`[worker] fetching solc-bin ${longVersion} at ${URL}`);
|
||||
|
||||
return fetch(URL)
|
||||
.then((r) => r.text())
|
||||
.then((code) => {
|
||||
const solcCode = code.replace(/^var Module;/, 'var Module=self.__solcModule;');
|
||||
self.__solcModule = {};
|
||||
|
||||
console.log(`[worker] evaluating ${longVersion}`);
|
||||
|
||||
// eslint-disable-next-line no-eval
|
||||
eval(solcCode);
|
||||
|
||||
console.log(`[worker] done evaluating ${longVersion}`);
|
||||
|
||||
const compiler = solc(self.__solcModule);
|
||||
self.solcVersions[path] = compiler;
|
||||
return compiler;
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error('fetching solc', e);
|
||||
});
|
||||
}
|
144
js/src/serviceWorker.js
Normal file
144
js/src/serviceWorker.js
Normal file
@ -0,0 +1,144 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (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 registerPromiseWorker from 'promise-worker/register';
|
||||
import SolidityUtils from '~/util/solidity';
|
||||
|
||||
const CACHE_NAME = 'parity-cache-v1';
|
||||
|
||||
registerPromiseWorker((msg) => {
|
||||
return handleMessage(msg);
|
||||
});
|
||||
|
||||
self.addEventListener('install', (event) => {
|
||||
event.waitUntil(self.skipWaiting());
|
||||
});
|
||||
|
||||
self.addEventListener('activate', (event) => {
|
||||
event.waitUntil(self.clients.claim());
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', (event) => {
|
||||
const { url } = event.request;
|
||||
|
||||
if (/raw.githubusercontent.com\/ethereum\/solc-bin(.+)list\.json$/.test(url)) {
|
||||
// Return the cached version, but still update it in background
|
||||
return event.respondWith(cachedFetcher(event.request, true));
|
||||
}
|
||||
|
||||
if (/raw.githubusercontent.com\/ethereum\/solc-bin(.+)soljson(.+)\.js$/.test(url)) {
|
||||
return event.respondWith(cachedFetcher(event.request));
|
||||
}
|
||||
});
|
||||
|
||||
self.solc = {};
|
||||
self.files = {};
|
||||
|
||||
function cachedFetcher (request, update = false) {
|
||||
return caches
|
||||
.match(request)
|
||||
.then((response) => {
|
||||
// Return cached response if exists and no
|
||||
// updates needed
|
||||
if (response && !update) {
|
||||
return response;
|
||||
}
|
||||
|
||||
const fetcher = fetch(request.clone())
|
||||
.then((response) => {
|
||||
// Check if we received a valid response
|
||||
if (!response || response.status !== 200) {
|
||||
return response;
|
||||
}
|
||||
|
||||
return caches
|
||||
.open(CACHE_NAME)
|
||||
.then((cache) => {
|
||||
cache.put(request, response.clone());
|
||||
return response;
|
||||
});
|
||||
});
|
||||
|
||||
// Cache hit - return response
|
||||
// Still want to perform the fetch (update)
|
||||
if (response) {
|
||||
return response;
|
||||
}
|
||||
|
||||
return fetcher;
|
||||
});
|
||||
}
|
||||
|
||||
function handleMessage (message) {
|
||||
switch (message.action) {
|
||||
case 'compile':
|
||||
return compile(message.data);
|
||||
|
||||
case 'load':
|
||||
return getCompiler(message.data).then(() => 'ok');
|
||||
|
||||
case 'setFiles':
|
||||
return setFiles(message.data);
|
||||
|
||||
default:
|
||||
console.warn(`unknown action "${message.action}"`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function compile (data) {
|
||||
const { build } = data;
|
||||
|
||||
return getCompiler(build)
|
||||
.then((compiler) => {
|
||||
return SolidityUtils.compile(data, compiler);
|
||||
});
|
||||
}
|
||||
|
||||
function setFiles (files) {
|
||||
const prevFiles = self.files;
|
||||
const nextFiles = files.reduce((obj, file) => {
|
||||
obj[file.name] = file.sourcecode;
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
self.files = {
|
||||
...prevFiles,
|
||||
...nextFiles
|
||||
};
|
||||
|
||||
return 'ok';
|
||||
}
|
||||
|
||||
function getCompiler (build) {
|
||||
const { longVersion } = build;
|
||||
|
||||
const fetcher = (url) => {
|
||||
const request = new Request(url);
|
||||
return cachedFetcher(request);
|
||||
};
|
||||
|
||||
if (!self.solc[longVersion]) {
|
||||
self.solc[longVersion] = SolidityUtils
|
||||
.getCompiler(build, fetcher)
|
||||
.then((compiler) => {
|
||||
self.solc[longVersion] = compiler;
|
||||
return compiler;
|
||||
});
|
||||
}
|
||||
|
||||
return self.solc[longVersion];
|
||||
}
|
89
js/src/util/solidity.js
Normal file
89
js/src/util/solidity.js
Normal file
@ -0,0 +1,89 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (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 solc from 'solc/browser-wrapper';
|
||||
|
||||
export default class SolidityUtils {
|
||||
|
||||
static compile (data, compiler) {
|
||||
const { sourcecode, build, optimize, files } = data;
|
||||
|
||||
const start = Date.now();
|
||||
console.log('[solidity] compiling...');
|
||||
|
||||
const input = {
|
||||
'': sourcecode
|
||||
};
|
||||
|
||||
const findFiles = (path) => {
|
||||
const file = files.find((f) => f.name === path);
|
||||
|
||||
if (file) {
|
||||
return { contents: file.sourcecode };
|
||||
} else {
|
||||
return { error: 'File not found' };
|
||||
}
|
||||
};
|
||||
|
||||
const compiled = compiler.compile({ sources: input }, optimize ? 1 : 0, findFiles);
|
||||
|
||||
const time = Math.round((Date.now() - start) / 100) / 10;
|
||||
console.log(`[solidity] done compiling in ${time}s`);
|
||||
|
||||
compiled.version = build.longVersion;
|
||||
compiled.sourcecode = sourcecode;
|
||||
|
||||
return compiled;
|
||||
}
|
||||
|
||||
static getCompiler (build, _fetcher) {
|
||||
const { longVersion, path } = build;
|
||||
|
||||
const URL = `https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/bin/${path}`;
|
||||
|
||||
const fetcher = typeof _fetcher === 'function'
|
||||
? _fetcher
|
||||
: (url) => fetch(url);
|
||||
|
||||
const isWorker = typeof window !== 'object';
|
||||
|
||||
return fetcher(URL)
|
||||
.then((r) => r.text())
|
||||
.then((code) => {
|
||||
// `window` for main thread, `self` for workers
|
||||
const _self = isWorker ? self : window;
|
||||
_self.Module = {};
|
||||
|
||||
const solcCode = code.replace('var Module;', `var Module=${isWorker ? 'self' : 'window'}.Module;`);
|
||||
|
||||
console.log(`[solidity] evaluating ${longVersion}`);
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line no-eval
|
||||
eval(solcCode);
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
|
||||
console.log(`[solidity] done evaluating ${longVersion}`);
|
||||
|
||||
const compiler = solc(_self.Module);
|
||||
delete _self.Module;
|
||||
|
||||
return compiler;
|
||||
});
|
||||
}
|
||||
}
|
@ -26,6 +26,16 @@
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.toggles {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 1em 0 0;
|
||||
|
||||
> * {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 1em 0;
|
||||
display: flex;
|
||||
@ -45,6 +55,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
background-color: rgba(200, 0, 0, 0.25);
|
||||
padding: 1em 0.5em;
|
||||
margin-top: -0.5em;
|
||||
font-family: monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.mainEditor {
|
||||
&:global(.ace-solarized-dark) {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
@ -87,13 +105,13 @@
|
||||
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;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.compilation {
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
import React, { PropTypes, Component } from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { MenuItem } from 'material-ui';
|
||||
import { MenuItem, Toggle } from 'material-ui';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import CircularProgress from 'material-ui/CircularProgress';
|
||||
@ -42,10 +42,11 @@ class WriteContract extends Component {
|
||||
static propTypes = {
|
||||
accounts: PropTypes.object.isRequired,
|
||||
setupWorker: PropTypes.func.isRequired,
|
||||
worker: PropTypes.object
|
||||
worker: PropTypes.object,
|
||||
workerError: PropTypes.any
|
||||
};
|
||||
|
||||
store = new WriteContractStore();
|
||||
store = WriteContractStore.get();
|
||||
|
||||
state = {
|
||||
resizing: false,
|
||||
@ -56,23 +57,32 @@ class WriteContract extends Component {
|
||||
const { setupWorker, worker } = this.props;
|
||||
setupWorker();
|
||||
|
||||
if (worker) {
|
||||
this.store.setCompiler(worker);
|
||||
if (worker !== undefined) {
|
||||
this.store.setWorker(worker);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.store.setEditor(this.refs.editor);
|
||||
|
||||
if (this.props.workerError) {
|
||||
this.store.setWorkerError(this.props.workerError);
|
||||
}
|
||||
|
||||
// Wait for editor to be loaded
|
||||
window.setTimeout(() => {
|
||||
this.store.resizeEditor();
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Set the worker if not set before (eg. first page loading)
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (!this.props.worker && nextProps.worker) {
|
||||
this.store.setCompiler(nextProps.worker);
|
||||
if (this.props.worker === undefined && nextProps.worker !== undefined) {
|
||||
this.store.setWorker(nextProps.worker);
|
||||
}
|
||||
|
||||
if (this.props.workerError !== nextProps.workerError) {
|
||||
this.store.setWorkerError(nextProps.workerError);
|
||||
}
|
||||
}
|
||||
|
||||
@ -217,7 +227,18 @@ class WriteContract extends Component {
|
||||
}
|
||||
|
||||
renderParameters () {
|
||||
const { compiling, contract, selectedBuild, loading } = this.store;
|
||||
const { compiling, contract, selectedBuild, loading, workerError } = this.store;
|
||||
|
||||
if (workerError) {
|
||||
return (
|
||||
<div className={ styles.panel }>
|
||||
<div className={ styles.centeredMessage }>
|
||||
<p>Unfortuantely, an error occurred...</p>
|
||||
<div className={ styles.error }>{ workerError.toString() }</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (selectedBuild < 0) {
|
||||
return (
|
||||
@ -262,6 +283,24 @@ class WriteContract extends Component {
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
<div className={ styles.toggles }>
|
||||
<div>
|
||||
<Toggle
|
||||
label='Optimize'
|
||||
labelPosition='right'
|
||||
onToggle={ this.store.handleOptimizeToggle }
|
||||
toggled={ this.store.optimize }
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Toggle
|
||||
label='Auto-Compile'
|
||||
labelPosition='right'
|
||||
onToggle={ this.store.handleAutocompileToggle }
|
||||
toggled={ this.store.autocompile }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{ this.renderSolidityVersions() }
|
||||
{ this.renderCompilation() }
|
||||
</div>
|
||||
@ -485,8 +524,8 @@ class WriteContract extends Component {
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { accounts } = state.personal;
|
||||
const { worker } = state.compiler;
|
||||
return { accounts, worker };
|
||||
const { worker, error } = state.compiler;
|
||||
return { accounts, worker, workerError: error };
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
|
@ -14,10 +14,13 @@
|
||||
// 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 { action, observable, transaction } from 'mobx';
|
||||
import store from 'store';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
import { sha3 } from '~/api/util/sha3';
|
||||
import SolidityUtils from '~/util/solidity';
|
||||
|
||||
const WRITE_CONTRACT_STORE_KEY = '_parity::writeContractStore';
|
||||
|
||||
const SNIPPETS = {
|
||||
@ -43,6 +46,8 @@ const SNIPPETS = {
|
||||
}
|
||||
};
|
||||
|
||||
let instance = null;
|
||||
|
||||
export default class WriteContractStore {
|
||||
|
||||
@observable sourcecode = '';
|
||||
@ -61,6 +66,9 @@ export default class WriteContractStore {
|
||||
@observable builds = [];
|
||||
@observable selectedBuild = -1;
|
||||
|
||||
@observable autocompile = false;
|
||||
@observable optimize = false;
|
||||
|
||||
@observable showDeployModal = false;
|
||||
@observable showSaveModal = false;
|
||||
@observable showLoadModal = false;
|
||||
@ -68,45 +76,55 @@ export default class WriteContractStore {
|
||||
@observable savedContracts = {};
|
||||
@observable selectedContract = {};
|
||||
|
||||
@observable workerError = null;
|
||||
|
||||
loadingSolidity = false;
|
||||
lastCompilation = {};
|
||||
snippets = SNIPPETS;
|
||||
worker = undefined;
|
||||
|
||||
useWorker = true;
|
||||
solc = {};
|
||||
|
||||
constructor () {
|
||||
this.reloadContracts();
|
||||
this.fetchSolidityVersions();
|
||||
|
||||
this.debouncedCompile = debounce(this.handleCompile, 1000);
|
||||
}
|
||||
|
||||
static get () {
|
||||
if (!instance) {
|
||||
instance = new WriteContractStore();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@action setWorkerError (error) {
|
||||
this.workerError = error;
|
||||
}
|
||||
|
||||
@action setEditor (editor) {
|
||||
this.editor = editor;
|
||||
}
|
||||
|
||||
@action setCompiler (compiler) {
|
||||
this.compiler = compiler;
|
||||
@action setWorker (worker) {
|
||||
if (this.worker !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.compiler.onmessage = (event) => {
|
||||
const message = JSON.parse(event.data);
|
||||
this.worker = worker;
|
||||
|
||||
switch (message.event) {
|
||||
case 'compiled':
|
||||
this.parseCompiled(message.data);
|
||||
break;
|
||||
case 'loading':
|
||||
this.parseLoading(message.data);
|
||||
break;
|
||||
case 'try-again':
|
||||
this.handleCompile();
|
||||
break;
|
||||
}
|
||||
};
|
||||
this
|
||||
.fetchSolidityVersions()
|
||||
.then(() => this.reloadContracts());
|
||||
}
|
||||
|
||||
fetchSolidityVersions () {
|
||||
fetch('https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/bin/list.json')
|
||||
return 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;
|
||||
let promise = Promise.resolve();
|
||||
|
||||
this.builds = builds.reverse().map((build, index) => {
|
||||
if (releases[build.version] === build.path) {
|
||||
@ -114,7 +132,7 @@ export default class WriteContractStore {
|
||||
|
||||
if (build.version === latestRelease) {
|
||||
build.latest = true;
|
||||
this.loadSolidityVersion(build);
|
||||
promise = promise.then(() => this.loadSolidityVersion(build));
|
||||
latestIndex = index;
|
||||
}
|
||||
}
|
||||
@ -123,29 +141,93 @@ export default class WriteContractStore {
|
||||
});
|
||||
|
||||
this.selectedBuild = latestIndex;
|
||||
return promise;
|
||||
})
|
||||
.catch((error) => {
|
||||
this.setWorkerError(error);
|
||||
});
|
||||
}
|
||||
|
||||
@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]);
|
||||
return this
|
||||
.loadSolidityVersion(this.builds[value])
|
||||
.then(() => this.handleCompile());
|
||||
}
|
||||
|
||||
getCompiler (build) {
|
||||
const { longVersion } = build;
|
||||
|
||||
if (!this.solc[longVersion]) {
|
||||
this.solc[longVersion] = SolidityUtils
|
||||
.getCompiler(build)
|
||||
.then((compiler) => {
|
||||
this.solc[longVersion] = compiler;
|
||||
return compiler;
|
||||
})
|
||||
.catch((error) => {
|
||||
this.setWorkerError(error);
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve(this.solc[longVersion]);
|
||||
}
|
||||
|
||||
@action loadSolidityVersion = (build) => {
|
||||
this.compiler.postMessage(JSON.stringify({
|
||||
action: 'load',
|
||||
data: build
|
||||
}));
|
||||
if (this.worker === undefined) {
|
||||
return;
|
||||
} else if (this.worker === null) {
|
||||
this.useWorker = false;
|
||||
}
|
||||
|
||||
if (this.loadingSolidity) {
|
||||
return this.loadingSolidity;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
|
||||
if (this.useWorker) {
|
||||
this.loadingSolidity = this.worker
|
||||
.postMessage({
|
||||
action: 'load',
|
||||
data: build
|
||||
})
|
||||
.then((result) => {
|
||||
if (result !== 'ok') {
|
||||
throw new Error('error while loading solidity: ' + result);
|
||||
}
|
||||
|
||||
this.loadingSolidity = false;
|
||||
this.loading = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('error while loading solidity', error);
|
||||
this.useWorker = false;
|
||||
this.loadingSolidity = null;
|
||||
|
||||
return this.loadSolidityVersion(build);
|
||||
});
|
||||
} else {
|
||||
this.loadingSolidity = this
|
||||
.getCompiler(build)
|
||||
.then(() => {
|
||||
this.loadingSolidity = false;
|
||||
this.loading = false;
|
||||
|
||||
return 'ok';
|
||||
})
|
||||
.catch((error) => {
|
||||
this.setWorkerError(error);
|
||||
this.loadingSolidity = false;
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
return this.loadingSolidity;
|
||||
}
|
||||
|
||||
@action handleOpenDeployModal = () => {
|
||||
@ -177,23 +259,120 @@ export default class WriteContractStore {
|
||||
this.contract = this.contracts[Object.keys(this.contracts)[value]];
|
||||
}
|
||||
|
||||
compile = (data) => {
|
||||
if (this.useWorker) {
|
||||
return this.worker.postMessage({
|
||||
action: 'compile',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
window.setTimeout(() => {
|
||||
this
|
||||
.getCompiler(data.build)
|
||||
.then((compiler) => {
|
||||
return SolidityUtils.compile(data, compiler);
|
||||
})
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
|
||||
@action handleCompile = () => {
|
||||
this.compiled = false;
|
||||
this.compiling = true;
|
||||
transaction(() => {
|
||||
this.compiled = false;
|
||||
this.compiling = true;
|
||||
});
|
||||
|
||||
const build = this.builds[this.selectedBuild];
|
||||
const version = build.longVersion;
|
||||
const sourcecode = this.sourcecode.replace(/\n+/g, '\n').replace(/\s(\s+)/g, ' ');
|
||||
const hash = sha3(JSON.stringify({ version, sourcecode, optimize: this.optimize }));
|
||||
|
||||
if (this.compiler && typeof this.compiler.postMessage === 'function') {
|
||||
this.sendFilesToWorker();
|
||||
let promise = Promise.resolve(null);
|
||||
|
||||
this.compiler.postMessage(JSON.stringify({
|
||||
action: 'compile',
|
||||
data: {
|
||||
sourcecode: this.sourcecode,
|
||||
build: build
|
||||
}
|
||||
}));
|
||||
if (hash === this.lastCompilation.hash) {
|
||||
promise = new Promise((resolve) => {
|
||||
window.setTimeout(() => {
|
||||
resolve(this.lastCompilation);
|
||||
}, 500);
|
||||
});
|
||||
} else {
|
||||
promise = this
|
||||
.compile({
|
||||
sourcecode: sourcecode,
|
||||
build: build,
|
||||
optimize: this.optimize,
|
||||
files: this.files
|
||||
})
|
||||
.then((data) => {
|
||||
const result = this.parseCompiled(data);
|
||||
|
||||
this.lastCompilation = {
|
||||
result: result,
|
||||
date: new Date(),
|
||||
version: data.version,
|
||||
hash
|
||||
};
|
||||
|
||||
return this.lastCompilation;
|
||||
})
|
||||
.catch((error) => {
|
||||
this.setWorkerError(error);
|
||||
});
|
||||
}
|
||||
|
||||
return promise.then((data = null) => {
|
||||
if (data) {
|
||||
const {
|
||||
contract, contractIndex,
|
||||
annotations, contracts, errors
|
||||
} = data.result;
|
||||
|
||||
this.contract = contract;
|
||||
this.contractIndex = contractIndex;
|
||||
|
||||
this.annotations = annotations;
|
||||
this.contracts = contracts;
|
||||
this.errors = errors;
|
||||
}
|
||||
|
||||
this.compiled = true;
|
||||
this.compiling = false;
|
||||
});
|
||||
}
|
||||
|
||||
@action handleAutocompileToggle = () => {
|
||||
this.autocompile = !this.autocompile;
|
||||
}
|
||||
|
||||
@action handleOptimizeToggle = () => {
|
||||
this.optimize = !this.optimize;
|
||||
}
|
||||
|
||||
@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
|
||||
);
|
||||
|
||||
const contractKeys = Object.keys(contracts || {});
|
||||
|
||||
const contract = contractKeys.length ? contracts[contractKeys[0]] : null;
|
||||
const contractIndex = contractKeys.length ? 0 : -1;
|
||||
|
||||
return {
|
||||
contract, contractIndex,
|
||||
contracts, errors, annotations
|
||||
};
|
||||
}
|
||||
|
||||
parseErrors = (data, formal = false) => {
|
||||
@ -220,43 +399,6 @@ export default class WriteContractStore {
|
||||
});
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
@ -268,7 +410,7 @@ export default class WriteContractStore {
|
||||
|
||||
if (compile) {
|
||||
this.handleCompile();
|
||||
} else {
|
||||
} else if (this.autocompile) {
|
||||
this.debouncedCompile();
|
||||
}
|
||||
}
|
||||
@ -327,8 +469,9 @@ export default class WriteContractStore {
|
||||
current: this.sourcecode
|
||||
});
|
||||
|
||||
this.handleCompile();
|
||||
this.resizeEditor();
|
||||
|
||||
return this.handleCompile();
|
||||
}
|
||||
|
||||
@action handleLoadContract = (contract) => {
|
||||
@ -363,16 +506,13 @@ export default class WriteContractStore {
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
sendFilesToWorker = () => {
|
||||
get files () {
|
||||
const files = [].concat(
|
||||
Object.values(this.snippets),
|
||||
Object.values(this.savedContracts)
|
||||
);
|
||||
|
||||
this.compiler.postMessage(JSON.stringify({
|
||||
action: 'setFiles',
|
||||
data: files
|
||||
}));
|
||||
return files;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ const WebpackErrorNotificationPlugin = require('webpack-error-notification');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
const ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin');
|
||||
|
||||
const Shared = require('./shared');
|
||||
const DAPPS = require('../src/dapps');
|
||||
@ -50,7 +51,7 @@ module.exports = {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
exclude: /(node_modules)/,
|
||||
// use: [ 'happypack/loader?id=js' ]
|
||||
use: isProd ? ['babel-loader'] : [
|
||||
'babel-loader?cacheDirectory=true'
|
||||
@ -136,7 +137,18 @@ module.exports = {
|
||||
},
|
||||
|
||||
plugins: (function () {
|
||||
const plugins = Shared.getPlugins().concat([
|
||||
const DappsHTMLInjection = DAPPS.map((dapp) => {
|
||||
return new HtmlWebpackPlugin({
|
||||
title: dapp.title,
|
||||
filename: dapp.name + '.html',
|
||||
template: './dapps/index.ejs',
|
||||
favicon: FAVICON,
|
||||
secure: dapp.secure,
|
||||
chunks: [ isProd ? null : 'commons', dapp.name ]
|
||||
});
|
||||
});
|
||||
|
||||
const plugins = Shared.getPlugins().concat(
|
||||
new CopyWebpackPlugin([{ from: './error_pages.css', to: 'styles.css' }], {}),
|
||||
new WebpackErrorNotificationPlugin(),
|
||||
|
||||
@ -151,17 +163,14 @@ module.exports = {
|
||||
template: './index.ejs',
|
||||
favicon: FAVICON,
|
||||
chunks: [ isProd ? null : 'commons', 'index' ]
|
||||
})
|
||||
], DAPPS.map((dapp) => {
|
||||
return new HtmlWebpackPlugin({
|
||||
title: dapp.title,
|
||||
filename: dapp.name + '.html',
|
||||
template: './dapps/index.ejs',
|
||||
favicon: FAVICON,
|
||||
secure: dapp.secure,
|
||||
chunks: [ isProd ? null : 'commons', dapp.name ]
|
||||
});
|
||||
}));
|
||||
}),
|
||||
|
||||
new ServiceWorkerWebpackPlugin({
|
||||
entry: path.join(__dirname, '../src/serviceWorker.js')
|
||||
}),
|
||||
|
||||
DappsHTMLInjection
|
||||
);
|
||||
|
||||
if (!isProd) {
|
||||
const DEST_I18N = path.join(__dirname, '..', DEST, 'i18n');
|
||||
|
Loading…
Reference in New Issue
Block a user