From 8caa478968be7d2fd61d5bce568ed73219e8d88d Mon Sep 17 00:00:00 2001 From: emizzle Date: Fri, 10 Aug 2018 12:16:38 +1000 Subject: [PATCH] Annotations, click to error, UI improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Compiler annotations added to editor gutter for errors and warnings Clicking an error now scrolls editor to offending line and scrolls page to the top of the editor Added Compiling… loader. --- embark-ui/package-lock.json | 58 ++++++++ embark-ui/package.json | 1 + .../monaco-editor-worker-loader-proxy.js | 5 - embark-ui/src/actions/index.js | 12 +- embark-ui/src/components/Fiddle.js | 57 +++++--- embark-ui/src/components/FiddleResults.js | 133 +++++++----------- .../src/components/FiddleResultsSummary.js | 45 ++++++ embark-ui/src/containers/FiddleContainer.js | 114 +++++++++++++-- embark-ui/src/general.css | 40 +++++- embark-ui/src/reducers/fiddleReducer.js | 12 +- embark-ui/src/sagas/index.js | 2 +- lib/modules/solidity/index.js | 3 - 12 files changed, 343 insertions(+), 139 deletions(-) delete mode 100644 embark-ui/public/monaco-editor-worker-loader-proxy.js create mode 100644 embark-ui/src/components/FiddleResultsSummary.js diff --git a/embark-ui/package-lock.json b/embark-ui/package-lock.json index b57f63d0d..8c3da60b9 100644 --- a/embark-ui/package-lock.json +++ b/embark-ui/package-lock.json @@ -2039,11 +2039,47 @@ "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.3.0.tgz", "integrity": "sha512-MAAAIOdi2s4Gl6rZ76PNcUa9IOYB+5ICdT41o5uMRf09aEu/F9RK+qhe8RjXNPwcTjGV7KU7h2P/fljThFVqyQ==" }, + "component-clone": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/component-clone/-/component-clone-0.2.2.tgz", + "integrity": "sha1-x/WXmCKID62M+wliuikYbQYe4E8=", + "requires": { + "component-type": "*" + } + }, "component-emitter": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" }, + "component-raf": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/component-raf/-/component-raf-1.2.0.tgz", + "integrity": "sha1-srxy1D8bAU/eeks8RHx2S8c8y6o=" + }, + "component-tween": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/component-tween/-/component-tween-1.2.0.tgz", + "integrity": "sha1-zDnOXbqwW1KCX0HRlHY4oLAbK4o=", + "requires": { + "component-clone": "0.2.2", + "component-emitter": "1.2.0", + "component-type": "1.1.0", + "ease-component": "1.0.0" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.0.tgz", + "integrity": "sha1-zNETqGOI0GSC0D3j/H35hSa6jv4=" + } + } + }, + "component-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/component-type/-/component-type-1.1.0.tgz", + "integrity": "sha1-lbZmqtU+XI0fK+E1xFtdSZGXwMU=" + }, "compressible": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.14.tgz", @@ -2835,6 +2871,11 @@ "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" }, + "ease-component": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ease-component/-/ease-component-1.0.0.tgz", + "integrity": "sha1-s3VybbC1sEWVt3RAOW/sfapdd8k=" + }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -9033,6 +9074,14 @@ } } }, + "react-scroll-to-component": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/react-scroll-to-component/-/react-scroll-to-component-1.0.2.tgz", + "integrity": "sha1-8mDck2xipT53J4bXgy/giE4ZU1Q=", + "requires": { + "scroll-to": "0.0.2" + } + }, "react-text-mask": { "version": "5.4.3", "resolved": "https://registry.npmjs.org/react-text-mask/-/react-text-mask-5.4.3.tgz", @@ -9554,6 +9603,15 @@ "ajv": "^5.0.0" } }, + "scroll-to": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/scroll-to/-/scroll-to-0.0.2.tgz", + "integrity": "sha1-k205ipEzZgokkhRcLACB38sHKPM=", + "requires": { + "component-raf": "1.2.0", + "component-tween": "1.2.0" + } + }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", diff --git a/embark-ui/package.json b/embark-ui/package.json index 6fc98abab..99671d5fa 100644 --- a/embark-ui/package.json +++ b/embark-ui/package.json @@ -15,6 +15,7 @@ "react-redux": "^5.0.7", "react-router-dom": "^4.3.1", "react-scripts": "1.1.4", + "react-scroll-to-component": "^1.0.2", "redux": "^4.0.0", "redux-saga": "^0.16.0", "tabler-react": "^1.18.0" diff --git a/embark-ui/public/monaco-editor-worker-loader-proxy.js b/embark-ui/public/monaco-editor-worker-loader-proxy.js deleted file mode 100644 index 0585b5c06..000000000 --- a/embark-ui/public/monaco-editor-worker-loader-proxy.js +++ /dev/null @@ -1,5 +0,0 @@ -self.MonacoEnvironment = { - baseUrl: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.13.1/min/' -}; - -importScripts('https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.13.1/min/vs/base/worker/workerMain.js'); // eslint-disable-line diff --git a/embark-ui/src/actions/index.js b/embark-ui/src/actions/index.js index 50b6670b8..834b5d97b 100644 --- a/embark-ui/src/actions/index.js +++ b/embark-ui/src/actions/index.js @@ -144,13 +144,13 @@ export function listenToContractLogs() { } // Fiddle -export const FETCH_COMPILE_CODE = 'FETCH_COMPILE_CODE'; -export const RECEIVE_COMPILE_CODE = 'RECEIVE_COMPILE_CODE'; -export const RECEIVE_COMPILE_CODE_ERROR = 'RECEIVE_COMPILE_CODE_ERROR'; +export const COMPILE_CODE_REQUEST = 'COMPILE_CODE_REQUEST'; +export const COMPILE_CODE_SUCCESS = 'COMPILE_CODE_SUCCESS'; +export const COMPILE_CODE_FAILURE = 'COMPILE_CODE_FAILURE'; export function fetchCodeCompilation(codeToCompile){ return { - type: FETCH_COMPILE_CODE, + type: COMPILE_CODE_REQUEST, codeToCompile }; } @@ -158,14 +158,14 @@ export function fetchCodeCompilation(codeToCompile){ export function receiveCodeCompilation(compilationResult){ return { - type: RECEIVE_COMPILE_CODE, + type: COMPILE_CODE_SUCCESS, compilationResult }; } export function receiveCodeCompilationError(){ return { - type: RECEIVE_COMPILE_CODE_ERROR + type: COMPILE_CODE_FAILURE }; } diff --git a/embark-ui/src/components/Fiddle.js b/embark-ui/src/components/Fiddle.js index 6d6183049..a380fe935 100644 --- a/embark-ui/src/components/Fiddle.js +++ b/embark-ui/src/components/Fiddle.js @@ -5,28 +5,49 @@ import 'brace/theme/tomorrow_night_blue'; import 'ace-mode-solidity/build/remix-ide/mode-solidity'; import PropTypes from 'prop-types'; -const Fiddle = ({onCodeChange, value}) => { +class Fiddle extends React.Component { + constructor(props) { + super(props); - return ( - -

Fiddle

-

Play around with contract code and deploy against your running node.

- -
- ); -}; + this.ace = null; + } + + render() { + const {onCodeChange, value, errors, warnings} = this.props; + const annotations = errors.map((error) => { return error.annotation; }).concat(warnings.map(warning => { return warning.annotation; })); + return ( + + + { this.ace = ace; }} + setOptions={{ + useWorker: false + }} + editorProps={{ + $blockScrolling: Infinity, + enableLiveAutocompletion:true, + highlightSelectedWord: true + }} + /> + + ); + } +} Fiddle.propTypes = { onCodeChange: PropTypes.func, - value: PropTypes.string + value: PropTypes.string, + errors: PropTypes.array, + warnings: PropTypes.array }; export default Fiddle; diff --git a/embark-ui/src/components/FiddleResults.js b/embark-ui/src/components/FiddleResults.js index 1f69fc009..b79a55d1e 100644 --- a/embark-ui/src/components/FiddleResults.js +++ b/embark-ui/src/components/FiddleResults.js @@ -1,41 +1,35 @@ +/* eslint {jsx-a11y/anchor-has-content:"off"} */ import React, {Component} from 'react'; import {Card, List, Badge} from 'tabler-react'; import PropTypes from 'prop-types'; -class FiddleResults extends Component{ +class FiddleResults extends Component { - constructor(props){ - super(props); - this.state = { - errors: props.compilationResult.errors - }; + static _removeClass(elems, className) { + for (let elem of elems) { + elem.className = elem.className.replace(className, '').replace(' ', ' '); + } } - static _removeClass(elems, className){ - for(let elem of elems) { - elem.className = elem.className.replace(className, '').replace(' ', ' '); + static _toggleClass(elems, className) { + for (let elem of elems) { + if (elem.className.indexOf(className) > -1) { + FiddleResults._removeClass([elem], className); } - } - - static _toggleClass(elems, className){ - for(let elem of elems) { - if(elem.className.indexOf(className) > -1){ - FiddleResults._removeClass([elem], className); - } - else{ - elem.className = (elem.className.length > 0 ? elem.className + ' ' : '') + className; - } + else { + elem.className = (elem.className.length > 0 ? elem.className + ' ' : '') + className; } + } } - toggleCollapse(e) { + _toggleCollapse(e) { const collapsedClassName = 'card-collapsed'; const className = e.currentTarget.parentElement.className.replace('card-options', '').replace(' ', ''); const elems = document.getElementsByClassName(className + '-card'); FiddleResults._toggleClass(elems, collapsedClassName); } - - toggleFullscreen(e) { + + _toggleFullscreen(e) { const collapsedClassName = 'card-collapsed'; const fullscreenClassName = 'card-fullscreen'; const className = e.currentTarget.parentElement.className.replace('card-options', '').replace(' ', ''); @@ -44,91 +38,58 @@ class FiddleResults extends Component{ FiddleResults._removeClass(elems, collapsedClassName); } - render(){ - const warningObjs = this.props.compilationResult.errors.filter(error => { - return error.severity === 'warning'; - }); - const errorObjs = this.props.compilationResult.errors.filter(error => { - return error.severity === 'error'; - }); - const warnings = warningObjs.map((warning, index) => { - return ( - - - Lines {warning.sourceLocation.start}-{warning.sourceLocation.end} - - {warning.formattedMessage} - - ); - }); - const errors = errorObjs.map((error, index) => { - return ( - - - Lines {error.sourceLocation.start}-{error.sourceLocation.end} - - {error.formattedMessage} - - ); - }); - const errorsCard = + className={errorType + "s-card"} + key={errorType + "s-card"}> - Errors {errors.length} - - - + {errorType + "s"} {errors.length} + + + - {errors} - - - ; - const warningsCard = - - Warnings {warnings.length} - - - - - - - - {warnings} + {errors.map(error => { return error.node; })} ; + } + + render() { + const {warnings, errors} = this.props; let renderings = []; - if(!this.state.errors){ - return 'Compilation successful (add green tick mark)'; - } - if(errors.length) renderings.push(errorsCard); - if(warnings.length) renderings.push(warningsCard); - + if (errors.length) renderings.push( + + {errors.length} error{errors.length > 1 ? "s" : ""} + + ); + if(warnings.length) renderings.push( + + {warnings.length} warning{warnings.length > 1 ? "s" : ""} + + ); + + return ( +
+ {renderings} + {!(hasResult || isFetching) ? " " : ""} +
+ ); + } +} + +FiddleResultsSummary.propTypes = { + errors: PropTypes.array, + warnings: PropTypes.array, + isFetching: PropTypes.bool, + hasResult: PropTypes.bool +}; + +export default FiddleResultsSummary; diff --git a/embark-ui/src/containers/FiddleContainer.js b/embark-ui/src/containers/FiddleContainer.js index cb385c9c8..fd9e61675 100644 --- a/embark-ui/src/containers/FiddleContainer.js +++ b/embark-ui/src/containers/FiddleContainer.js @@ -6,43 +6,129 @@ import PropTypes from 'prop-types'; import {fetchCodeCompilation} from '../actions'; import Fiddle from '../components/Fiddle'; import FiddleResults from '../components/FiddleResults'; +import FiddleReultsSummary from '../components/FiddleResultsSummary'; +import {Badge} from 'tabler-react'; +import scrollToComponent from 'react-scroll-to-component'; class FiddleContainer extends Component { - constructor(props){ + constructor(props) { super(props); - this.state = { + this.state = { value: '' }; this.compileTimeout = null; + this.ace = null; + this.editor = null; } - componentDidMount(){ - if(this.state.value){ + componentDidMount() { + if (this.state.value) { this.props.fetchCodeCompilation(this.state.value); } } - onCodeChange(newValue) { + _onCodeChange(newValue) { this.setState({value: newValue}); - if(this.compileTimeout) clearTimeout(this.compileTimeout); + if (this.compileTimeout) clearTimeout(this.compileTimeout); this.compileTimeout = setTimeout(() => { this.props.fetchCodeCompilation(newValue); }, 1000); - + + } + + _getFormattedErrors(errors, errorType){ + return errors.reduce( + (errors, error, index) => { + if (error.severity === errorType) { + const errorRowCol = this._getRowCol(error.formattedMessage); + const annotation = Object.assign({}, { + row: errorRowCol.row - 1, // must be 0 based + column: errorRowCol.col - 1, // must be 0 based + text: error.formattedMessage, // text to show in tooltip + type: error.severity // "error"|"warning"|"info" + }); + errors.push({ + solcError: error, + node: + { this._onErrorClick(e, annotation); }} + key={index} + //ref={(item) => { this.refCallback(item, annotation); }} + > + + Line {errorRowCol.row} + + {error.formattedMessage} + , + annotation: annotation + }); + } + return errors; + }, []); + } + + _getRowCol(errorMessage){ + const errorSplit = errorMessage.split(':'); + if(errorSplit.length >= 3){ + return {row: errorSplit[1], col: errorSplit[2]}; + } + return {row: 0, col: 0}; + } + + _onErrorClick(e, annotation){ + e.preventDefault(); + this.editor.gotoLine(annotation.row + 1); + scrollToComponent(this.ace); } render() { const {fiddles} = this.props; - - let renderings = [ this.onCodeChange(n)} />]; - if(fiddles.compilationResult) { - renderings.push(); + let renderings = []; + let warnings = []; + let errors = []; + if (fiddles.compilationResult) { + warnings = this._getFormattedErrors(fiddles.compilationResult.errors, "warning"); + errors = this._getFormattedErrors(fiddles.compilationResult.errors, "error"); + } - else renderings.push('Nothing to compile'); - + renderings.push( + + + this._onCodeChange(n)} + errors={errors} + warnings={warnings} + ref={(fiddle) => { + if(fiddle) { + this.editor = fiddle.ace.editor; + this.ace = fiddle.ace; + } + }} + /> + + ); + if (fiddles.compilationResult) { + renderings.push( + ); + } + return ( - + +

Fiddle

+

Play around with contract code and deploy against your running node.

{renderings}
); diff --git a/embark-ui/src/general.css b/embark-ui/src/general.css index 97a265f08..819083634 100644 --- a/embark-ui/src/general.css +++ b/embark-ui/src/general.css @@ -25,5 +25,43 @@ white-space: pre-line; } .card.card-fullscreen{ - z-index:4; + z-index:6; } +.card.warnings-card, .card.errors-card{ + text-transform: capitalize; +} +.card.warnings-card .list-group-item, .card.errors-card .list-group-item{ + white-space: pre-line; +} +.card.warnings-card .card-options a, .card.errors-card .card-options a{ + cursor:pointer; +} +.compilation-summary { + float:right; + margin-bottom:3px; + line-height:30px; + visibility: hidden; +} +.compilation-summary.visible{ + visibility: visible; +} +.compilation-summary .badge-link:not(:last-child){ + margin-right:5px; +} +.ace_editor { + margin-bottom:24px; +} +.loader, .loader:before, .loader:after{ + width:1.2rem; + height:1.2rem; +} +.loader:before, .loader:after{ + margin:-0.6rem 0 0 -0.6rem; +} +.loader, .loader-text{ + display:inline-block; + vertical-align: middle; +} +.loader { + margin-right:5px; +} \ No newline at end of file diff --git a/embark-ui/src/reducers/fiddleReducer.js b/embark-ui/src/reducers/fiddleReducer.js index 4ce66f5e5..5d53d1308 100644 --- a/embark-ui/src/reducers/fiddleReducer.js +++ b/embark-ui/src/reducers/fiddleReducer.js @@ -1,11 +1,13 @@ -import {RECEIVE_COMPILE_CODE, RECEIVE_COMPILE_CODE_ERROR} from "../actions"; +import {COMPILE_CODE_REQUEST, COMPILE_CODE_FAILURE, COMPILE_CODE_SUCCESS} from "../actions"; export default function processes(state = {}, action) { switch (action.type) { - case RECEIVE_COMPILE_CODE: - return Object.assign({}, state, {compilationResult: action.compilationResult}); - case RECEIVE_COMPILE_CODE_ERROR: - return Object.assign({}, state, {error: true}); + case COMPILE_CODE_REQUEST: + return {...state, isFetching: true, compilationResult: action.compilationResult}; + case COMPILE_CODE_SUCCESS: + return {...state, isFetching: false, compilationResult: action.compilationResult}; + case COMPILE_CODE_FAILURE: + return {...state, isFetching: false, error: true}; default: return state; } diff --git a/embark-ui/src/sagas/index.js b/embark-ui/src/sagas/index.js index 85e09cc9c..5243cf852 100644 --- a/embark-ui/src/sagas/index.js +++ b/embark-ui/src/sagas/index.js @@ -170,7 +170,7 @@ export function *fetchCodeCompilation(action) { } export function *watchFetchCodeCompilation() { - yield takeEvery(actions.FETCH_COMPILE_CODE, fetchCodeCompilation); + yield takeEvery(actions.COMPILE_CODE_REQUEST, fetchCodeCompilation); } export default function *root() { diff --git a/lib/modules/solidity/index.js b/lib/modules/solidity/index.js index 386229cde..5784507d9 100644 --- a/lib/modules/solidity/index.js +++ b/lib/modules/solidity/index.js @@ -25,10 +25,7 @@ class Solidity { 'post', '/embark-api/contract/compile', (req, res) => { - console.log('=====> POST contract/compile, req = ' + JSON.stringify(req.body)); this.events.request("contract:compile", req.body.code, (errors, compilationResult) => { - console.log('=====> POST contract/compile, errors = ' + JSON.stringify(errors)); - console.log('=====> POST contract/compile, compilationResult = ' + JSON.stringify(compilationResult)); res.send({errors:errors, compilationResult: compilationResult}); }); }