From cba4e9fb910e820d0535086905675b84d7af6885 Mon Sep 17 00:00:00 2001 From: emizzle Date: Mon, 27 Aug 2018 12:49:01 +1000 Subject: [PATCH] Fiddle file stored in filesystem MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fiddles are now stored in the filesystem so they can be preserved across page reloads, but also so that contracts deployed via fiddles can have their source code shown. Fiddles deployed as contracts now fully work with existing contract list UI and functionality. Fiddle deployed contracts are listed in a separate section in the UI. Current limitation is that only single contracts per file are supported. If the fiddle contains multiple contracts, it’s currently not supported. --- embark-ui/src/actions/index.js | 9 +++++- embark-ui/src/api/index.js | 4 +++ embark-ui/src/components/Contracts.js | 7 +++-- embark-ui/src/components/FiddleResults.js | 4 +-- .../src/containers/ContractSourceContainer.js | 4 +-- .../src/containers/ContractsContainer.js | 22 +++++++++++---- embark-ui/src/containers/FiddleContainer.js | 27 ++++++++++++++---- embark-ui/src/reducers/index.js | 1 + embark-ui/src/reducers/selectors.js | 10 ++++++- embark-ui/src/sagas/index.js | 9 +++++- lib/modules/contracts_manager/index.js | 8 ++++-- lib/modules/pipeline/index.js | 18 ++++++++++++ lib/modules/solidity/index.js | 28 +++++++++++++++++-- 13 files changed, 124 insertions(+), 27 deletions(-) diff --git a/embark-ui/src/actions/index.js b/embark-ui/src/actions/index.js index fd162838b..6a58d48af 100644 --- a/embark-ui/src/actions/index.js +++ b/embark-ui/src/actions/index.js @@ -179,11 +179,18 @@ export const FIDDLE_DEPLOY = createRequestTypes('FIDDLE_DEPLOY'); export const fiddleDeploy = { post: (compiledCode) => action(FIDDLE_DEPLOY[REQUEST], {compiledCode}), success: (response) => { - return action(FIDDLE_DEPLOY[SUCCESS], {fiddleDeploys: [response.contractNames]}); + return action(FIDDLE_DEPLOY[SUCCESS], {fiddleDeploys: response.result}); }, failure: (error) => action(FIDDLE_DEPLOY[FAILURE], {error}) }; +export const FIDDLE_FILE = createRequestTypes('FIDDLE_FILE'); +export const fiddleFile = { + request: () => action(FIDDLE_FILE[REQUEST]), + success: (source) => action(FIDDLE_FILE[SUCCESS], {fiddleFiles: [{source, filename: 'temp'}]}), + failure: (error) => action(FIDDLE_FILE[FAILURE], {error}) +}; + // Web Socket export const WATCH_NEW_PROCESS_LOGS = 'WATCH_NEW_PROCESS_LOGS'; export const WATCH_NEW_CONTRACT_LOGS = 'WATCH_NEW_CONTRACT_LOGS'; diff --git a/embark-ui/src/api/index.js b/embark-ui/src/api/index.js index 319e4ffa0..09833a8f2 100644 --- a/embark-ui/src/api/index.js +++ b/embark-ui/src/api/index.js @@ -108,6 +108,10 @@ export function fetchContractFile(payload) { return get('/files/contracts', {params: payload}); } +export function fetchLastFiddle() { + return get('/files/lastfiddle', {params: 'temp'}); +} + export function listenToChannel(channel) { return new WebSocket(`${constants.wsEndpoint}/communication/listenTo/${channel}`); } diff --git a/embark-ui/src/components/Contracts.js b/embark-ui/src/components/Contracts.js index 3c807923b..e4b18f6cf 100644 --- a/embark-ui/src/components/Contracts.js +++ b/embark-ui/src/components/Contracts.js @@ -9,8 +9,8 @@ import { import {Link} from 'react-router-dom'; import {formatContractForDisplay} from '../utils/presentation'; -const Contracts = ({contracts}) => ( - +const Contracts = ({contracts, title = "Contracts"}) => ( + @@ -48,7 +48,8 @@ const Contracts = ({contracts}) => ( ); Contracts.propTypes = { - contracts: PropTypes.arrayOf(PropTypes.object) + contracts: PropTypes.array, + title: PropTypes.string }; export default Contracts; diff --git a/embark-ui/src/components/FiddleResults.js b/embark-ui/src/components/FiddleResults.js index 5bf2460da..de7dfa302 100644 --- a/embark-ui/src/components/FiddleResults.js +++ b/embark-ui/src/components/FiddleResults.js @@ -116,7 +116,7 @@ class FiddleResults extends Component { @@ -153,7 +153,7 @@ FiddleResults.propTypes = { fatalFiddle: PropTypes.string, fatalFiddleDeploy: PropTypes.string, isLoading: PropTypes.bool, - deployedContracts: PropTypes.object + deployedContracts: PropTypes.string }; export default FiddleResults; diff --git a/embark-ui/src/containers/ContractSourceContainer.js b/embark-ui/src/containers/ContractSourceContainer.js index 53590ceca..7b6b5c5f8 100644 --- a/embark-ui/src/containers/ContractSourceContainer.js +++ b/embark-ui/src/containers/ContractSourceContainer.js @@ -11,7 +11,7 @@ import {getContract, getContractFile} from "../reducers/selectors"; class ContractSourceContainer extends Component { componentDidMount() { - this.props.fetchContractFile(this.props.contract.originalFilename); + this.props.fetchContractFile(this.props.contract.filename); } render() { @@ -27,7 +27,7 @@ class ContractSourceContainer extends Component { function mapStateToProps(state, props) { const contract = getContract(state, props.match.params.contractName); - const contractFile = getContractFile(state, contract.originalFilename); + const contractFile = getContractFile(state, contract.filename); return { contract, diff --git a/embark-ui/src/containers/ContractsContainer.js b/embark-ui/src/containers/ContractsContainer.js index 1d769eded..80f6dcdfb 100644 --- a/embark-ui/src/containers/ContractsContainer.js +++ b/embark-ui/src/containers/ContractsContainer.js @@ -5,7 +5,7 @@ import {contracts as contractsAction} from "../actions"; import Contracts from '../components/Contracts'; import DataWrapper from "../components/DataWrapper"; -import {getContracts} from "../reducers/selectors"; +import {getContracts, getFiddleContracts} from "../reducers/selectors"; class ContractsContainer extends Component { componentDidMount() { @@ -14,19 +14,31 @@ class ContractsContainer extends Component { render() { return ( - 0} {...this.props} render={({contracts}) => ( - - )} /> + + 0} {...this.props} render={({contracts}) => ( + + )} /> + 0} {...this.props} render={({fiddleContracts}) => ( + + + + )} /> + ); } } function mapStateToProps(state) { - return {contracts: getContracts(state), error: state.errorMessage, loading: state.loading}; + return { + contracts: getContracts(state), + fiddleContracts: getFiddleContracts(state), + error: state.errorMessage, + loading: state.loading}; } ContractsContainer.propTypes = { contracts: PropTypes.array, + fiddleContracts: PropTypes.array, fetchContracts: PropTypes.func }; diff --git a/embark-ui/src/containers/FiddleContainer.js b/embark-ui/src/containers/FiddleContainer.js index 9dc410670..0354f9f68 100644 --- a/embark-ui/src/containers/FiddleContainer.js +++ b/embark-ui/src/containers/FiddleContainer.js @@ -3,12 +3,12 @@ import React, {Component} from 'react'; import {connect} from 'react-redux'; import PropTypes from 'prop-types'; -import {fiddle as fiddleAction, fiddleDeploy as fiddleDeployAction} from '../actions'; +import {fiddle as fiddleAction, fiddleDeploy as fiddleDeployAction, fiddleFile as fiddleFileAction} from '../actions'; import Fiddle from '../components/Fiddle'; import FiddleResults from '../components/FiddleResults'; import FiddleResultsSummary from '../components/FiddleResultsSummary'; import scrollToComponent from 'react-scroll-to-component'; -import {getFiddle, getFiddleDeploy} from "../reducers/selectors"; +import {getFiddle, getFiddleDeploy, getLastFiddle} from "../reducers/selectors"; import CompilerError from "../components/CompilerError"; class FiddleContainer extends Component { @@ -24,6 +24,16 @@ class FiddleContainer extends Component { this.editor = null; } + componentDidMount() { + this.props.fetchLastFiddle(); + + } + componentDidUpdate(prevProps){ + if(prevProps.lastFiddle !== this.props.lastFiddle){ + this._onCodeChange(this.props.lastFiddle); + } + } + _onCodeChange(newValue) { this.setState({value: newValue}); if (this.compileTimeout) clearTimeout(this.compileTimeout); @@ -82,7 +92,7 @@ class FiddleContainer extends Component { } render() { - const {fiddle, loading, fiddleError, fiddleDeployError, deployedContracts} = this.props; + const {fiddle, loading, fiddleError, fiddleDeployError, deployedContracts, lastFiddle} = this.props; const {loadingMessage} = this.state; let renderings = []; let warnings = []; @@ -104,7 +114,7 @@ class FiddleContainer extends Component { onDeployClick={(e) => this._onDeployClick(e)} /> this._onCodeChange(n)} errors={errors} warnings={warnings} @@ -142,11 +152,13 @@ class FiddleContainer extends Component { function mapStateToProps(state) { const fiddle = getFiddle(state); const deployedFiddle = getFiddleDeploy(state); + const lastFiddle = getLastFiddle(state); return { fiddle: fiddle.data, deployedContracts: deployedFiddle.data, fiddleError: fiddle.error, fiddleDeployError: deployedFiddle.error, + lastFiddle: lastFiddle ? lastFiddle.source : '', loading: state.loading }; } @@ -158,13 +170,16 @@ FiddleContainer.propTypes = { loading: PropTypes.bool, postFiddle: PropTypes.func, postFiddleDeploy: PropTypes.func, - deployedContracts: PropTypes.object + deployedContracts: PropTypes.string, + fetchLastFiddle: PropTypes.func, + lastFiddle: PropTypes.string }; export default connect( mapStateToProps, { postFiddle: fiddleAction.post, - postFiddleDeploy: fiddleDeployAction.post + postFiddleDeploy: fiddleDeployAction.post, + fetchLastFiddle: fiddleFileAction.request }, )(FiddleContainer); diff --git a/embark-ui/src/reducers/index.js b/embark-ui/src/reducers/index.js index 6382dc936..15209cf7a 100644 --- a/embark-ui/src/reducers/index.js +++ b/embark-ui/src/reducers/index.js @@ -22,6 +22,7 @@ const entitiesDefaultState = { messageChannels: [], fiddles: [], fiddleDeploys: [], + fiddleFiles: [], versions: [], plugins: [], ensRecords: [] diff --git a/embark-ui/src/reducers/selectors.js b/embark-ui/src/reducers/selectors.js index 9887a1810..bd35a969b 100644 --- a/embark-ui/src/reducers/selectors.js +++ b/embark-ui/src/reducers/selectors.js @@ -53,7 +53,11 @@ export function getContractLogsByContract(state, contractName) { } export function getContracts(state) { - return state.entities.contracts; + return state.entities.contracts.filter(contract => !contract.isFiddle); +} + +export function getFiddleContracts(state) { + return state.entities.contracts.filter(contract => contract.isFiddle); } export function getContract(state, contractName) { @@ -118,6 +122,10 @@ export function getFiddleDeploy(state) { }; } +export function getLastFiddle(state) { + return state.entities.fiddleFiles.find((fiddleFile => fiddleFile.filename === 'temp')); +} + export function getEnsRecords(state) { return state.entities.ensRecords; } diff --git a/embark-ui/src/sagas/index.js b/embark-ui/src/sagas/index.js index 34d74a238..e1f8a9d4c 100644 --- a/embark-ui/src/sagas/index.js +++ b/embark-ui/src/sagas/index.js @@ -5,7 +5,8 @@ import {all, call, fork, put, takeEvery, take} from 'redux-saga/effects'; const {account, accounts, block, blocks, transaction, transactions, processes, commands, processLogs, contracts, contract, contractProfile, messageSend, versions, plugins, messageListen, fiddle, - fiddleDeploy, ensRecord, ensRecords, contractLogs, contractFile, contractFunction, contractDeploy} = actions; + fiddleDeploy, ensRecord, ensRecords, contractLogs, contractFile, contractFunction, contractDeploy, + fiddleFile} = actions; function *doRequest(entity, apiFn, payload) { const {response, error} = yield call(apiFn, payload); @@ -32,6 +33,7 @@ export const fetchContracts = doRequest.bind(null, contracts, api.fetchContracts export const fetchContract = doRequest.bind(null, contract, api.fetchContract); export const fetchContractProfile = doRequest.bind(null, contractProfile, api.fetchContractProfile); export const fetchContractFile = doRequest.bind(null, contractFile, api.fetchContractFile); +export const fetchLastFiddle = doRequest.bind(null, fiddleFile, api.fetchLastFiddle); export const postContractFunction = doRequest.bind(null, contractFunction, api.postContractFunction); export const postContractDeploy = doRequest.bind(null, contractDeploy, api.postContractDeploy); export const postFiddle = doRequest.bind(null, fiddle, api.postFiddle); @@ -96,6 +98,10 @@ export function *watchFetchContractFile() { yield takeEvery(actions.CONTRACT_FILE[actions.REQUEST], fetchContractFile); } +export function *watchFetchLastFiddle() { + yield takeEvery(actions.FIDDLE_FILE[actions.REQUEST], fetchLastFiddle); +} + export function *watchPostContractFunction() { yield takeEvery(actions.CONTRACT_FUNCTION[actions.REQUEST], postContractFunction); } @@ -223,6 +229,7 @@ export default function *root() { fork(watchFetchTransaction), fork(watchPostFiddle), fork(watchPostFiddleDeploy), + fork(watchFetchLastFiddle), fork(watchFetchEnsRecord), fork(watchPostEnsRecords) ]); diff --git a/lib/modules/contracts_manager/index.js b/lib/modules/contracts_manager/index.js index c2fc69c5c..a3dde9ad9 100644 --- a/lib/modules/contracts_manager/index.js +++ b/lib/modules/contracts_manager/index.js @@ -76,7 +76,8 @@ class ContractsManager { className: contract.className, deploy: contract.deploy, error: contract.error, - address: contract.deployedAddress + address: contract.deployedAddress, + isFiddle: Boolean(contract.isFiddle) }); i += 1; } @@ -181,11 +182,12 @@ class ContractsManager { const builtContracts = _.pick(self.contracts, contractNames); // for each contract, deploy (in parallel) - async.eachOf(builtContracts, (contract, contractName, callback) => { + async.eachOf(builtContracts, (contract, contractName, next) => { contract.args = []; /* TODO: override contract.args */ contract.className = contractName; + contract.isFiddle = true; self.events.request("deploy:contract", contract, (err) => { - callback(err); + next(err); }); }, (err) => { let responseData = {}; diff --git a/lib/modules/pipeline/index.js b/lib/modules/pipeline/index.js index fefbec4ed..a05409c5e 100644 --- a/lib/modules/pipeline/index.js +++ b/lib/modules/pipeline/index.js @@ -24,6 +24,13 @@ class Pipeline { const self = this; self.events.setCommandHandler("files:contract", (filename, cb) => { + // handle case where we have a fiddle file and not a file stored in the dapp + if(filename.indexOf('.embark/fiddles') > -1){ + return fs.readFile(filename, 'utf8', (err, source) => { + if (err) return cb({error: err}); + cb(source); + }); + } let file = self.contractsFiles.find((file) => file.filename === filename); if (!file) { return cb({error: filename + " not found"}); @@ -39,6 +46,17 @@ class Pipeline { self.events.request('files:contract', req.query.filename, res.send.bind(res)); } ); + + plugin.registerAPICall( + 'get', + '/embark-api/files/lastfiddle', + (req, res) => { + fs.readFile(fs.dappPath('.embark/fiddles/temp.sol'), 'utf8', (err, source) => { + if (err) return res.send({error: err}); + res.send(source); + }); + } + ); } build({modifiedAssets}, callback) { diff --git a/lib/modules/solidity/index.js b/lib/modules/solidity/index.js index ab30cc13c..67b928e4f 100644 --- a/lib/modules/solidity/index.js +++ b/lib/modules/solidity/index.js @@ -23,8 +23,10 @@ class Solidity { const input = {'fiddle': {content: req.body.code.replace(/\r\n/g, '\n')}}; this.compile_solidity_code(input, {}, true, (errors, compilationResult) => { // write code to filesystem so we can view the source after page refresh - const filePath = `.embark/fiddles/${Object.keys(compilationResult).join('_')}.sol`; - fs.writeFile(filePath, req.body.code, 'utf8'); // async, do not need to wait + const className = !compilationResult ? 'temp' : Object.keys(compilationResult).join('_'); + this._writeFiddleToFileAsync(req.body.code, className, Boolean(compilationResult), (err) => { + if(err) this.logger.trace('Error writing fiddle to filesystem: ', err); + }); // async, do not need to wait const responseData = {errors: errors, compilationResult: compilationResult}; this.logger.trace(`POST response /embark-api/contract/compile:\n ${JSON.stringify(responseData)}`); @@ -34,6 +36,26 @@ class Solidity { ); } + _writeFiddleToFileAsync(code, className, isCompiled, cb){ + fs.mkdirp('.embark/fiddles', (err) => { + if(err) return cb(err); + + // always write to temp.sol file + const filePath = Solidity._getFiddlePath('temp'); + fs.writeFile(filePath, code, 'utf8', cb); + + // if it's compiled, also write to [classname].sol + if(isCompiled){ + const filePath = Solidity._getFiddlePath(className); + fs.writeFile(filePath, code, 'utf8', cb); + } + }); + } + + static _getFiddlePath(className){ + return fs.dappPath(`.embark/fiddles/${className}.sol`); + } + _compile(jsonObj, returnAllErrors, callback) { const self = this; self.solcW.compile(jsonObj, function (err, output) { @@ -128,7 +150,7 @@ class Solidity { const className = contractName; let filename = contractFile; - if(filename === 'fiddle') filename = `.embark/fiddles/${className}.sol`; + if(filename === 'fiddle') filename = Solidity._getFiddlePath(className); compiled_object[className] = {}; compiled_object[className].code = contract.evm.bytecode.object;