Feature(map): Add top level collapse
This commit is contained in:
@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import PureComponent from 'react-pure-render/component';
|
import PureComponent from 'react-pure-render/component';
|
||||||
import dedent from 'dedent';
|
import dedent from 'dedent';
|
||||||
import SuperBlock from './Super-Block.jsx';
|
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 {
|
export default class CodingPrep extends PureComponent {
|
||||||
static displayName = 'CodingPrep;'
|
static displayName = 'CodingPrep;'
|
||||||
|
static propTypes = {
|
||||||
|
mapUi: PropTypes.object,
|
||||||
|
toggleThisPanel: PropTypes.func
|
||||||
|
};
|
||||||
render() {
|
render() {
|
||||||
|
const { mapUi, toggleThisPanel } = this.props;
|
||||||
return (
|
return (
|
||||||
<div>
|
|
||||||
<SuperBlock
|
<SuperBlock
|
||||||
blocks={ codingPrep }
|
blocks={ codingPrep }
|
||||||
|
dashedName={ dashedName }
|
||||||
|
mapUi={ mapUi }
|
||||||
message={ lockMessage }
|
message={ lockMessage }
|
||||||
title='Coding Interview Prep '
|
title={ title }
|
||||||
|
toggleThisPanel={ toggleThisPanel }
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import PureComponent from 'react-pure-render/component';
|
import PureComponent from 'react-pure-render/component';
|
||||||
import dedent from 'dedent';
|
import dedent from 'dedent';
|
||||||
import SuperBlock from './Super-Block.jsx';
|
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 {
|
export default class FullStack extends PureComponent {
|
||||||
static displayName = 'FullStack';
|
static displayName = 'FullStack';
|
||||||
|
static propTypes = {
|
||||||
|
mapUi: PropTypes.object,
|
||||||
|
toggleThisPanel: PropTypes.func
|
||||||
|
};
|
||||||
render() {
|
render() {
|
||||||
const title = 'Full Stack Development Certification';
|
const { mapUi, toggleThisPanel } = this.props;
|
||||||
return (
|
return (
|
||||||
<SuperBlock
|
<SuperBlock
|
||||||
blocks={ [ nonprofitProjects ] }
|
blocks={ [ nonprofitProjects ] }
|
||||||
|
dashedName={ dashedName }
|
||||||
|
mapUi={ mapUi }
|
||||||
message={ lockMessage }
|
message={ lockMessage }
|
||||||
title={ title }
|
title={ title }
|
||||||
|
toggleThisPanel={ toggleThisPanel }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
|
import PureComponent from 'react-pure-render/component';
|
||||||
import { InputGroup, FormControl, Button, Row } from 'react-bootstrap';
|
import { InputGroup, FormControl, Button, Row } from 'react-bootstrap';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
const clearIcon = <i className='fa fa-times' />;
|
const clearIcon = <i className='fa fa-times' />;
|
||||||
const searchIcon = <i className='fa fa-search' />;
|
const searchIcon = <i className='fa fa-search' />;
|
||||||
const ESC = 27;
|
const ESC = 27;
|
||||||
export default class Header extends React.Component {
|
export default class Header extends PureComponent {
|
||||||
constructor(...props) {
|
constructor(...props) {
|
||||||
super(...props);
|
super(...props);
|
||||||
this.handleKeyDown = this.handleKeyDown.bind(this);
|
this.handleKeyDown = this.handleKeyDown.bind(this);
|
||||||
|
@ -11,13 +11,15 @@ import FullStack from './Full-Stack.jsx';
|
|||||||
import CodingPrep from './Coding-Prep.jsx';
|
import CodingPrep from './Coding-Prep.jsx';
|
||||||
import {
|
import {
|
||||||
clearFilter,
|
clearFilter,
|
||||||
updateFilter,
|
fetchChallenges,
|
||||||
fetchChallenges
|
toggleThisPanel,
|
||||||
|
updateFilter
|
||||||
} from '../../redux/actions';
|
} from '../../redux/actions';
|
||||||
|
|
||||||
const bindableActions = {
|
const bindableActions = {
|
||||||
clearFilter,
|
clearFilter,
|
||||||
fetchChallenges,
|
fetchChallenges,
|
||||||
|
toggleThisPanel,
|
||||||
updateFilter
|
updateFilter
|
||||||
};
|
};
|
||||||
const superBlocksSelector = createSelector(
|
const superBlocksSelector = createSelector(
|
||||||
@ -51,10 +53,12 @@ const superBlocksSelector = createSelector(
|
|||||||
const mapStateToProps = createSelector(
|
const mapStateToProps = createSelector(
|
||||||
superBlocksSelector,
|
superBlocksSelector,
|
||||||
state => state.challengesApp.filter,
|
state => state.challengesApp.filter,
|
||||||
({ superBlocks }, filter) => {
|
state => state.challengesApp.map,
|
||||||
|
({ superBlocks }, filter, mapUi) => {
|
||||||
return {
|
return {
|
||||||
superBlocks,
|
superBlocks,
|
||||||
filter
|
filter,
|
||||||
|
mapUi
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -72,17 +76,26 @@ export class ShowMap extends PureComponent {
|
|||||||
clearFilter: PropTypes.func,
|
clearFilter: PropTypes.func,
|
||||||
filter: PropTypes.string,
|
filter: PropTypes.string,
|
||||||
superBlocks: PropTypes.array,
|
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) {
|
if (!Array.isArray(superBlocks) || !superBlocks.length) {
|
||||||
return <div>No Super Blocks</div>;
|
return <div>No Super Blocks</div>;
|
||||||
}
|
}
|
||||||
return superBlocks.map((superBlock) => {
|
return superBlocks
|
||||||
|
.map((superBlock) => {
|
||||||
return (
|
return (
|
||||||
<SuperBlock
|
<SuperBlock
|
||||||
key={ superBlock.title }
|
key={ superBlock.title }
|
||||||
|
mapUi={ mapUi }
|
||||||
|
toggleThisPanel={ toggleThisPanel }
|
||||||
updateCurrentChallenge={ updateCurrentChallenge }
|
updateCurrentChallenge={ updateCurrentChallenge }
|
||||||
{ ...superBlock }
|
{ ...superBlock }
|
||||||
/>
|
/>
|
||||||
@ -96,7 +109,9 @@ export class ShowMap extends PureComponent {
|
|||||||
superBlocks,
|
superBlocks,
|
||||||
updateFilter,
|
updateFilter,
|
||||||
clearFilter,
|
clearFilter,
|
||||||
filter
|
filter,
|
||||||
|
mapUi,
|
||||||
|
toggleThisPanel
|
||||||
} = this.props;
|
} = this.props;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -105,12 +120,24 @@ export class ShowMap extends PureComponent {
|
|||||||
filter={ filter }
|
filter={ filter }
|
||||||
updateFilter={ updateFilter }
|
updateFilter={ updateFilter }
|
||||||
/>
|
/>
|
||||||
<div
|
<div className='map-accordion'>
|
||||||
className='map-accordion'
|
{
|
||||||
>
|
this.renderSuperBlocks(
|
||||||
{ this.renderSuperBlocks(superBlocks, updateCurrentChallenge) }
|
superBlocks,
|
||||||
<FullStack />
|
updateCurrentChallenge,
|
||||||
<CodingPrep />
|
mapUi,
|
||||||
|
toggleThisPanel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<FullStack
|
||||||
|
mapUi={ mapUi }
|
||||||
|
toggleThisPanel={ toggleThisPanel }
|
||||||
|
/>
|
||||||
|
<CodingPrep
|
||||||
|
mapUi={ mapUi }
|
||||||
|
toggleThisPanel={ toggleThisPanel }
|
||||||
|
/>
|
||||||
|
<div className='spacer' />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -6,13 +6,25 @@ import { Panel } from 'react-bootstrap';
|
|||||||
import Block from './Block.jsx';
|
import Block from './Block.jsx';
|
||||||
|
|
||||||
export default class SuperBlock extends PureComponent {
|
export default class SuperBlock extends PureComponent {
|
||||||
|
constructor(...props) {
|
||||||
|
super(...props);
|
||||||
|
this.handleSelect = this.handleSelect.bind(this);
|
||||||
|
}
|
||||||
static displayName = 'SuperBlock';
|
static displayName = 'SuperBlock';
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
|
dashedName: PropTypes.string,
|
||||||
message: 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) {
|
renderBlocks(blocks) {
|
||||||
if (!Array.isArray(blocks) || !blocks.length) {
|
if (!Array.isArray(blocks) || !blocks.length) {
|
||||||
return <div>No Blocks Found</div>;
|
return <div>No Blocks Found</div>;
|
||||||
@ -28,15 +40,17 @@ export default class SuperBlock extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { title, blocks, message } = this.props;
|
const { title, dashedName, blocks, message, mapUi = {} } = this.props;
|
||||||
return (
|
return (
|
||||||
<Panel
|
<Panel
|
||||||
bsClass='map-accordion-panel'
|
bsClass='map-accordion-panel'
|
||||||
collapsible={ true }
|
collapsible={ true }
|
||||||
expanded={ true }
|
eventKey={ dashedName || title }
|
||||||
|
expanded={ dashedName ? mapUi[dashedName] : true }
|
||||||
header={ <h2><FA name='caret-right' />{ title }</h2> }
|
header={ <h2><FA name='caret-right' />{ title }</h2> }
|
||||||
id={ title }
|
id={ title }
|
||||||
key={ title }
|
key={ dashedName || title }
|
||||||
|
onSelect={ this.handleSelect }
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
message ?
|
message ?
|
||||||
|
@ -42,6 +42,29 @@ export const updateFilter = createAction(
|
|||||||
e => e.target.value
|
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);
|
export const clearFilter = createAction(types.clearFilter);
|
||||||
|
|
||||||
// files
|
// files
|
||||||
|
@ -5,15 +5,16 @@ import {
|
|||||||
fetchChallenges,
|
fetchChallenges,
|
||||||
replaceChallenge
|
replaceChallenge
|
||||||
} from './types';
|
} from './types';
|
||||||
|
import {
|
||||||
|
fetchChallengeCompleted,
|
||||||
|
fetchChallengesCompleted,
|
||||||
|
updateCurrentChallenge,
|
||||||
|
initMap
|
||||||
|
} from './actions';
|
||||||
import {
|
import {
|
||||||
delayedRedirect,
|
delayedRedirect,
|
||||||
createErrorObserable
|
createErrorObserable
|
||||||
} from '../../../redux/actions';
|
} from '../../../redux/actions';
|
||||||
import {
|
|
||||||
fetchChallengeCompleted,
|
|
||||||
fetchChallengesCompleted,
|
|
||||||
updateCurrentChallenge
|
|
||||||
} from './actions';
|
|
||||||
|
|
||||||
function createNameIdMap(entities) {
|
function createNameIdMap(entities) {
|
||||||
const { challenge } = entities;
|
const { challenge } = entities;
|
||||||
@ -68,11 +69,12 @@ export default function fetchChallengesSaga(action$, getState, { services }) {
|
|||||||
redirect ? delayedRedirect(redirect) : null
|
redirect ? delayedRedirect(redirect) : null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Observable.just(
|
return Observable.of(
|
||||||
fetchChallengesCompleted(
|
fetchChallengesCompleted(
|
||||||
createNameIdMap(entities),
|
createNameIdMap(entities),
|
||||||
result
|
result
|
||||||
)
|
),
|
||||||
|
initMap(entities, result),
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.catch(createErrorObserable);
|
.catch(createErrorObserable);
|
||||||
|
@ -46,6 +46,10 @@ const initialState = {
|
|||||||
legacyKey: '',
|
legacyKey: '',
|
||||||
files: {},
|
files: {},
|
||||||
// map
|
// map
|
||||||
|
map: {
|
||||||
|
'full-stack': true,
|
||||||
|
'coding-prep': true
|
||||||
|
},
|
||||||
filter: '',
|
filter: '',
|
||||||
superBlocks: [],
|
superBlocks: [],
|
||||||
// misc
|
// 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) {
|
export default function challengeReducers(state, action) {
|
||||||
const newState = mainReducer(state, action);
|
const newState = mainReducer(state, action);
|
||||||
const files = filesReducer(state && state.files || {}, 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;
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,8 @@ export default createTypes([
|
|||||||
// map
|
// map
|
||||||
'updateFilter',
|
'updateFilter',
|
||||||
'clearFilter',
|
'clearFilter',
|
||||||
|
'initMap',
|
||||||
|
'toggleThisPanel',
|
||||||
|
|
||||||
// files
|
// files
|
||||||
'updateFile',
|
'updateFile',
|
||||||
|
Reference in New Issue
Block a user