Feature(challenges): Add hint system to challenges
This commit is contained in:
@ -130,7 +130,7 @@ export class FreeCodeCamp extends React.Component {
|
||||
bsStyle='primary'
|
||||
className='animated fadeIn'
|
||||
onClick={ submitChallenge }
|
||||
>
|
||||
>
|
||||
Submit and go to my next challenge
|
||||
</Button>
|
||||
);
|
||||
@ -170,7 +170,8 @@ export class FreeCodeCamp extends React.Component {
|
||||
<ToastContainer
|
||||
className='toast-bottom-right'
|
||||
ref='toaster'
|
||||
toastMessageFactory={ toastMessageFactory } />
|
||||
toastMessageFactory={ toastMessageFactory }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -66,7 +66,8 @@ export class Challenge extends PureComponent {
|
||||
return (
|
||||
<Col
|
||||
lg={ 3 }
|
||||
md={ 4 }>
|
||||
md={ 4 }
|
||||
>
|
||||
<Preview />
|
||||
</Col>
|
||||
);
|
||||
@ -85,12 +86,14 @@ export class Challenge extends PureComponent {
|
||||
<div>
|
||||
<Col
|
||||
lg={ 3 }
|
||||
md={ showPreview ? 3 : 4 }>
|
||||
md={ showPreview ? 3 : 4 }
|
||||
>
|
||||
<SidePanel />
|
||||
</Col>
|
||||
<Col
|
||||
lg={ showPreview ? 6 : 9 }
|
||||
md={ showPreview ? 5 : 8 }>
|
||||
md={ showPreview ? 5 : 8 }
|
||||
>
|
||||
<Editor
|
||||
content={ content }
|
||||
executeChallenge={ executeChallenge }
|
||||
|
@ -9,25 +9,31 @@ import TestSuite from './Test-Suite.jsx';
|
||||
import Output from './Output.jsx';
|
||||
import ToolPanel from './Tool-Panel.jsx';
|
||||
import { challengeSelector } from '../../redux/selectors';
|
||||
import { updateHint, executeChallenge } from '../../redux/actions';
|
||||
import { makeToast } from '../../../../redux/actions';
|
||||
|
||||
const bindableActions = { makeToast, executeChallenge, updateHint };
|
||||
const mapStateToProps = createSelector(
|
||||
challengeSelector,
|
||||
state => state.app.windowHeight,
|
||||
state => state.app.navHeight,
|
||||
state => state.challengesApp.tests,
|
||||
state => state.challengesApp.output,
|
||||
state => state.challengesApp.hintIndex,
|
||||
(
|
||||
{ challenge: { title, description } = {} },
|
||||
{ challenge: { title, description, hints = [] } = {} },
|
||||
windowHeight,
|
||||
navHeight,
|
||||
tests,
|
||||
output
|
||||
output,
|
||||
hintIndex
|
||||
) => ({
|
||||
title,
|
||||
description,
|
||||
height: windowHeight - navHeight - 20,
|
||||
tests,
|
||||
output
|
||||
output,
|
||||
hint: hints[hintIndex]
|
||||
})
|
||||
);
|
||||
|
||||
@ -43,7 +49,10 @@ export class SidePanel extends PureComponent {
|
||||
height: PropTypes.number,
|
||||
tests: PropTypes.arrayOf(PropTypes.object),
|
||||
title: PropTypes.string,
|
||||
output: PropTypes.string
|
||||
output: PropTypes.string,
|
||||
hints: PropTypes.string,
|
||||
updateHint: PropTypes.func,
|
||||
makeToast: PropTypes.func
|
||||
};
|
||||
|
||||
renderDescription(description = [ 'Happy Coding!' ], descriptionRegex) {
|
||||
@ -52,14 +61,16 @@ export class SidePanel extends PureComponent {
|
||||
return (
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: line }}
|
||||
key={ line.slice(-6) + index } />
|
||||
key={ line.slice(-6) + index }
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<p
|
||||
className='wrappable'
|
||||
dangerouslySetInnerHTML= {{ __html: line }}
|
||||
key={ line.slice(-6) + index }/>
|
||||
key={ line.slice(-6) + index }
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -70,7 +81,11 @@ export class SidePanel extends PureComponent {
|
||||
description,
|
||||
height,
|
||||
tests = [],
|
||||
output
|
||||
output,
|
||||
hint,
|
||||
executeChallenge,
|
||||
updateHint,
|
||||
makeToast
|
||||
} = this.props;
|
||||
const style = {
|
||||
overflowX: 'hidden',
|
||||
@ -82,7 +97,8 @@ export class SidePanel extends PureComponent {
|
||||
return (
|
||||
<div
|
||||
ref='panel'
|
||||
style={ style }>
|
||||
style={ style }
|
||||
>
|
||||
<div>
|
||||
<h4 className='text-center challenge-instructions-title'>
|
||||
{ title || 'Happy Coding!' }
|
||||
@ -91,12 +107,18 @@ export class SidePanel extends PureComponent {
|
||||
<Row>
|
||||
<Col
|
||||
className='challenge-instructions'
|
||||
xs={ 12 }>
|
||||
xs={ 12 }
|
||||
>
|
||||
{ this.renderDescription(description, this.descriptionRegex) }
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
<ToolPanel />
|
||||
<ToolPanel
|
||||
executeChallenge={ executeChallenge }
|
||||
hint={ hint }
|
||||
makeToast={ makeToast }
|
||||
updateHint={ updateHint }
|
||||
/>
|
||||
<Output output={ output }/>
|
||||
<br />
|
||||
<TestSuite tests={ tests } />
|
||||
@ -105,4 +127,7 @@ export class SidePanel extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(SidePanel);
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
bindableActions
|
||||
)(SidePanel);
|
||||
|
@ -25,13 +25,15 @@ export default class extends PureComponent {
|
||||
<Row key={ text.slice(-6) + index }>
|
||||
<Col
|
||||
className='text-center'
|
||||
xs={ 2 }>
|
||||
xs={ 2 }
|
||||
>
|
||||
<i className={ iconClass } />
|
||||
</Col>
|
||||
<Col
|
||||
className='test-output'
|
||||
dangerouslySetInnerHTML={{ __html: text }}
|
||||
xs={ 10 } />
|
||||
xs={ 10 }
|
||||
/>
|
||||
</Row>
|
||||
);
|
||||
});
|
||||
@ -42,7 +44,8 @@ export default class extends PureComponent {
|
||||
return (
|
||||
<div
|
||||
className='challenge-test-suite'
|
||||
style={{ marginTop: '10px' }}>
|
||||
style={{ marginTop: '10px' }}
|
||||
>
|
||||
{ this.renderTests(tests) }
|
||||
</div>
|
||||
);
|
||||
|
@ -1,23 +1,48 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Button, ButtonGroup } from 'react-bootstrap';
|
||||
import PureComponent from 'react-pure-render/component';
|
||||
|
||||
import { executeChallenge } from '../../redux/actions';
|
||||
|
||||
const bindableActions = { executeChallenge };
|
||||
|
||||
export class ToolPanel extends PureComponent {
|
||||
export default class ToolPanel extends PureComponent {
|
||||
constructor(...props) {
|
||||
super(...props);
|
||||
this.makeHint = this.makeHint.bind(this);
|
||||
}
|
||||
static displayName = 'ToolPanel';
|
||||
|
||||
static propTypes = {
|
||||
executeChallenge: PropTypes.func
|
||||
executeChallenge: PropTypes.func,
|
||||
updateHint: PropTypes.func,
|
||||
hint: PropTypes.string
|
||||
};
|
||||
|
||||
makeHint() {
|
||||
this.props.makeToast({
|
||||
message: this.props.hint
|
||||
});
|
||||
this.props.updateHint();
|
||||
}
|
||||
|
||||
renderHint(hint, makeHint) {
|
||||
if (!hint) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
block={ true }
|
||||
bsStyle='primary'
|
||||
className='btn-big'
|
||||
onClick={ makeHint }
|
||||
>
|
||||
Hint
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { executeChallenge } = this.props;
|
||||
const { hint, executeChallenge } = this.props;
|
||||
return (
|
||||
<div>
|
||||
{ this.renderHint(hint, this.makeHint) }
|
||||
<Button
|
||||
block={ true }
|
||||
bsStyle='primary'
|
||||
@ -58,5 +83,3 @@ export class ToolPanel extends PureComponent {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(null, bindableActions)(ToolPanel);
|
||||
|
@ -19,6 +19,7 @@ export const fetchChallengeCompleted = createAction(
|
||||
entities => ({ entities })
|
||||
);
|
||||
export const resetUi = createAction(types.resetUi);
|
||||
export const updateHint = createAction(types.updateHint);
|
||||
|
||||
export const fetchChallenges = createAction(types.fetchChallenges);
|
||||
export const fetchChallengesCompleted = createAction(
|
||||
|
@ -12,6 +12,7 @@ import {
|
||||
} from '../utils';
|
||||
|
||||
const initialUiState = {
|
||||
hintIndex: 0,
|
||||
// step index tracing
|
||||
currentIndex: 0,
|
||||
previousIndex: -1,
|
||||
@ -64,12 +65,19 @@ const mainReducer = handleActions(
|
||||
legacyKey: challenge.name,
|
||||
challenge: challenge.dashedName,
|
||||
key: getFileKey(challenge),
|
||||
tests: createTests(challenge)
|
||||
tests: createTests(challenge),
|
||||
numOfHints: Array.isArray(challenge.hints) ? challenge.hints.length : 0
|
||||
}),
|
||||
[types.updateTests]: (state, { payload: tests }) => ({
|
||||
...state,
|
||||
tests
|
||||
}),
|
||||
[types.updateHint]: state => ({
|
||||
...state,
|
||||
hintIndex: state.hintIndex + 1 >= state.numOfHints ?
|
||||
0 :
|
||||
state.hintIndex + 1
|
||||
}),
|
||||
[types.executeChallenge]: state => ({
|
||||
...state,
|
||||
tests: state.tests.map(test => ({ ...test, err: false, pass: false }))
|
||||
|
@ -13,6 +13,7 @@ export default createTypes([
|
||||
'updateCurrentChallenge',
|
||||
'replaceChallenge',
|
||||
'resetUi',
|
||||
'updateHint',
|
||||
|
||||
// map
|
||||
'updateFilter',
|
||||
|
@ -51,6 +51,11 @@
|
||||
"type": "number",
|
||||
"description": "Used to determine super block order, set by prepending two digit number to super block folder name"
|
||||
},
|
||||
"hint": {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"description": "hints must be a single line of plain text"
|
||||
},
|
||||
"block": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -19,6 +19,11 @@
|
||||
"",
|
||||
"sumAll([1, 4]);"
|
||||
],
|
||||
"hints": [
|
||||
"Use Math.max() to find the maximum value of two numbers.",
|
||||
"Use Math.min() to find the minimum value of two numbers.",
|
||||
"Remember to that you must add all the numbers in between so this would require a way to get those numbers."
|
||||
],
|
||||
"solutions": [
|
||||
"function sumAll(arr) {\n var sum = 0;\n arr.sort(function(a,b) {return a-b;});\n for (var i = arr[0]; i <= arr[1]; i++) {\n sum += i; \n }\n return sum;\n}"
|
||||
],
|
||||
|
Reference in New Issue
Block a user