Refactor logic to FiddleContainer

Refactored fiddle logic to be contained in the `FiddleContainer` and the components as purely presentational.

Added scroll from summary to errors/warnings/fatal/deployed cards.

Added fatal error support (ie network error in api)

Removed `lodash`
This commit is contained in:
emizzle 2018-08-30 21:12:39 +10:00 committed by Pascal Precht
parent 05b324dffe
commit 6207023bec
No known key found for this signature in database
GPG Key ID: 0EE28D8D6FD85D7D
10 changed files with 299 additions and 277 deletions

View File

@ -8,7 +8,6 @@
"classnames": "^2.2.6", "classnames": "^2.2.6",
"connected-react-router": "^4.3.0", "connected-react-router": "^4.3.0",
"history": "^4.7.2", "history": "^4.7.2",
"lodash": "^4.17.10",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^16.4.1", "react": "^16.4.1",
"react-ace": "^6.1.4", "react-ace": "^6.1.4",

View File

@ -14,7 +14,7 @@ import ContractDeploymentContainer from '../containers/ContractDeploymentContain
import ContractProfileContainer from '../containers/ContractProfileContainer'; import ContractProfileContainer from '../containers/ContractProfileContainer';
import ContractSourceContainer from '../containers/ContractSourceContainer'; import ContractSourceContainer from '../containers/ContractSourceContainer';
const ContractLayout = ({match, contract}) => ( const ContractLayout = ({match, contractIsFiddle = false}) => (
<Grid.Row> <Grid.Row>
<Grid.Col md={3}> <Grid.Col md={3}>
<Page.Title className="my-5">&nbsp;</Page.Title> <Page.Title className="my-5">&nbsp;</Page.Title>
@ -28,7 +28,7 @@ const ContractLayout = ({match, contract}) => (
> >
Overview Overview
</List.GroupItem> </List.GroupItem>
{!contract.isFiddle ? {!contractIsFiddle &&
<List.GroupItem <List.GroupItem
className="d-flex align-items-center" className="d-flex align-items-center"
to={`/embark/contracts/${match.params.contractName}/deployment`} to={`/embark/contracts/${match.params.contractName}/deployment`}
@ -37,8 +37,6 @@ const ContractLayout = ({match, contract}) => (
> >
Deployment / Utils Deployment / Utils
</List.GroupItem> </List.GroupItem>
:
''
} }
<List.GroupItem <List.GroupItem
className="d-flex align-items-center" className="d-flex align-items-center"
@ -89,7 +87,8 @@ const ContractLayout = ({match, contract}) => (
); );
ContractLayout.propTypes = { ContractLayout.propTypes = {
match: PropTypes.object match: PropTypes.object,
contractIsFiddle: PropTypes.bool
}; };
export default withRouter(ContractLayout); export default withRouter(ContractLayout);

View File

@ -1,159 +1,25 @@
/* eslint {jsx-a11y/anchor-has-content:"off"} */ import React from 'react';
import React, {Component} from 'react';
import {Card, List, Badge, Icon, Dimmer, Button} from 'tabler-react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames';
import {NavLink} from 'react-router-dom';
class FiddleResults extends Component { const FiddleResults = ({warningsCard, errorsCard, fatalFiddleCard, fatalFiddleDeployCard, deployedContractsCard, fatalErrorCard, forwardedRef}) => (
<div ref={forwardedRef}>
constructor(props){ {fatalErrorCard}
super(props); {fatalFiddleCard}
{fatalFiddleDeployCard}
this.state = { {deployedContractsCard}
errorsCollapsed: false, {errorsCard}
warningsCollapsed: false, {warningsCard}
errorsFullscreen: false, </div>
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 <Card
isCollapsible={true}
isFullscreenable={true}
statusColor={color}
statusSide="true"
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={(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>
<Dimmer active={loading ? "active" : ""} loader>
<List.Group>
{errors.map(error => { return error.node; })}
</List.Group>
</Dimmer>
</Card.Body>
</Card>;
}
render() {
const {warnings, errors, fatalFiddle, fatalFiddleDeploy, isLoading, deployedContracts} = this.props;
const hasFatal = fatalFiddle || fatalFiddleDeploy;
let renderings = [];
if(hasFatal){
if(fatalFiddle){
renderings.push(
<React.Fragment key="fatal-compile">
<a id="fatal-compile" aria-hidden="true"/>
<Card
statusColor="danger"
statusSide="true"
className="fatal-card">
<Card.Header>
<Card.Title color="danger"><Icon name="slash"/> Failed to compile</Card.Title>
</Card.Header>
<Card.Body>
<Dimmer active={isLoading ? "active" : ""} loader>
{fatalFiddle}
</Dimmer>
</Card.Body>
</Card>
</React.Fragment>
);
}
if(fatalFiddleDeploy){
renderings.push(
<React.Fragment key="fatal-deploy">
<a id="fatal-deploy" aria-hidden="true"/>
<Card
statusColor="danger"
statusSide="true"
className="fatal-card">
<Card.Header>
<Card.Title color="danger"><Icon name="slash"/> Failed to deploy</Card.Title>
</Card.Header>
<Card.Body>
<Dimmer active={isLoading ? "active" : ""} loader>
{fatalFiddleDeploy}
</Dimmer>
</Card.Body>
</Card>
</React.Fragment>
);
}
}
else if (deployedContracts){
renderings.push(
<Card
statusColor="success"
statusSide="true"
className="success-card"
key="success-card">
<Card.Header>
<Card.Title color="success"><Icon name="check"/> Contract(s) deployed!</Card.Title>
</Card.Header>
<Card.Body>
<Dimmer active={isLoading ? "active" : ""} loader>
<Button
to={`/embark/contracts/${deployedContracts}/overview`}
RootComponent={NavLink}
>Play with my contract(s)</Button>
</Dimmer>
</Card.Body>
</Card>
);
}
else{
if (errors.length) renderings.push(
<React.Fragment key="errors">
<a id="errors" aria-hidden="true"/>
{this._getFormatted(errors, "error", isLoading)}
</React.Fragment>
);
if (warnings.length) renderings.push(
<React.Fragment key="warnings">
<a id="warnings" aria-hidden="true"/>
{this._getFormatted(warnings, "warning", isLoading)}
</React.Fragment>
);
}
return (
<React.Fragment>
{renderings}
</React.Fragment>
);
}
}
FiddleResults.propTypes = { FiddleResults.propTypes = {
errors: PropTypes.array, errorsCard: PropTypes.node,
warnings: PropTypes.array, warningsCard: PropTypes.node,
fatalFiddle: PropTypes.string, fatalFiddleCard: PropTypes.node,
fatalFiddleDeploy: PropTypes.string, fatalFiddleDeployCard: PropTypes.node,
isLoading: PropTypes.bool, deployedContractsCard: PropTypes.node,
deployedContracts: PropTypes.string fatalErrorCard: PropTypes.node,
forwardedRef: PropTypes.any
}; };
export default FiddleResults; export default FiddleResults;

View File

@ -1,71 +1,69 @@
import React, {Component} from 'react'; import React, {Component} from 'react';
import {Badge, Icon} from 'tabler-react'; import {Badge, Icon, Loader} from 'tabler-react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import FiddleDeployButton from './FiddleDeployButton'; import FiddleDeployButton from './FiddleDeployButton';
import classNames from 'classnames';
class FiddleResultsSummary extends Component{ class FiddleResultsSummary extends Component {
render(){ _renderFatal(fatalType, title) {
const {warnings, errors, isLoading, loadingMessage, hasResult, fatalFiddle, fatalFiddleDeploy} = this.props; return <a className="badge-link" href={`#fatal-${fatalType}`} onClick={(e) => this.props.onFatalClick(e)}><Badge color="danger"><Icon name="slash" className="mr-1" />{title}</Badge></a>;
let renderings = [];
if(isLoading){
renderings.push(
<React.Fragment key="loading"><div className="loader"></div><span className="loader-text">{loadingMessage}</span></React.Fragment>
);
}
if(fatalFiddle) {
renderings.push(
<React.Fragment key="errors">
<a className="badge-link" href="#fatal-compile"><Badge color="danger"><Icon name="slash"/> Compilation</Badge></a>
</React.Fragment>
);
} }
if(fatalFiddleDeploy) { _renderError(errorType, numErrors) {
renderings.push( const color = errorType === 'error' ? 'danger' : 'warning';
<React.Fragment key="errors"> const clickAction = errorType === 'error' ? this.props.onWarningsClick : this.props.onErrorsClick;
<a className="badge-link" href="#fatal-deploy"><Badge color="danger"><Icon name="slash"/> Deployment</Badge></a> return <a className="badge-link" href={`#${errorType}`} onClick={(e) => clickAction(e)}><Badge color={color}>{numErrors} {errorType}{numErrors > 1 ? "s" : ""}</Badge></a>;
</React.Fragment>
);
} }
if(errors.length) renderings.push( render() {
<React.Fragment key="errors"> const {numWarnings, numErrors, isLoading, loadingMessage, isVisible, showDeploy, showFatalFiddle, showFatalFiddleDeploy, showFatalError} = this.props;
<a className="badge-link" href="#errors"><Badge color="danger">{errors.length} error{errors.length > 1 ? "s" : ""}</Badge></a> const classes = classNames("compilation-summary", {
</React.Fragment> 'visible': isVisible
); });
if(warnings.length) renderings.push(
<React.Fragment key="warnings"> return (
<a className="badge-link" href="#warnings"><Badge color="warning">{warnings.length} warning{warnings.length > 1 ? "s" : ""}</Badge></a> <div className={classes}>
</React.Fragment> {isLoading &&
); <Loader className="mr-1">
if(hasResult && !errors.length){ <span className="loader-text">{loadingMessage}</span>
renderings.push( </Loader>}
{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 &&
<React.Fragment key="success"> <React.Fragment key="success">
<Badge className="badge-link" color="success">Compiled</Badge> <Badge className="badge-link" color="success">Compiled</Badge>
<FiddleDeployButton onDeployClick={(e) => this.props.onDeployClick(e)} /> <FiddleDeployButton onDeployClick={(e) => this.props.onDeployClick(e)} />
</React.Fragment> </React.Fragment>
);
} }
return (
<div className={"compilation-summary " + ((hasResult || isLoading) ? "visible" : "")}>
{renderings}
{!(hasResult || isLoading) ? "&nbsp;" : ""}
</div> </div>
); );
} }
} }
FiddleResultsSummary.propTypes = { FiddleResultsSummary.propTypes = {
errors: PropTypes.array,
warnings: PropTypes.array,
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
loadingMessage: PropTypes.string, loadingMessage: PropTypes.string,
hasResult: PropTypes.bool, isVisible: PropTypes.bool,
fatalFiddle: PropTypes.string, showDeploy: PropTypes.bool,
fatalFiddleDeploy: PropTypes.string, showFatalError: PropTypes.bool,
onDeployClick: PropTypes.func 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; export default FiddleResultsSummary;

View File

@ -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 (
<Card
statusColor={color}
statusSide="true"
className={classes}
isCollapsible={showCardOptions}
isFullscreenable={showCardOptions}>
<Card.Header>
<Card.Title color={color}>{iconName && <Icon name={iconName} className="mr-1" />}{headerTitle}</Card.Title>
{showCardOptions &&
<Card.Options className={cardOptionsClassName}>
<Card.OptionsItem key="0" type="collapse" icon="chevron-up" onClick={(e) => this._onToggle(e, 'Collapsed')} />
<Card.OptionsItem key="1" type="fullscreen" icon="maximize" onClick={(e) => this._onToggle(e, 'Fullscreen')} />
</Card.Options>
}
</Card.Header>
<Card.Body>
<Dimmer active={isLoading ? "active" : ""} loader>
{body}
</Dimmer>
</Card.Body>
</Card>
);
}
}
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;

View File

@ -14,7 +14,7 @@ class ContractLayoutContainer extends Component {
render() { render() {
if (this.props.contract){ if (this.props.contract){
return <ContractLayout contract={this.props.contract} />; return <ContractLayout contractIsFiddle={this.props.contract.isFiddle} />;
} else { } else {
return <React.Fragment />; return <React.Fragment />;
} }

View File

@ -14,6 +14,10 @@ import FiddleResultsSummary from '../components/FiddleResultsSummary';
import scrollToComponent from 'react-scroll-to-component'; import scrollToComponent from 'react-scroll-to-component';
import {getFiddle, getFiddleDeploy} from "../reducers/selectors"; import {getFiddle, getFiddleDeploy} from "../reducers/selectors";
import CompilerError from "../components/CompilerError"; 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 { class FiddleContainer extends Component {
@ -22,12 +26,16 @@ class FiddleContainer extends Component {
this.state = { this.state = {
value: undefined, value: undefined,
loadingMessage: 'Loading...', loadingMessage: 'Loading...',
readOnly: true readOnly: true
}; };
this.compileTimeout = null; this.compileTimeout = null;
this.ace = null; this.ace = null;
this.editor = null; this.editor = null;
this.warningsCardRef = null;
this.errorsCardRef = null;
this.fatalCardRef = null;
this.deployedCardRef = null;
this.fiddleResultsRef = React.createRef();
} }
componentDidMount() { componentDidMount() {
@ -37,12 +45,20 @@ class FiddleContainer extends Component {
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
const {lastFiddle} = this.props; const {lastFiddle} = this.props;
if(this.state.value === '' && prevProps.lastFiddle === lastFiddle) return; if (this.state.value === '' && prevProps.lastFiddle === lastFiddle) return;
if((!this.state.value && lastFiddle && !lastFiddle.error) && this.state.value !== lastFiddle) { if ((!this.state.value && lastFiddle && !lastFiddle.error) && this.state.value !== lastFiddle) {
this._onCodeChange(lastFiddle, true); 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) { _onCodeChange(newValue, immediate = false) {
this.setState({readOnly: false, value: newValue}); this.setState({readOnly: false, value: newValue});
if (this.compileTimeout) clearTimeout(this.compileTimeout); if (this.compileTimeout) clearTimeout(this.compileTimeout);
@ -50,10 +66,25 @@ class FiddleContainer extends Component {
this.setState({loadingMessage: 'Compiling...'}); this.setState({loadingMessage: 'Compiling...'});
this.props.postFiddle(newValue, Date.now()); this.props.postFiddle(newValue, Date.now());
}, immediate ? 0 : 1000); }, 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( return errors.reduce(
(errors, error, index) => { (errors, error, index) => {
if (error.severity === errorType) { if (error.severity === errorType) {
@ -73,7 +104,7 @@ class FiddleContainer extends Component {
index={index} index={index}
errorType={errorType} errorType={errorType}
row={errorRowCol.row} row={errorRowCol.row}
errorMessage={error.formattedMessage}/>, errorMessage={error.formattedMessage} />,
annotation: annotation annotation: annotation
}); });
} }
@ -81,46 +112,85 @@ class FiddleContainer extends Component {
}, []); }, []);
} }
_getRowCol(errorMessage){ _renderErrorsCard(errors, errorType) {
const errorSplit = errorMessage.split(':'); const color = (errorType === "error" ? "danger" : errorType);
if(errorSplit.length >= 3){
return {row: errorSplit[1], col: errorSplit[2]}; return (Boolean(errors.length) && <LoadingCardWithIcon
anchorId={errorType + "s"}
color={color}
className={errorType + "s-card "}
key={errorType + "s-card"}
showCardOptions={true}
isLoading={this.props.loading}
cardOptionsClassName={errorType + "s"}
body={
<List.Group>
{errors.map(error => { return error.node; })}
</List.Group>
} }
return {row: 0, col: 0}; headerTitle={
<React.Fragment>
<span className="mr-1">{errorType + "s"}</span><Badge color={color}>{errors.length}</Badge>
</React.Fragment>
}
ref={cardRef => { this[errorType + "sCardRef"] = cardRef; }}
/>);
} }
_onErrorClick(e, annotation){ _renderSuccessCard(title, body) {
e.preventDefault(); return this._renderLoadingCard("success", "success-card", "check", title, body, (cardRef) => {
this.editor.gotoLine(annotation.row + 1); this.deployedCardRef = cardRef;
scrollToComponent(this.ace); });
} }
_onDeployClick(_e){ _renderFatalCard(title, body) {
this.setState({loadingMessage: 'Deploying...'}); return body && this._renderLoadingCard("danger", "fatal-card", "slash", title, body, (cardRef) => {
this.props.postFiddleDeploy(this.props.fiddle.compilationResult); this.fatalCardRef = cardRef;
});
}
_renderLoadingCard(color, className, iconName, headerTitle, body, refCb) {
return (<LoadingCardWithIcon
color={color}
className={className}
iconName={iconName}
showCardOptions={false}
isLoading={this.props.loading}
body={body}
headerTitle={headerTitle}
key={hashCode([className, iconName, headerTitle].join(''))}
ref={refCb}
/>);
} }
render() { render() {
const {fiddle, loading, fiddleError, fiddleDeployError, deployedContracts} = this.props; const {fiddle, loading, fiddleError, fiddleDeployError, deployedContracts, fatalError} = this.props;
const {loadingMessage, value, readOnly} = this.state; const {loadingMessage, value, readOnly} = this.state;
let renderings = [];
let warnings = []; let warnings = [];
let errors = []; let errors = [];
if (fiddle && fiddle.errors) { if (fiddle && fiddle.errors) {
warnings = this._getFormattedErrors(fiddle.errors, "warning"); warnings = this._renderErrors(fiddle.errors, "warning");
errors = this._getFormattedErrors(fiddle.errors, "error"); errors = this._renderErrors(fiddle.errors, "error");
} }
renderings.push( const hasResult = Boolean(fiddle);
<React.Fragment key="fiddle"> return (
<React.Fragment>
<h1 className="page-title">Fiddle</h1>
<p>Play around with contract code and deploy against your running node.</p>
<FiddleResultsSummary <FiddleResultsSummary
errors={errors} numErrors={errors.length}
warnings={warnings} numWarnings={warnings.length}
isLoading={loading} isLoading={loading}
loadingMessage={loadingMessage} loadingMessage={loadingMessage}
hasResult={Boolean(fiddle)} showFatalError={Boolean(fatalError)}
fatalFiddle={fiddleError} showFatalFiddle={Boolean(fiddleError)}
fatalFiddleDeploy={fiddleDeployError} showFatalFiddleDeploy={Boolean(fiddleDeployError)}
onDeployClick={(e) => this._onDeployClick(e)} onDeployClick={(e) => 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")}
/> />
<Fiddle <Fiddle
value={value} value={value}
@ -129,32 +199,27 @@ class FiddleContainer extends Component {
errors={errors} errors={errors}
warnings={warnings} warnings={warnings}
ref={(fiddle) => { ref={(fiddle) => {
if(fiddle) { if (fiddle) {
this.editor = fiddle.ace.editor; this.editor = fiddle.ace.editor;
this.ace = fiddle.ace; this.ace = fiddle.ace;
} }
}} }}
/> />
</React.Fragment>
);
if (fiddle || (this.state.value && (fiddleError || fiddleDeployError))) {
renderings.push(
<FiddleResults <FiddleResults
key="results" key="results"
errors={errors} errorsCard={this._renderErrorsCard(errors, "error")}
warnings={warnings} warningsCard={this._renderErrorsCard(warnings, "warning")}
fatalFiddle={fiddleError} fatalErrorCard={this._renderFatalCard("Fatal error", fatalError)}
fatalFiddleDeploy={fiddleDeployError} fatalFiddleCard={this._renderFatalCard("Failed to compile", fiddleError)}
isLoading={loading} fatalFiddleDeployCard={this._renderFatalCard("Failed to deploy", fiddleDeployError)}
deployedContracts={deployedContracts} deployedContractsCard={deployedContracts && this._renderSuccessCard("Contract(s) deployed!",
/>); <Button
} to={`/embark/contracts/${deployedContracts}/overview`}
RootComponent={NavLink}
return ( >Play with my contract(s)</Button>
<React.Fragment> )}
<h1 className="page-title">Fiddle</h1> forwardedRef={this.fiddleResultsRef}
<p>Play around with contract code and deploy against your running node.</p> />
{renderings}
</React.Fragment> </React.Fragment>
); );
} }
@ -168,7 +233,8 @@ function mapStateToProps(state) {
fiddleError: fiddle.error, fiddleError: fiddle.error,
fiddleDeployError: deployedFiddle.error, fiddleDeployError: deployedFiddle.error,
loading: state.loading, 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, postFiddleDeploy: PropTypes.func,
deployedContracts: PropTypes.string, deployedContracts: PropTypes.string,
fetchLastFiddle: PropTypes.func, fetchLastFiddle: PropTypes.func,
lastFiddle: PropTypes.any lastFiddle: PropTypes.any,
fatalError: PropTypes.string
}; };
export default connect( export default connect(

View File

@ -37,7 +37,6 @@
.compilation-summary { .compilation-summary {
float: right; float: right;
margin-bottom: 3px; margin-bottom: 3px;
line-height: 30px;
visibility: hidden; visibility: hidden;
} }
.compilation-summary.visible{ .compilation-summary.visible{
@ -55,11 +54,11 @@
} }
.loader:before, .loader:after{ .loader:before, .loader:after{
margin: -0.6rem 0 0 -0.6rem; margin: -0.6rem 0 0 -0.6rem;
left: 0;
} }
.loader, .loader-text{ .loader, .loader-text{
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: top;
} padding-left: 0.6rem;
.loader { width: auto;
margin-right: 5px;
} }

View File

@ -1,4 +1,4 @@
import _ from 'lodash'; import {last} from '../utils/utils';
export function getAccounts(state) { export function getAccounts(state) {
return state.entities.accounts; return state.entities.accounts;
@ -109,7 +109,7 @@ export function getMessages(state) {
} }
export function getFiddle(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); const isNoTempFileError = Boolean(fiddleCompilation && fiddleCompilation.codeToCompile && fiddleCompilation.codeToCompile.error && fiddleCompilation.codeToCompile.error.indexOf('ENOENT') > -1);
return { return {
data: fiddleCompilation, data: fiddleCompilation,
@ -119,7 +119,7 @@ export function getFiddle(state) {
export function getFiddleDeploy(state) { export function getFiddleDeploy(state) {
return { return {
data: _.last(state.entities.fiddleDeploys), data: last(state.entities.fiddleDeploys),
error: state.errorEntities.fiddleDeploys error: state.errorEntities.fiddleDeploys
}; };
} }

View File

@ -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;
}