diff --git a/common/app/routes/challenges/components/map/Coding-Prep.jsx b/common/app/routes/challenges/components/map/Coding-Prep.jsx index d91b2dcb74..ce06f7e69b 100644 --- a/common/app/routes/challenges/components/map/Coding-Prep.jsx +++ b/common/app/routes/challenges/components/map/Coding-Prep.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { PropTypes } from 'react'; import PureComponent from 'react-pure-render/component'; import dedent from 'dedent'; import SuperBlock from './Super-Block.jsx'; @@ -44,19 +44,25 @@ const codingPrep = [{ } ] }]; - +const title = 'Coding Interview Prep'; +const dashedName = 'coding-prep'; export default class CodingPrep extends PureComponent { static displayName = 'CodingPrep;' - + static propTypes = { + mapUi: PropTypes.object, + toggleThisPanel: PropTypes.func + }; render() { + const { mapUi, toggleThisPanel } = this.props; return ( -
- -
+ ); } } diff --git a/common/app/routes/challenges/components/map/Full-Stack.jsx b/common/app/routes/challenges/components/map/Full-Stack.jsx index ba65d445f0..247c61ca95 100644 --- a/common/app/routes/challenges/components/map/Full-Stack.jsx +++ b/common/app/routes/challenges/components/map/Full-Stack.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { PropTypes } from 'react'; import PureComponent from 'react-pure-render/component'; import dedent from 'dedent'; import SuperBlock from './Super-Block.jsx'; @@ -40,15 +40,24 @@ const nonprofitProjects = { ] }; +const title = 'Full Stack Development Certification'; +const dashedName = 'full-stack'; export default class FullStack extends PureComponent { static displayName = 'FullStack'; + static propTypes = { + mapUi: PropTypes.object, + toggleThisPanel: PropTypes.func + }; render() { - const title = 'Full Stack Development Certification'; + const { mapUi, toggleThisPanel } = this.props; return ( ); } diff --git a/common/app/routes/challenges/components/map/Header.jsx b/common/app/routes/challenges/components/map/Header.jsx index 8fd2c8e7d4..61543bb334 100644 --- a/common/app/routes/challenges/components/map/Header.jsx +++ b/common/app/routes/challenges/components/map/Header.jsx @@ -1,11 +1,12 @@ import React, { PropTypes } from 'react'; +import PureComponent from 'react-pure-render/component'; import { InputGroup, FormControl, Button, Row } from 'react-bootstrap'; import classnames from 'classnames'; const clearIcon = ; const searchIcon = ; const ESC = 27; -export default class Header extends React.Component { +export default class Header extends PureComponent { constructor(...props) { super(...props); this.handleKeyDown = this.handleKeyDown.bind(this); diff --git a/common/app/routes/challenges/components/map/Map.jsx b/common/app/routes/challenges/components/map/Map.jsx index e584ebed37..10a4571224 100644 --- a/common/app/routes/challenges/components/map/Map.jsx +++ b/common/app/routes/challenges/components/map/Map.jsx @@ -11,13 +11,15 @@ import FullStack from './Full-Stack.jsx'; import CodingPrep from './Coding-Prep.jsx'; import { clearFilter, - updateFilter, - fetchChallenges + fetchChallenges, + toggleThisPanel, + updateFilter } from '../../redux/actions'; const bindableActions = { clearFilter, fetchChallenges, + toggleThisPanel, updateFilter }; const superBlocksSelector = createSelector( @@ -51,10 +53,12 @@ const superBlocksSelector = createSelector( const mapStateToProps = createSelector( superBlocksSelector, state => state.challengesApp.filter, - ({ superBlocks }, filter) => { + state => state.challengesApp.map, + ({ superBlocks }, filter, mapUi) => { return { superBlocks, - filter + filter, + mapUi }; } ); @@ -72,22 +76,31 @@ export class ShowMap extends PureComponent { clearFilter: PropTypes.func, filter: PropTypes.string, superBlocks: PropTypes.array, - updateFilter: PropTypes.func + updateFilter: PropTypes.func, + mapUi: PropTypes.object }; - renderSuperBlocks(superBlocks, updateCurrentChallenge) { + renderSuperBlocks( + superBlocks, + updateCurrentChallenge, + mapUi, + toggleThisPanel + ) { if (!Array.isArray(superBlocks) || !superBlocks.length) { return
No Super Blocks
; } - return superBlocks.map((superBlock) => { - return ( - - ); - }); + return superBlocks + .map((superBlock) => { + return ( + + ); + }); } render() { @@ -96,7 +109,9 @@ export class ShowMap extends PureComponent { superBlocks, updateFilter, clearFilter, - filter + filter, + mapUi, + toggleThisPanel } = this.props; return (
@@ -105,12 +120,24 @@ export class ShowMap extends PureComponent { filter={ filter } updateFilter={ updateFilter } /> -
- { this.renderSuperBlocks(superBlocks, updateCurrentChallenge) } - - +
+ { + this.renderSuperBlocks( + superBlocks, + updateCurrentChallenge, + mapUi, + toggleThisPanel + ) + } + + +
); diff --git a/common/app/routes/challenges/components/map/Super-Block.jsx b/common/app/routes/challenges/components/map/Super-Block.jsx index a8aa048066..220b00e060 100644 --- a/common/app/routes/challenges/components/map/Super-Block.jsx +++ b/common/app/routes/challenges/components/map/Super-Block.jsx @@ -6,13 +6,25 @@ import { Panel } from 'react-bootstrap'; import Block from './Block.jsx'; export default class SuperBlock extends PureComponent { + constructor(...props) { + super(...props); + this.handleSelect = this.handleSelect.bind(this); + } static displayName = 'SuperBlock'; static propTypes = { title: PropTypes.string, + dashedName: PropTypes.string, message: PropTypes.string, - blocks: PropTypes.array + blocks: PropTypes.array, + mapUi: PropTypes.object, + toggleThisPanl: PropTypes.func }; + handleSelect(eventKey, e) { + e.preventDefault(); + this.props.toggleThisPanel(eventKey); + } + renderBlocks(blocks) { if (!Array.isArray(blocks) || !blocks.length) { return
No Blocks Found
; @@ -28,15 +40,17 @@ export default class SuperBlock extends PureComponent { } render() { - const { title, blocks, message } = this.props; + const { title, dashedName, blocks, message, mapUi = {} } = this.props; return ( { title } } id={ title } - key={ title } + key={ dashedName || title } + onSelect={ this.handleSelect } > { message ? diff --git a/common/app/routes/challenges/redux/actions.js b/common/app/routes/challenges/redux/actions.js index a62eeabe95..f48790d141 100644 --- a/common/app/routes/challenges/redux/actions.js +++ b/common/app/routes/challenges/redux/actions.js @@ -42,6 +42,29 @@ export const updateFilter = createAction( e => e.target.value ); +function createMapKey(map, key) { + map[key] = true; + return map; +} +export const initMap = createAction( + types.initMap, + ( + { superBlock: superBlockMap }, + superBlocks + ) => { + if (!superBlocks || !superBlockMap) { + return {}; + } + const blocks = superBlocks + .map(superBlock => superBlockMap[superBlock].blocks) + .reduce((blocks, block) => blocks.concat(block)); + return superBlocks + .concat(blocks) + .reduce(createMapKey, {}); + } +); +export const toggleThisPanel = createAction(types.toggleThisPanel); + export const clearFilter = createAction(types.clearFilter); // files diff --git a/common/app/routes/challenges/redux/fetch-challenges-saga.js b/common/app/routes/challenges/redux/fetch-challenges-saga.js index 72cc63c904..51e8c89911 100644 --- a/common/app/routes/challenges/redux/fetch-challenges-saga.js +++ b/common/app/routes/challenges/redux/fetch-challenges-saga.js @@ -5,15 +5,16 @@ import { fetchChallenges, replaceChallenge } from './types'; +import { + fetchChallengeCompleted, + fetchChallengesCompleted, + updateCurrentChallenge, + initMap +} from './actions'; import { delayedRedirect, createErrorObserable } from '../../../redux/actions'; -import { - fetchChallengeCompleted, - fetchChallengesCompleted, - updateCurrentChallenge -} from './actions'; function createNameIdMap(entities) { const { challenge } = entities; @@ -68,11 +69,12 @@ export default function fetchChallengesSaga(action$, getState, { services }) { redirect ? delayedRedirect(redirect) : null ); } - return Observable.just( + return Observable.of( fetchChallengesCompleted( createNameIdMap(entities), result - ) + ), + initMap(entities, result), ); }) .catch(createErrorObserable); diff --git a/common/app/routes/challenges/redux/reducer.js b/common/app/routes/challenges/redux/reducer.js index 9308f60cde..61bbc9b062 100644 --- a/common/app/routes/challenges/redux/reducer.js +++ b/common/app/routes/challenges/redux/reducer.js @@ -46,6 +46,10 @@ const initialState = { legacyKey: '', files: {}, // map + map: { + 'full-stack': true, + 'coding-prep': true + }, filter: '', superBlocks: [], // misc @@ -230,8 +234,36 @@ const filesReducer = handleActions( {} ); +// { +// // show +// [(super)BlockName]: { open: Boolean } +// // do not show +// [(super)BlockName]: null +// } +const mapReducer = handleActions( + { + [types.initMap]: (state, { payload }) => ({ + ...state, + ...payload + }), + [types.toggleThisPanel]: (state, { payload }) => ({ + ...state, + [payload]: !state[payload] + }) + }, + initialState.map +); + export default function challengeReducers(state, action) { const newState = mainReducer(state, action); const files = filesReducer(state && state.files || {}, action); - return newState.files !== files ? { ...newState, files } : newState; + if (newState.files !== files) { + return { ...newState, files }; + } + // map actions only effect this reducer; + const map = mapReducer(state && state.map || {}, action); + if (newState.map !== map) { + return { ...newState, map }; + } + return newState; } diff --git a/common/app/routes/challenges/redux/types.js b/common/app/routes/challenges/redux/types.js index 9073cd5b84..c758986620 100644 --- a/common/app/routes/challenges/redux/types.js +++ b/common/app/routes/challenges/redux/types.js @@ -20,6 +20,8 @@ export default createTypes([ // map 'updateFilter', 'clearFilter', + 'initMap', + 'toggleThisPanel', // files 'updateFile',