Skip to content

Commit

Permalink
Add initial dynamic challenge rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
Berkeley Martinez authored and Berkeley Martinez committed Jul 29, 2016
1 parent 59dcabb commit a0f6ecf
Show file tree
Hide file tree
Showing 20 changed files with 376 additions and 171 deletions.
9 changes: 9 additions & 0 deletions common/app/redux/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,18 @@ export const createErrorObserable = error => Observable.just({

// challenges
// these need to be used by more than one route so we put them here
export const fetchChallenge = createAction(types.fetchChallenge);
export const fetchChallengeCompleted = createAction(
types.fetchChallengeCompleted,
(_, challenge) => challenge,
entities => ({ entities })
);

export const fetchChallenges = createAction(types.fetchChallenges);
export const fetchChallengesCompleted = createAction(
types.fetchChallengesCompleted,
(_, superBlocks) => superBlocks,
entities => ({ entities })
);

export const setChallenge = createAction(types.setChallenge);
4 changes: 3 additions & 1 deletion common/app/redux/entities-reducer.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
const initialState = {
hike: {},
superBlock: {},
block: {},
challenge: {},
job: {}
};

export default function dataReducer(state = initialState, action) {
export default function entities(state = initialState, action) {
if (action.meta && action.meta.entities) {
return {
...state,
Expand Down
32 changes: 25 additions & 7 deletions common/app/redux/fetch-challenges-saga.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
import { fetchChallenges } from './types';
import { createErrorObserable, fetchChallengesCompleted } from './actions';
import { Observable } from 'rx';
import { fetchChallenge, fetchChallenges } from './types';
import {
createErrorObserable,
fetchChallengeCompleted,
fetchChallengesCompleted,
setChallenge
} from './actions';

export default function fetchChallengesSaga(action$, getState, { services }) {
return action$
.filter(action => action.type === fetchChallenges)
.flatMap(() => {
return services.readService$({ service: 'map' })
.map(({ entities, result } = {}) => {
return fetchChallengesCompleted(entities, result);
.filter(
({ type }) => type === fetchChallenges || type === fetchChallenge
)
.flatMap(({ type, payload })=> {
const options = { service: 'map' };
if (type === fetchChallenge) {
options.params = { dashedName: payload };
}
return services.readService$(options)
.flatMap(({ entities, result } = {}) => {
if (type === fetchChallenge) {
return Observable.of(
fetchChallengeCompleted(entities, result),
setChallenge(entities.challenge[result])
);
}
return Observable.just(fetchChallengesCompleted(entities, result));
})
.catch(createErrorObserable);
});
Expand Down
8 changes: 5 additions & 3 deletions common/app/redux/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ export default createTypes([
'updateChallengesData',
'updateJobsData',
'updateHikesData',
// challenges


// challenges
'fetchChallenge',
'fetchChallenges',
'fetchChallengesCompleted'
'fetchChallengeCompleted',
'fetchChallengesCompleted',
'setChallenge'
], 'app');
19 changes: 13 additions & 6 deletions common/app/routes/challenges/components/Challenge.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ export default class extends PureComponent {
static displayName = 'Challenge';

static propTypes = {
showPreview: PropTypes.bool
showPreview: PropTypes.bool,
challenge: PropTypes.object
};

static defaultProps = {
challenge: {}
};

renderPreview(showPreview) {
Expand All @@ -21,26 +26,28 @@ export default class extends PureComponent {
return (
<Col
lg={ 3 }
md={ 5 }>
md={ 4 }>
<Preview />
</Col>
);
}

render() {
const { showPreview } = this.props;

const { content, challenge, mode, showPreview } = this.props;
return (
<div>
<Col
lg={ 3 }
md={ showPreview ? 3 : 4 }>
<SidePanel />
<SidePanel { ...challenge }/>
</Col>
<Col
lg={ showPreview ? 6 : 9 }
md={ showPreview ? 5 : 8 }>
<Editor />
<Editor
content={ content }
mode={ mode }
/>
</Col>
{ this.renderPreview(showPreview) }
</div>
Expand Down
71 changes: 62 additions & 9 deletions common/app/routes/challenges/components/Challenges.jsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,72 @@
import React from 'react';

import React, { PropTypes } from 'react';
import { compose } from 'redux';
import { contain } from 'redux-epic';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import PureComponent from 'react-pure-render/component';

// import Challenge from './Challenge.jsx';
import Challenge from './Challenge.jsx';
import Step from './step/Step.jsx';
import { fetchChallenge } from '../../../redux/actions';
import { STEP, HTML } from '../../../utils/challengeTypes';

const bindableActions = {
fetchChallenge
};

const challengeSelector = createSelector(
state => state.challengesApp.challenge,
state => state.entities.challenge,
(challengeName, challengeMap) => {
const challenge = challengeMap[challengeName];
return {
challenge: challenge,
showPreview: !!challenge && challenge.challengeType === HTML,
isStep: !!challenge && challenge.challengeType === STEP,
mode: !!challenge && challenge.challengeType === HTML ?
'text/html' :
'javascript'
};
}
);

const mapStateToProps = createSelector(
challengeSelector,
state => state.challengesApp.content,
(challengeProps, content) => ({
...challengeProps,
content
})
);

// <Challenge showPreview={ false } />
export default class extends PureComponent {
const fetchOptions = {
fetchAction: 'fetchChallenge',
getActionArgs({ params: { dashedName } }) {
return [ dashedName ];
},
isPrimed({ challenge }) {
return challenge;
}
};

export class Challenges extends PureComponent {
static displayName = 'Challenges';
static propTypes = {};
static propTypes = {
challenge: PropTypes.object,
showPreview: PropTypes.bool,
mode: PropTypes.string,
isStep: PropTypes.bool
};

render() {
return (
<Step />
);
if (this.props.isStep) {
return <Step { ...this.props }/>;
}
return <Challenge { ...this.props }/>;
}
}

export default compose(
connect(mapStateToProps, bindableActions),
contain(fetchOptions)
)(Challenges);
15 changes: 11 additions & 4 deletions common/app/routes/challenges/components/Editor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,18 @@ const options = {
export class Editor extends PureComponent {
static displayName = 'Editor';
static propTypes = {
height: PropTypes.number
height: PropTypes.number,
content: PropTypes.string,
mode: PropTypes.string
};

static defaultProps = {
content: '// Happy Coding!',
mode: 'javascript'
};

render() {
const { height } = this.props;
const { content, height, mode } = this.props;
const style = {};
if (height) {
style.height = height + 'px';
Expand All @@ -43,8 +50,8 @@ export class Editor extends PureComponent {
style={ style }>
<NoSSR>
<Codemirror
options={ options }
value='foo test' />
options={{ ...options, mode }}
value={ content } />
</NoSSR>
</div>
);
Expand Down
20 changes: 3 additions & 17 deletions common/app/routes/challenges/components/Side-Panel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,6 @@ const mapStateToProps = createSelector(
(windowHeight, navHeight) => ({ height: windowHeight - navHeight - 50 })
);

/* eslint-disable max-len */
const description = [
'Comments are lines of code that JavaScript will intentionally ignore. Comments are a great way to leave notes to yourself and to other people who will later need to figure out what that code does.',
'There are two ways to write comments in JavaScript:',
'Using <code>//</code> will tell JavaScript to ignore the remainder of the text on the current line:',
'<blockquote>// This is an in-line comment.</blockquote>',
'You can make a multi-line comment beginning with <code>/*</code> and ending with <code>*/</code>:',
'<blockquote>/* This is a <br> multi-line comment */</blockquote>',
'<strong>Best Practice</strong><br>As you write code, you should regularly add comments to clarify the function of parts of your code. Good commenting can help communicate the intent of your code&mdash;both for others <em>and</em> for your future self.',
'<h4>Instructions</h4>',
'Try creating one of each type of comment.'
];
/* eslint-enable max-len */

export class SidePanel extends PureComponent {
constructor(...args) {
super(...args);
Expand All @@ -42,7 +28,7 @@ export class SidePanel extends PureComponent {
};

static defaultProps = {
description
description: [ 'Happy Coding!' ]
};

renderDescription(description, descriptionRegex) {
Expand All @@ -64,7 +50,7 @@ export class SidePanel extends PureComponent {
}

render() {
const { height } = this.props;
const { title, description, height } = this.props;
const style = {
overflowX: 'hidden',
overflowY: 'auto'
Expand All @@ -78,7 +64,7 @@ export class SidePanel extends PureComponent {
style={ style }>
<div>
<h4 className='text-center challenge-instructions-title'>
Build JavaScript Objects
{ title }
</h4>
<hr />
<Row>
Expand Down
Loading

0 comments on commit a0f6ecf

Please sign in to comment.