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 b57f63d0..8c3da60b 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 6fc98aba..99671d5f 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 0585b5c0..00000000 --- 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 50b6670b..834b5d97 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 6d618304..a380fe93 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 1f69fc00..b79a55d1 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 cb385c9c..fd9e6167 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 97a265f0..81908363 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 4ce66f5e..5d53d130 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 85e09cc9..5243cf85 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 386229cd..5784507d 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}); }); }