Addressed PR comments

Changed `fiddle` to an entity and removed unneeded fiddle reducer.

Added a selector for getting the entity.

Changed fiddle saga to `doRequest`.

Changed fiddle api call to the `post` method (did not see beofre the rebase).

Added `CompilerError` presentation component to handle displaying compiler errors and warnings.

Added spaces to css (as requested).

Removed extra space after function in solidity compiler (as requested).

Removed the compile contract event from the solidity compiler (as requested).

Handling of fatal api error in the UI.

Changed fiddle action to the one created with `createRequestTypes`.

Moved `Fiddle` nav tab before `Documentation`.

Changed `FiddleResults` DOM manipulation to be controlled via React state instead.
This commit is contained in:
emizzle 2018-08-13 21:44:42 +10:00 committed by Iuri Matias
parent 13e4b4dc0d
commit eee898527e
13 changed files with 193 additions and 183 deletions

View File

@ -143,30 +143,17 @@ export function listenToContractLogs() {
};
}
// Fiddle
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){
export function initBlockHeader(){
return {
type: COMPILE_CODE_REQUEST,
codeToCompile
type: INIT_BLOCK_HEADER
};
}
export function receiveCodeCompilation(compilationResult){
return {
type: COMPILE_CODE_SUCCESS,
compilationResult
};
}
export function receiveCodeCompilationError(){
return {
type: COMPILE_CODE_FAILURE
};
}
export const FIDDLE = createRequestTypes('FIDDLE');
export const fiddle = {
request: (codeToCompile) => action(FIDDLE[REQUEST], {codeToCompile}),
success: (fiddle) => action(FIDDLE[SUCCESS], {fiddle}),
failure: (error) => action(FIDDLE[FAILURE], {error})
};

View File

@ -96,6 +96,6 @@ export function webSocketBlockHeader() {
return new WebSocket(`${constants.wsEndpoint}/blockchain/blockHeader`);
}
export function fetchCodeCompilation(codeToCompile) {
return axios.post(constants.httpEndpoint + '/contract/compile', {code: codeToCompile});
export function fetchFiddle(payload) {
return post('/contract/compile', {code: payload.codeToCompile});
}

View File

@ -0,0 +1,27 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Badge} from 'tabler-react';
const CompilerError = ({key, onClick, errorType, row, errorMessage}) => (
<a
href="#editor"
className="list-group-item list-group-item-action"
onClick={onClick}
key={key}
>
<Badge color={errorType === "error" ? "danger" : errorType} className="mr-1" key={key}>
Line {row}
</Badge>
{errorMessage}
</a>
);
CompilerError.propTypes = {
key: PropTypes.string,
onClick: PropTypes.func,
errorType: PropTypes.string,
row: PropTypes.number,
errorMessage: PropTypes.string
};
export default CompilerError;

View File

@ -1,57 +1,48 @@
/* eslint {jsx-a11y/anchor-has-content:"off"} */
import React, {Component} from 'react';
import {Card, List, Badge} from 'tabler-react';
import {Card, List, Badge, Icon} from 'tabler-react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
class FiddleResults extends Component {
static _removeClass(elems, className) {
for (let elem of elems) {
elem.className = elem.className.replace(className, '').replace(' ', ' ');
}
constructor(props){
super(props);
this.state = {
errorsCollapsed: false,
warningsCollapsed: false,
errorsFullscreen: false,
warningsFullscreen: false
};
}
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;
}
}
}
_toggleCollapse(e) {
const collapsedClassName = 'card-collapsed';
_toggle(e, type){
const className = e.currentTarget.parentElement.className.replace('card-options', '').replace(' ', '');
const elems = document.getElementsByClassName(className + '-card');
FiddleResults._toggleClass(elems, collapsedClassName);
}
_toggleFullscreen(e) {
const collapsedClassName = 'card-collapsed';
const fullscreenClassName = 'card-fullscreen';
const className = e.currentTarget.parentElement.className.replace('card-options', '').replace(' ', '');
const elems = document.getElementsByClassName(className + '-card');
FiddleResults._toggleClass(elems, fullscreenClassName);
FiddleResults._removeClass(elems, collapsedClassName);
const updatedState = {};
updatedState[className + type] = !(this.state[className + type]);
this.setState(updatedState);
}
_getFormatted(errors, errorType){
const color = (errorType === "error" ? "danger" : errorType);
const isFullscreen = Boolean(this.state[errorType + 'sFullscreen']);
const classes = classNames({
'card-fullscreen': Boolean(this.state[errorType + 'sFullscreen']),
'card-collapsed': Boolean(this.state[errorType + 'sCollapsed']) && !isFullscreen
});
return <Card
isCollapsible={true}
isFullscreenable={true}
statusColor={color}
statusSide="true"
className={errorType + "s-card"}
className={errorType + "s-card " + classes}
key={errorType + "s-card"}>
<Card.Header>
<Card.Title color={color}>{errorType + "s"} <Badge color={color}>{errors.length}</Badge></Card.Title>
<Card.Options className={errorType + "s"}>
<Card.OptionsItem key="0" type="collapse" icon="chevron-up" onClick={this._toggleCollapse} />
<Card.OptionsItem key="1" type="fullscreen" icon="maximize" onClick={this._toggleFullscreen} />
<Card.OptionsItem key="0" type="collapse" icon="chevron-up" onClick={(e) => this._toggle(e, 'Collapsed')}/>
<Card.OptionsItem key="1" type="fullscreen" icon="maximize" onClick={(e) => this._toggle(e, 'Fullscreen')} />
</Card.Options>
</Card.Header>
<Card.Body>
@ -63,21 +54,42 @@ class FiddleResults extends Component {
}
render() {
const {warnings, errors} = this.props;
const {warnings, errors, fatal} = this.props;
let renderings = [];
if (errors.length) renderings.push(
<React.Fragment key="errors">
<a id="errors" aria-hidden="true"/>
{this._getFormatted(errors, "error")}
</React.Fragment>
);
if (warnings.length) renderings.push(
<React.Fragment key="warnings">
<a id="warnings" aria-hidden="true"/>
{this._getFormatted(warnings, "warning")}
</React.Fragment>
);
if(fatal){
renderings.push(
<React.Fragment key="fatal">
<a id="fatal" aria-hidden="true"/>
<Card
statusColor="danger"
statusSide="true"
className="fatal-card"
key="fatal-card">
<Card.Header>
<Card.Title color="danger"><Icon name="slash"/> Failed to compile</Card.Title>
</Card.Header>
<Card.Body>
{fatal}
</Card.Body>
</Card>
</React.Fragment>
);
}
else{
if (errors.length) renderings.push(
<React.Fragment key="errors">
<a id="errors" aria-hidden="true"/>
{this._getFormatted(errors, "error")}
</React.Fragment>
);
if (warnings.length) renderings.push(
<React.Fragment key="warnings">
<a id="warnings" aria-hidden="true"/>
{this._getFormatted(warnings, "warning")}
</React.Fragment>
);
}
return (
<React.Fragment>
@ -89,7 +101,8 @@ class FiddleResults extends Component {
FiddleResults.propTypes = {
errors: PropTypes.array,
warnings: PropTypes.array
warnings: PropTypes.array,
fatal: PropTypes.string
};
export default FiddleResults;

View File

@ -1,31 +1,39 @@
import React, {Component} from 'react';
import {Badge} from 'tabler-react';
import {Badge, Icon} from 'tabler-react';
import PropTypes from 'prop-types';
class FiddleResultsSummary extends Component{
render(){
const {warnings, errors, isFetching, hasResult} = this.props;
const {warnings, errors, isFetching, hasResult, fatal} = this.props;
let renderings = [];
if(isFetching){
if(fatal) {
renderings.push(
<React.Fragment key="errors">
<a className="badge-link" href="#fatal"><Badge color="danger"><Icon name="slash"/></Badge></a>
</React.Fragment>
);
}
else if(isFetching){
renderings.push(
<React.Fragment key="compiling"><div className="loader"></div><span className="loader-text">Compiling...</span></React.Fragment>
);
}
if(hasResult && !errors.length){
renderings.push(<Badge key="success" className="badge-link" color="success">Compiled</Badge>);
else {
if(hasResult && !errors.length){
renderings.push(<Badge key="success" className="badge-link" color="success">Compiled</Badge>);
}
if(errors.length) renderings.push(
<React.Fragment key="errors">
<a className="badge-link" href="#errors"><Badge color="danger">{errors.length} error{errors.length > 1 ? "s" : ""}</Badge></a>
</React.Fragment>
);
if(warnings.length) renderings.push(
<React.Fragment key="warnings">
<a className="badge-link" href="#warnings"><Badge color="warning">{warnings.length} warning{warnings.length > 1 ? "s" : ""}</Badge></a>
</React.Fragment>
);
}
if(errors.length) renderings.push(
<React.Fragment key="errors">
<a className="badge-link" href="#errors"><Badge color="danger">{errors.length} error{errors.length > 1 ? "s" : ""}</Badge></a>
</React.Fragment>
);
if(warnings.length) renderings.push(
<React.Fragment key="warnings">
<a className="badge-link" href="#warnings"><Badge color="warning">{warnings.length} warning{warnings.length > 1 ? "s" : ""}</Badge></a>
</React.Fragment>
);
return (
<div className={"compilation-summary " + ((hasResult || isFetching) ? "visible" : "")}>
{renderings}
@ -39,7 +47,8 @@ FiddleResultsSummary.propTypes = {
errors: PropTypes.array,
warnings: PropTypes.array,
isFetching: PropTypes.bool,
hasResult: PropTypes.bool
hasResult: PropTypes.bool,
fatal: PropTypes.string
};
export default FiddleResultsSummary;

View File

@ -10,8 +10,8 @@ const navBarItems = [
{value: "Contracts", to: "/embark/contracts", icon: "box", LinkComponent: withRouter(NavLink)},
{value: "Explorer", to: "/embark/explorer/accounts", icon: "activity", LinkComponent: withRouter(NavLink)},
{value: "Processes", to: "/embark/processes", icon: "cpu", LinkComponent: withRouter(NavLink)},
{value: "Documentation", to: "/embark/documentation", icon: "file-text", LinkComponent: withRouter(NavLink)},
{value: "Fiddle", to: "/embark/fiddle", icon: "codepen", LinkComponent: withRouter(NavLink)}
{value: "Fiddle", to: "/embark/fiddle", icon: "codepen", LinkComponent: withRouter(NavLink)},
{value: "Documentation", to: "/embark/documentation", icon: "file-text", LinkComponent: withRouter(NavLink)}
];
const Layout = (props) => (

View File

@ -3,12 +3,13 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import {fetchCodeCompilation} from '../actions';
import {fiddle as fiddleAction} from '../actions';
import Fiddle from '../components/Fiddle';
import FiddleResults from '../components/FiddleResults';
import FiddleReultsSummary from '../components/FiddleResultsSummary';
import {Badge} from 'tabler-react';
import FiddleResultsSummary from '../components/FiddleResultsSummary';
import scrollToComponent from 'react-scroll-to-component';
import {getFiddle} from "../reducers/selectors";
import CompilerError from "../components/CompilerError";
class FiddleContainer extends Component {
@ -22,17 +23,11 @@ class FiddleContainer extends Component {
this.editor = null;
}
componentDidMount() {
if (this.state.value) {
this.props.fetchCodeCompilation(this.state.value);
}
}
_onCodeChange(newValue) {
this.setState({value: newValue});
if (this.compileTimeout) clearTimeout(this.compileTimeout);
this.compileTimeout = setTimeout(() => {
this.props.fetchCodeCompilation(newValue);
this.props.fetchFiddle(newValue);
}, 1000);
}
@ -51,17 +46,12 @@ class FiddleContainer extends Component {
errors.push({
solcError: error,
node:
<a
href="#editor"
className="list-group-item list-group-item-action"
onClick={(e) => { this._onErrorClick(e, annotation); }}
key={index}
>
<Badge color={errorType === "error" ? "danger" : errorType} className="mr-1" key={index}>
Line {errorRowCol.row}
</Badge>
{error.formattedMessage}
</a>,
<CompilerError
onClick={(e) => { this._onErrorClick(e, annotation); }}
key={index}
errorType={errorType}
row={errorRowCol.row}
errorMessage={error.formattedMessage}/>,
annotation: annotation
});
}
@ -84,22 +74,22 @@ class FiddleContainer extends Component {
}
render() {
const {fiddles} = this.props;
const {fiddle, loading, error} = this.props;
let renderings = [];
let warnings = [];
let errors = [];
if (fiddles.compilationResult) {
warnings = this._getFormattedErrors(fiddles.compilationResult.errors, "warning");
errors = this._getFormattedErrors(fiddles.compilationResult.errors, "error");
if (fiddle && fiddle.errors) {
warnings = this._getFormattedErrors(fiddle.errors, "warning");
errors = this._getFormattedErrors(fiddle.errors, "error");
}
renderings.push(
<React.Fragment key="fiddle">
<FiddleReultsSummary
<FiddleResultsSummary
errors={errors}
warnings={warnings}
isFetching={fiddles.isFetching}
hasResult={Boolean(fiddles.compilationResult)}
isFetching={loading}
hasResult={Boolean(fiddle)}
fatal={error}
/>
<Fiddle
value={this.state.value}
@ -115,12 +105,13 @@ class FiddleContainer extends Component {
/>
</React.Fragment>
);
if (fiddles.compilationResult) {
if (fiddle || (this.state.value && error)) {
renderings.push(
<FiddleResults
key="results"
errors={errors}
warnings={warnings}
fatal={error}
/>);
}
@ -134,19 +125,24 @@ class FiddleContainer extends Component {
}
}
function mapStateToProps(state) {
return {
fiddles: state.fiddles
return {
fiddle: getFiddle(state),
error: state.errorMessage,
loading: state.loading
};
}
FiddleContainer.propTypes = {
fiddles: PropTypes.object,
fetchCodeCompilation: PropTypes.func
fiddle: PropTypes.object,
error: PropTypes.string,
fetchFiddle: PropTypes.func,
loading: PropTypes.bool
};
export default connect(
mapStateToProps,
{
fetchCodeCompilation
fetchFiddle: fiddleAction.request
//fetchBlock: blockAction.request
},
)(FiddleContainer);

View File

@ -21,47 +21,44 @@
color: #8f98a2;
}
.text__new-line {
.text__new-line, .card.warnings-card .list-group-item, .card.errors-card .list-group-item {
white-space: pre-line;
}
.card.card-fullscreen{
z-index:6;
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;
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;
margin-right: 5px;
}
.ace_editor {
margin-bottom:24px;
margin-bottom: 24px;
}
.loader, .loader:before, .loader:after{
width:1.2rem;
height:1.2rem;
width: 1.2rem;
height: 1.2rem;
}
.loader:before, .loader:after{
margin:-0.6rem 0 0 -0.6rem;
margin: -0.6rem 0 0 -0.6rem;
}
.loader, .loader-text{
display:inline-block;
display: inline-block;
vertical-align: middle;
}
.loader {
margin-right:5px;
margin-right: 5px;
}

View File

@ -1,14 +0,0 @@
import {COMPILE_CODE_REQUEST, COMPILE_CODE_FAILURE, COMPILE_CODE_SUCCESS} from "../actions";
export default function processes(state = {}, action) {
switch (action.type) {
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;
}
}

View File

@ -1,6 +1,5 @@
import {combineReducers} from 'redux';
import {REQUEST} from "../actions";
import fiddleRecuder from './fiddleReducer';
const BN_FACTOR = 10000;
@ -16,7 +15,8 @@ const entitiesDefaultState = {
commands: [],
messages: [],
messageChannels: [],
messageVersion: null
messageVersion: null,
fiddle: null
};
const sorter = {
@ -97,8 +97,7 @@ function loading(_state = false, action) {
const rootReducer = combineReducers({
entities,
loading,
errorMessage,
fiddles: fiddleRecuder
errorMessage
});
export default rootReducer;

View File

@ -80,3 +80,7 @@ export function getMessages(state) {
});
return messages;
}
export function getFiddle(state){
return state.entities.fiddle;
}

View File

@ -4,7 +4,8 @@ import {eventChannel} from 'redux-saga';
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, messageVersion, messageListen, contractLogs} = actions;
contracts, contract, contractProfile, messageSend, messageVersion, messageListen, contractLogs,
fiddle} = actions;
function *doRequest(entity, apiFn, payload) {
const {response, error} = yield call(apiFn, payload);
@ -28,6 +29,7 @@ export const fetchContractLogs = doRequest.bind(null, contractLogs, api.fetchCon
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 fetchFiddle = doRequest.bind(null, fiddle, api.fetchFiddle);
export function *watchFetchTransaction() {
yield takeEvery(actions.TRANSACTION[actions.REQUEST], fetchTransaction);
@ -157,20 +159,8 @@ export function *watchCommunicationVersion() {
yield takeEvery(actions.MESSAGE_VERSION[actions.REQUEST], fetchCommunicationVersion);
}
export function *fetchCodeCompilation(action) {
try {
const compilationResponse = yield call(api.fetchCodeCompilation, action.codeToCompile);
if(compilationResponse.status !== 200){
yield put(actions.receiveCodeCompilationError(compilationResponse.data));
}
else yield put(actions.receiveCodeCompilation(compilationResponse.data));
} catch (e) {
yield put(actions.receiveCodeCompilationError(e));
}
}
export function *watchFetchCodeCompilation() {
yield takeEvery(actions.COMPILE_CODE_REQUEST, fetchCodeCompilation);
export function *watchFetchFiddle() {
yield takeEvery(actions.FIDDLE[actions.REQUEST], fetchFiddle);
}
export default function *root() {
@ -194,7 +184,7 @@ export default function *root() {
fork(watchFetchContract),
fork(watchFetchTransaction),
fork(watchFetchContractProfile),
fork(watchFetchCodeCompilation)
fork(watchFetchFiddle)
]);
}

View File

@ -25,23 +25,26 @@ class Solidity {
'post',
'/embark-api/contract/compile',
(req, res) => {
this.events.request("contract:compile", req.body.code, (errors, compilationResult) => {
res.send({errors:errors, compilationResult: compilationResult});
const input = {'fiddler': {content: req.body.code.replace(/\r\n/g, '\n')}};
this.compile_solidity_code(input, {}, true, (errors, compilationResult) => {
const responseData = {errors: errors, compilationResult: compilationResult};
this.logger.trace(`POST response /embark-api/contract/compile:\n ${JSON.stringify(responseData)}`);
res.send(responseData);
});
}
);
}
_compile(jsonObj, returnAllErrors, callback){
_compile(jsonObj, returnAllErrors, callback) {
this.solcW.compile(jsonObj, (err, output) => {
if(err){
if (err) {
return callback(err);
}
if(output.errors && returnAllErrors){
if (output.errors && returnAllErrors) {
return callback(output.errors);
}
if (output.errors) {
for (let i=0; i<output.errors.length; i++) {
for (let i = 0; i < output.errors.length; i++) {
if (output.errors[i].type === 'Warning') {
this.logger.warn(output.errors[i].formattedMessage);
}
@ -54,8 +57,7 @@ class Solidity {
});
}
compile_solidity_code(codeInputs, originalFilepaths, returnAllErrors, cb){
compile_solidity_code(codeInputs, originalFilepaths, returnAllErrors, cb) {
const self = this;
async.waterfall([
@ -99,7 +101,7 @@ class Solidity {
},
function compileContracts(callback) {
self.logger.info(__("compiling solidity contracts") + "...");
let jsonObj = {
let jsonObj = {
language: 'Solidity',
sources: codeInputs,
settings: {
@ -203,7 +205,7 @@ class Solidity {
async.waterfall([
function prepareInput(callback) {
async.each(contractFiles,
function(file, fileCb) {
function (file, fileCb) {
let filename = file.filename;
for (let directory of self.contractDirectories) {
@ -213,7 +215,7 @@ class Solidity {
originalFilepath[filename] = file.filename;
file.content(function(fileContent) {
file.content(function (fileContent) {
if (!fileContent) {
self.logger.error(__('Error while loading the content of ') + filename);
return fileCb();