2018-04-16 12:04:25 +01:00
|
|
|
/* global graphql */
|
|
|
|
import React, { PureComponent } from 'react';
|
|
|
|
import PropTypes from 'prop-types';
|
|
|
|
import { createSelector } from 'reselect';
|
|
|
|
import { reduxForm } from 'redux-form';
|
|
|
|
import { Col, Row } from 'react-bootstrap';
|
|
|
|
|
|
|
|
import ChallengeTitle from '../components/Challenge-Title';
|
|
|
|
import ChallengeDescription from '../components/Challenge-Description';
|
|
|
|
import TestSuite from '../components/Test-Suite';
|
|
|
|
import Output from '../components/Output';
|
2018-05-09 13:27:42 +01:00
|
|
|
import CompletionModal from '../components/CompletionModal';
|
2018-05-18 14:54:21 +01:00
|
|
|
import ProjectToolPanel from '../project/Tool-Panel';
|
2018-04-16 12:04:25 +01:00
|
|
|
import {
|
|
|
|
executeChallenge,
|
|
|
|
challengeTestsSelector,
|
|
|
|
consoleOutputSelector,
|
|
|
|
initTests,
|
2018-04-17 11:33:04 +01:00
|
|
|
updateChallengeMeta,
|
|
|
|
backendNS
|
2018-04-16 12:04:25 +01:00
|
|
|
} from '../redux';
|
|
|
|
|
|
|
|
import {
|
|
|
|
createFormValidator,
|
|
|
|
isValidURL,
|
|
|
|
makeRequired,
|
|
|
|
Form
|
|
|
|
} from '../../../components/formHelpers';
|
2018-05-18 14:54:21 +01:00
|
|
|
import Spacer from '../../../components/util/Spacer';
|
2018-04-16 12:04:25 +01:00
|
|
|
|
|
|
|
// provided by redux form
|
|
|
|
const reduxFormPropTypes = {
|
|
|
|
fields: PropTypes.object,
|
|
|
|
handleSubmit: PropTypes.func.isRequired,
|
|
|
|
resetForm: PropTypes.func.isRequired,
|
|
|
|
submitting: PropTypes.bool
|
|
|
|
};
|
|
|
|
|
|
|
|
const propTypes = {
|
|
|
|
description: PropTypes.arrayOf(PropTypes.string),
|
|
|
|
executeChallenge: PropTypes.func.isRequired,
|
|
|
|
id: PropTypes.string,
|
|
|
|
output: PropTypes.string,
|
|
|
|
tests: PropTypes.array,
|
|
|
|
title: PropTypes.string,
|
|
|
|
...reduxFormPropTypes
|
|
|
|
};
|
|
|
|
|
|
|
|
const fields = ['solution'];
|
|
|
|
|
|
|
|
const fieldValidators = {
|
|
|
|
solution: makeRequired(isValidURL)
|
|
|
|
};
|
|
|
|
|
|
|
|
const mapStateToProps = createSelector(
|
|
|
|
consoleOutputSelector,
|
|
|
|
challengeTestsSelector,
|
|
|
|
(output, tests) => ({
|
|
|
|
tests,
|
|
|
|
output
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
const mapDispatchToActions = {
|
|
|
|
executeChallenge,
|
|
|
|
initTests,
|
|
|
|
updateChallengeMeta
|
|
|
|
};
|
|
|
|
|
|
|
|
const formFields = ['solution'];
|
|
|
|
const options = {
|
|
|
|
required: ['solution'],
|
|
|
|
types: {
|
|
|
|
solution: 'url'
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
export class BackEnd extends PureComponent {
|
2018-04-18 11:15:32 +01:00
|
|
|
componentDidMount() {
|
2018-04-16 12:04:25 +01:00
|
|
|
const {
|
|
|
|
initTests,
|
|
|
|
updateChallengeMeta,
|
2018-05-09 13:27:42 +01:00
|
|
|
data: { challengeNode: { fields: { tests }, challengeType } },
|
2018-04-16 12:04:25 +01:00
|
|
|
pathContext: { challengeMeta }
|
|
|
|
} = this.props;
|
|
|
|
initTests(tests);
|
2018-05-09 13:27:42 +01:00
|
|
|
updateChallengeMeta({ ...challengeMeta, challengeType });
|
2018-04-16 12:04:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
componentDidUpdate(prevProps) {
|
|
|
|
const { data: { challengeNode: { title: prevTitle } } } = prevProps;
|
|
|
|
const {
|
|
|
|
initTests,
|
|
|
|
updateChallengeMeta,
|
2018-05-09 13:27:42 +01:00
|
|
|
data: {
|
|
|
|
challengeNode: { title: currentTitle, fields: { tests }, challengeType }
|
|
|
|
},
|
2018-04-16 12:04:25 +01:00
|
|
|
pathContext: { challengeMeta }
|
|
|
|
} = this.props;
|
|
|
|
if (prevTitle !== currentTitle) {
|
|
|
|
initTests(tests);
|
2018-05-09 13:27:42 +01:00
|
|
|
updateChallengeMeta({ ...challengeMeta, challengeType });
|
2018-04-16 12:04:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
const {
|
|
|
|
data: { challengeNode: { fields: { blockName }, title, description } },
|
|
|
|
output,
|
|
|
|
tests,
|
2018-04-17 11:33:04 +01:00
|
|
|
submitting,
|
|
|
|
executeChallenge
|
2018-04-16 12:04:25 +01:00
|
|
|
} = this.props;
|
|
|
|
|
2018-05-09 13:27:42 +01:00
|
|
|
// TODO: Should be tied to user.isSignedIn
|
2018-04-16 12:04:25 +01:00
|
|
|
const buttonCopy = submitting
|
|
|
|
? 'Submit and go to my next challenge'
|
|
|
|
: "I've completed this challenge";
|
|
|
|
const blockNameTitle = `${blockName} - ${title}`;
|
|
|
|
return (
|
|
|
|
<Row>
|
|
|
|
<Col xs={6} xsOffset={3}>
|
2018-05-18 14:54:21 +01:00
|
|
|
<Spacer />
|
|
|
|
<div>
|
2018-04-16 12:04:25 +01:00
|
|
|
<ChallengeTitle>{blockNameTitle}</ChallengeTitle>
|
|
|
|
<ChallengeDescription description={description} />
|
2018-05-18 14:54:21 +01:00
|
|
|
</div>
|
|
|
|
<div>
|
2018-04-16 12:04:25 +01:00
|
|
|
<Form
|
|
|
|
buttonText={buttonCopy + '(Ctrl + Enter)'}
|
|
|
|
formFields={formFields}
|
2018-04-17 11:33:04 +01:00
|
|
|
id={backendNS}
|
2018-04-16 12:04:25 +01:00
|
|
|
options={options}
|
2018-04-17 11:33:04 +01:00
|
|
|
submit={executeChallenge}
|
2018-04-16 12:04:25 +01:00
|
|
|
/>
|
2018-06-01 03:36:42 +05:30
|
|
|
<ProjectToolPanel />
|
2018-05-18 14:54:21 +01:00
|
|
|
</div>
|
|
|
|
<div>
|
2018-04-16 12:04:25 +01:00
|
|
|
<br />
|
|
|
|
<Output
|
|
|
|
defaultOutput={`/**
|
2018-05-18 14:54:21 +01:00
|
|
|
*
|
2018-04-16 12:04:25 +01:00
|
|
|
* Test output will go here
|
2018-05-18 14:54:21 +01:00
|
|
|
*
|
|
|
|
*
|
2018-04-16 12:04:25 +01:00
|
|
|
*/`}
|
2018-05-18 14:54:21 +01:00
|
|
|
height={150}
|
2018-04-16 12:04:25 +01:00
|
|
|
output={output}
|
|
|
|
/>
|
2018-05-18 14:54:21 +01:00
|
|
|
</div>
|
|
|
|
<div>
|
2018-04-16 12:04:25 +01:00
|
|
|
<TestSuite tests={tests} />
|
2018-05-18 14:54:21 +01:00
|
|
|
</div>
|
|
|
|
<Spacer />
|
2018-04-16 12:04:25 +01:00
|
|
|
</Col>
|
2018-05-09 13:27:42 +01:00
|
|
|
<CompletionModal />
|
2018-04-16 12:04:25 +01:00
|
|
|
</Row>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
BackEnd.displayName = 'BackEnd';
|
|
|
|
BackEnd.propTypes = propTypes;
|
|
|
|
|
|
|
|
export default reduxForm(
|
|
|
|
{
|
|
|
|
form: 'BackEndChallenge',
|
|
|
|
fields,
|
|
|
|
validate: createFormValidator(fieldValidators)
|
|
|
|
},
|
|
|
|
mapStateToProps,
|
|
|
|
mapDispatchToActions
|
|
|
|
)(BackEnd);
|
|
|
|
|
|
|
|
export const query = graphql`
|
|
|
|
query BackendChallenge($slug: String!) {
|
|
|
|
challengeNode(fields: { slug: { eq: $slug } }) {
|
|
|
|
title
|
|
|
|
guideUrl
|
|
|
|
description
|
|
|
|
challengeType
|
|
|
|
fields {
|
|
|
|
blockName
|
|
|
|
tests {
|
|
|
|
text
|
|
|
|
testString
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`;
|