Feature(map): Add top level collapse

This commit is contained in:
Berkeley Martinez
2016-06-21 16:28:13 -07:00
parent a50a56a1b6
commit b8434edd51
9 changed files with 163 additions and 47 deletions

View File

@ -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 (
<div>
<SuperBlock
blocks={ codingPrep }
message={ lockMessage }
title='Coding Interview Prep '
/>
</div>
<SuperBlock
blocks={ codingPrep }
dashedName={ dashedName }
mapUi={ mapUi }
message={ lockMessage }
title={ title }
toggleThisPanel={ toggleThisPanel }
/>
);
}
}

View File

@ -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 (
<SuperBlock
blocks={ [ nonprofitProjects ] }
dashedName={ dashedName }
mapUi={ mapUi }
message={ lockMessage }
title={ title }
toggleThisPanel={ toggleThisPanel }
/>
);
}

View File

@ -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 = <i className='fa fa-times' />;
const searchIcon = <i className='fa fa-search' />;
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);

View File

@ -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 <div>No Super Blocks</div>;
}
return superBlocks.map((superBlock) => {
return (
<SuperBlock
key={ superBlock.title }
updateCurrentChallenge={ updateCurrentChallenge }
{ ...superBlock }
/>
);
});
return superBlocks
.map((superBlock) => {
return (
<SuperBlock
key={ superBlock.title }
mapUi={ mapUi }
toggleThisPanel={ toggleThisPanel }
updateCurrentChallenge={ updateCurrentChallenge }
{ ...superBlock }
/>
);
});
}
render() {
@ -96,7 +109,9 @@ export class ShowMap extends PureComponent {
superBlocks,
updateFilter,
clearFilter,
filter
filter,
mapUi,
toggleThisPanel
} = this.props;
return (
<div>
@ -105,12 +120,24 @@ export class ShowMap extends PureComponent {
filter={ filter }
updateFilter={ updateFilter }
/>
<div
className='map-accordion'
>
{ this.renderSuperBlocks(superBlocks, updateCurrentChallenge) }
<FullStack />
<CodingPrep />
<div className='map-accordion'>
{
this.renderSuperBlocks(
superBlocks,
updateCurrentChallenge,
mapUi,
toggleThisPanel
)
}
<FullStack
mapUi={ mapUi }
toggleThisPanel={ toggleThisPanel }
/>
<CodingPrep
mapUi={ mapUi }
toggleThisPanel={ toggleThisPanel }
/>
<div className='spacer' />
</div>
</div>
);

View File

@ -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 <div>No Blocks Found</div>;
@ -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 (
<Panel
bsClass='map-accordion-panel'
collapsible={ true }
expanded={ true }
eventKey={ dashedName || title }
expanded={ dashedName ? mapUi[dashedName] : true }
header={ <h2><FA name='caret-right' />{ title }</h2> }
id={ title }
key={ title }
key={ dashedName || title }
onSelect={ this.handleSelect }
>
{
message ?

View File

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

View File

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

View File

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

View File

@ -20,6 +20,8 @@ export default createTypes([
// map
'updateFilter',
'clearFilter',
'initMap',
'toggleThisPanel',
// files
'updateFile',