diff --git a/embark-ui/package.json b/embark-ui/package.json index aade0843..99671d5f 100644 --- a/embark-ui/package.json +++ b/embark-ui/package.json @@ -8,7 +8,6 @@ "classnames": "^2.2.6", "connected-react-router": "^4.3.0", "history": "^4.7.2", - "lodash": "^4.17.10", "prop-types": "^15.6.2", "react": "^16.4.1", "react-ace": "^6.1.4", diff --git a/embark-ui/src/components/ContractLayout.js b/embark-ui/src/components/ContractLayout.js index b30b7636..51060035 100644 --- a/embark-ui/src/components/ContractLayout.js +++ b/embark-ui/src/components/ContractLayout.js @@ -14,7 +14,7 @@ import ContractDeploymentContainer from '../containers/ContractDeploymentContain import ContractProfileContainer from '../containers/ContractProfileContainer'; import ContractSourceContainer from '../containers/ContractSourceContainer'; -const ContractLayout = ({match, contract}) => ( +const ContractLayout = ({match, contractIsFiddle = false}) => (   @@ -28,7 +28,7 @@ const ContractLayout = ({match, contract}) => ( > Overview - {!contract.isFiddle ? + {!contractIsFiddle && ( > Deployment / Utils - : - '' } ( ); ContractLayout.propTypes = { - match: PropTypes.object + match: PropTypes.object, + contractIsFiddle: PropTypes.bool }; export default withRouter(ContractLayout); diff --git a/embark-ui/src/components/FiddleResults.js b/embark-ui/src/components/FiddleResults.js index de7dfa30..f59204cf 100644 --- a/embark-ui/src/components/FiddleResults.js +++ b/embark-ui/src/components/FiddleResults.js @@ -1,159 +1,25 @@ -/* eslint {jsx-a11y/anchor-has-content:"off"} */ -import React, {Component} from 'react'; -import {Card, List, Badge, Icon, Dimmer, Button} from 'tabler-react'; +import React from 'react'; import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import {NavLink} from 'react-router-dom'; -class FiddleResults extends Component { - - constructor(props){ - super(props); - - this.state = { - errorsCollapsed: false, - warningsCollapsed: false, - errorsFullscreen: false, - warningsFullscreen: false - }; - } - - _toggle(e, type){ - const className = e.currentTarget.parentElement.className.replace('card-options', '').replace(' ', ''); - const updatedState = {}; - updatedState[className + type] = !(this.state[className + type]); - this.setState(updatedState); - } - - _getFormatted(errors, errorType, loading){ - 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 - - {errorType + "s"} {errors.length} - - this._toggle(e, 'Collapsed')}/> - this._toggle(e, 'Fullscreen')} /> - - - - - - {errors.map(error => { return error.node; })} - - - - ; - } - - render() { - const {warnings, errors, fatalFiddle, fatalFiddleDeploy, isLoading, deployedContracts} = this.props; - const hasFatal = fatalFiddle || fatalFiddleDeploy; - let renderings = []; - if(hasFatal){ - if(fatalFiddle){ - renderings.push( - - Compilation - - ); - } + _renderFatal(fatalType, title) { + return this.props.onFatalClick(e)}>{title}; + } + + _renderError(errorType, numErrors) { + const color = errorType === 'error' ? 'danger' : 'warning'; + const clickAction = errorType === 'error' ? this.props.onWarningsClick : this.props.onErrorsClick; + return clickAction(e)}>{numErrors} {errorType}{numErrors > 1 ? "s" : ""}; + } + + render() { + const {numWarnings, numErrors, isLoading, loadingMessage, isVisible, showDeploy, showFatalFiddle, showFatalFiddleDeploy, showFatalError} = this.props; + const classes = classNames("compilation-summary", { + 'visible': isVisible + }); - if(fatalFiddleDeploy) { - renderings.push( - - Deployment - - ); - } - - if(errors.length) renderings.push( - - {errors.length} error{errors.length > 1 ? "s" : ""} - - ); - if(warnings.length) renderings.push( - - {warnings.length} warning{warnings.length > 1 ? "s" : ""} - - ); - if(hasResult && !errors.length){ - renderings.push( - - Compiled - this.props.onDeployClick(e)} /> - - ); - } - return ( -
- {renderings} - {!(hasResult || isLoading) ? " " : ""} +
+ {isLoading && + + {loadingMessage} + } + + {showFatalError && this._renderFatal("error", "Error")} + + {showFatalFiddle && this._renderFatal("compile", "Compilation")} + + {showFatalFiddleDeploy && this._renderFatal("deploy", "Deployment")} + + {numErrors > 0 && this._renderError("error", numErrors)} + + {numWarnings > 0 && this._renderError("warning", numWarnings)} + + {showDeploy && + + Compiled + this.props.onDeployClick(e)} /> + + }
); } } FiddleResultsSummary.propTypes = { - errors: PropTypes.array, - warnings: PropTypes.array, isLoading: PropTypes.bool, loadingMessage: PropTypes.string, - hasResult: PropTypes.bool, - fatalFiddle: PropTypes.string, - fatalFiddleDeploy: PropTypes.string, - onDeployClick: PropTypes.func + isVisible: PropTypes.bool, + showDeploy: PropTypes.bool, + showFatalError: PropTypes.bool, + showFatalFiddle: PropTypes.bool, + showFatalFiddleDeploy: PropTypes.bool, + onDeployClick: PropTypes.func, + numErrors: PropTypes.number, + numWarnings: PropTypes.number, + onWarningsClick: PropTypes.func.isRequired, + onErrorsClick: PropTypes.func.isRequired, + onFatalClick: PropTypes.func.isRequired }; export default FiddleResultsSummary; diff --git a/embark-ui/src/components/LoadingCardWithIcon.js b/embark-ui/src/components/LoadingCardWithIcon.js new file mode 100644 index 00000000..3a1ed466 --- /dev/null +++ b/embark-ui/src/components/LoadingCardWithIcon.js @@ -0,0 +1,80 @@ +/* eslint {jsx-a11y/anchor-has-content:"off"} */ +import React from 'react'; +import PropTypes from 'prop-types'; +import {Card, Icon, Dimmer} from 'tabler-react'; +import classNames from 'classnames'; + +class LoadingCardWithIcon extends React.Component { + constructor(props) { + super(props); + + this.state = { + errorsCollapsed: false, + warningsCollapsed: false, + errorsFullscreen: false, + warningsFullscreen: false + }; + } + + _onToggle(e, type) { + const className = e.currentTarget.parentElement.className.replace('card-options', '').replace(' ', ''); + const updatedState = {}; + updatedState[className + type] = !(this.state[className + type]); + this.setState(updatedState); + } + + render() { + const { + color, + className, + iconName, + headerTitle, + isLoading, + body, + showCardOptions = true, + cardOptionsClassName} = this.props; + + const isFullscreen = Boolean(this.state[cardOptionsClassName + 'Fullscreen']); + const classes = classNames(className, { + 'card-fullscreen': showCardOptions && Boolean(this.state[cardOptionsClassName + 'Fullscreen']), + 'card-collapsed': showCardOptions && Boolean(this.state[cardOptionsClassName + 'Collapsed']) && !isFullscreen + }); + + return ( + + + {iconName && }{headerTitle} + {showCardOptions && + + this._onToggle(e, 'Collapsed')} /> + this._onToggle(e, 'Fullscreen')} /> + + } + + + + {body} + + + + ); + } +} +LoadingCardWithIcon.propTypes = { + color: PropTypes.string.isRequired, + className: PropTypes.string.isRequired, + iconName: PropTypes.string, + headerTitle: PropTypes.any, + isLoading: PropTypes.bool.isRequired, + body: PropTypes.node, + showCardOptions: PropTypes.bool, + onOptionToggle: PropTypes.func, + cardOptionsClassName: PropTypes.string +}; + +export default LoadingCardWithIcon; diff --git a/embark-ui/src/containers/ContractLayoutContainer.js b/embark-ui/src/containers/ContractLayoutContainer.js index b3fcf6f6..768b792d 100644 --- a/embark-ui/src/containers/ContractLayoutContainer.js +++ b/embark-ui/src/containers/ContractLayoutContainer.js @@ -14,7 +14,7 @@ class ContractLayoutContainer extends Component { render() { if (this.props.contract){ - return ; + return ; } else { return ; } diff --git a/embark-ui/src/containers/FiddleContainer.js b/embark-ui/src/containers/FiddleContainer.js index b406c62a..885a3d48 100644 --- a/embark-ui/src/containers/FiddleContainer.js +++ b/embark-ui/src/containers/FiddleContainer.js @@ -14,6 +14,10 @@ import FiddleResultsSummary from '../components/FiddleResultsSummary'; import scrollToComponent from 'react-scroll-to-component'; import {getFiddle, getFiddleDeploy} from "../reducers/selectors"; import CompilerError from "../components/CompilerError"; +import {List, Badge, Button} from 'tabler-react'; +import {NavLink} from 'react-router-dom'; +import LoadingCardWithIcon from '../components/LoadingCardWithIcon'; +import {hashCode} from '../utils/utils'; class FiddleContainer extends Component { @@ -22,12 +26,16 @@ class FiddleContainer extends Component { this.state = { value: undefined, loadingMessage: 'Loading...', - readOnly: true }; this.compileTimeout = null; this.ace = null; this.editor = null; + this.warningsCardRef = null; + this.errorsCardRef = null; + this.fatalCardRef = null; + this.deployedCardRef = null; + this.fiddleResultsRef = React.createRef(); } componentDidMount() { @@ -37,12 +45,20 @@ class FiddleContainer extends Component { componentDidUpdate(prevProps) { const {lastFiddle} = this.props; - if(this.state.value === '' && prevProps.lastFiddle === lastFiddle) return; - if((!this.state.value && lastFiddle && !lastFiddle.error) && this.state.value !== lastFiddle) { + if (this.state.value === '' && prevProps.lastFiddle === lastFiddle) return; + if ((!this.state.value && lastFiddle && !lastFiddle.error) && this.state.value !== lastFiddle) { this._onCodeChange(lastFiddle, true); } } + _getRowCol(errorMessage) { + const errorSplit = errorMessage.split(':'); + if (errorSplit.length >= 3) { + return {row: errorSplit[1], col: errorSplit[2]}; + } + return {row: 0, col: 0}; + } + _onCodeChange(newValue, immediate = false) { this.setState({readOnly: false, value: newValue}); if (this.compileTimeout) clearTimeout(this.compileTimeout); @@ -50,10 +66,25 @@ class FiddleContainer extends Component { this.setState({loadingMessage: 'Compiling...'}); this.props.postFiddle(newValue, Date.now()); }, immediate ? 0 : 1000); - } - _getFormattedErrors(errors, errorType){ + _onErrorClick(e, annotation) { + e.preventDefault(); + this.editor.gotoLine(annotation.row + 1); + scrollToComponent(this.ace); + } + + _onErrorSummaryClick(e, refName) { + scrollToComponent(this[refName]); + } + + _onDeployClick(_e) { + this.setState({loadingMessage: 'Deploying...'}); + this.props.postFiddleDeploy(this.props.fiddle.compilationResult); + scrollToComponent(this.deployedCardRef || this.fiddleResultsRef.current); // deployedCardRef null on first Deploy click + } + + _renderErrors(errors, errorType) { return errors.reduce( (errors, error, index) => { if (error.severity === errorType) { @@ -67,13 +98,13 @@ class FiddleContainer extends Component { errors.push({ solcError: error, node: - { this._onErrorClick(e, annotation); }} - key={`${errorType}_${index}`} - index={index} - errorType={errorType} - row={errorRowCol.row} - errorMessage={error.formattedMessage}/>, + { this._onErrorClick(e, annotation); }} + key={`${errorType}_${index}`} + index={index} + errorType={errorType} + row={errorRowCol.row} + errorMessage={error.formattedMessage} />, annotation: annotation }); } @@ -81,46 +112,85 @@ class FiddleContainer extends Component { }, []); } - _getRowCol(errorMessage){ - const errorSplit = errorMessage.split(':'); - if(errorSplit.length >= 3){ - return {row: errorSplit[1], col: errorSplit[2]}; - } - return {row: 0, col: 0}; + _renderErrorsCard(errors, errorType) { + const color = (errorType === "error" ? "danger" : errorType); + + return (Boolean(errors.length) && + {errors.map(error => { return error.node; })} + + } + headerTitle={ + + {errorType + "s"}{errors.length} + + } + ref={cardRef => { this[errorType + "sCardRef"] = cardRef; }} + />); } - _onErrorClick(e, annotation){ - e.preventDefault(); - this.editor.gotoLine(annotation.row + 1); - scrollToComponent(this.ace); + _renderSuccessCard(title, body) { + return this._renderLoadingCard("success", "success-card", "check", title, body, (cardRef) => { + this.deployedCardRef = cardRef; + }); } - _onDeployClick(_e){ - this.setState({loadingMessage: 'Deploying...'}); - this.props.postFiddleDeploy(this.props.fiddle.compilationResult); + _renderFatalCard(title, body) { + return body && this._renderLoadingCard("danger", "fatal-card", "slash", title, body, (cardRef) => { + this.fatalCardRef = cardRef; + }); + } + + _renderLoadingCard(color, className, iconName, headerTitle, body, refCb) { + return (); } render() { - const {fiddle, loading, fiddleError, fiddleDeployError, deployedContracts} = this.props; + const {fiddle, loading, fiddleError, fiddleDeployError, deployedContracts, fatalError} = this.props; const {loadingMessage, value, readOnly} = this.state; - let renderings = []; let warnings = []; let errors = []; if (fiddle && fiddle.errors) { - warnings = this._getFormattedErrors(fiddle.errors, "warning"); - errors = this._getFormattedErrors(fiddle.errors, "error"); + warnings = this._renderErrors(fiddle.errors, "warning"); + errors = this._renderErrors(fiddle.errors, "error"); } - renderings.push( - + const hasResult = Boolean(fiddle); + return ( + +

Fiddle

+

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

this._onDeployClick(e)} + isVisible={Boolean(fatalError || hasResult || loading)} + showDeploy={hasResult && !errors.length} + onWarningsClick={(e) => this._onErrorSummaryClick(e, "errorsCardRef")} + onErrorsClick={(e) => this._onErrorSummaryClick(e, "warningsCardRef")} + onFatalClick={(e) => this._onErrorSummaryClick(e, "fatalCardRef")} /> { - if(fiddle) { + if (fiddle) { this.editor = fiddle.ace.editor; this.ace = fiddle.ace; } }} /> -
- ); - if (fiddle || (this.state.value && (fiddleError || fiddleDeployError))) { - renderings.push( ); - } - - return ( - -

Fiddle

-

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

- {renderings} + errorsCard={this._renderErrorsCard(errors, "error")} + warningsCard={this._renderErrorsCard(warnings, "warning")} + fatalErrorCard={this._renderFatalCard("Fatal error", fatalError)} + fatalFiddleCard={this._renderFatalCard("Failed to compile", fiddleError)} + fatalFiddleDeployCard={this._renderFatalCard("Failed to deploy", fiddleDeployError)} + deployedContractsCard={deployedContracts && this._renderSuccessCard("Contract(s) deployed!", + + )} + forwardedRef={this.fiddleResultsRef} + />
); } @@ -168,7 +233,8 @@ function mapStateToProps(state) { fiddleError: fiddle.error, fiddleDeployError: deployedFiddle.error, loading: state.loading, - lastFiddle: fiddle.data ? fiddle.data.codeToCompile : undefined + lastFiddle: fiddle.data ? fiddle.data.codeToCompile : undefined, + fatalError: state.errorMessage }; } @@ -181,7 +247,8 @@ FiddleContainer.propTypes = { postFiddleDeploy: PropTypes.func, deployedContracts: PropTypes.string, fetchLastFiddle: PropTypes.func, - lastFiddle: PropTypes.any + lastFiddle: PropTypes.any, + fatalError: PropTypes.string }; export default connect( diff --git a/embark-ui/src/general.css b/embark-ui/src/general.css index 9bdf0438..97d39c92 100644 --- a/embark-ui/src/general.css +++ b/embark-ui/src/general.css @@ -37,7 +37,6 @@ .compilation-summary { float: right; margin-bottom: 3px; - line-height: 30px; visibility: hidden; } .compilation-summary.visible{ @@ -55,11 +54,11 @@ } .loader:before, .loader:after{ margin: -0.6rem 0 0 -0.6rem; + left: 0; } .loader, .loader-text{ display: inline-block; - vertical-align: middle; -} -.loader { - margin-right: 5px; + vertical-align: top; + padding-left: 0.6rem; + width: auto; } \ No newline at end of file diff --git a/embark-ui/src/reducers/selectors.js b/embark-ui/src/reducers/selectors.js index a4b032e5..94ec16b5 100644 --- a/embark-ui/src/reducers/selectors.js +++ b/embark-ui/src/reducers/selectors.js @@ -1,4 +1,4 @@ -import _ from 'lodash'; +import {last} from '../utils/utils'; export function getAccounts(state) { return state.entities.accounts; @@ -109,7 +109,7 @@ export function getMessages(state) { } export function getFiddle(state) { - const fiddleCompilation = _.last(state.entities.fiddles.sort((a, b) => { return (a.timestamp || 0) - (b.timestamp || 0); })); + const fiddleCompilation = last(state.entities.fiddles.sort((a, b) => { return (a.timestamp || 0) - (b.timestamp || 0); })); const isNoTempFileError = Boolean(fiddleCompilation && fiddleCompilation.codeToCompile && fiddleCompilation.codeToCompile.error && fiddleCompilation.codeToCompile.error.indexOf('ENOENT') > -1); return { data: fiddleCompilation, @@ -119,7 +119,7 @@ export function getFiddle(state) { export function getFiddleDeploy(state) { return { - data: _.last(state.entities.fiddleDeploys), + data: last(state.entities.fiddleDeploys), error: state.errorEntities.fiddleDeploys }; } diff --git a/embark-ui/src/utils/utils.js b/embark-ui/src/utils/utils.js new file mode 100644 index 00000000..86fb7b95 --- /dev/null +++ b/embark-ui/src/utils/utils.js @@ -0,0 +1,14 @@ +export function last(array) { + return array && array.length ? array[array.length - 1] : undefined; +} +/* eslint no-bitwise: "off" */ +export function hashCode(str) { + let hash = 0; + if (str.length === 0) return hash; + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + hash = hash & hash; // Convert to 32bit integer + } + return hash; +}