Refactor(map): move map header to own component

pull/7430/head
Berkeley Martinez 2016-06-21 12:36:51 -07:00
parent 9941984262
commit a50a56a1b6
5 changed files with 155 additions and 150 deletions

View File

@ -2,7 +2,7 @@ import React, { PropTypes } from 'react';
import NoSSR from 'react-no-ssr';
import Drawer from './Drawer.jsx';
import ShowMap from '../routes/challenges/components/map/Show.jsx';
import ShowMap from '../routes/challenges/components/map/Map.jsx';
export default class MapDrawer extends React.Component {
static displayName = 'MapDrawer';
@ -18,7 +18,7 @@ export default class MapDrawer extends React.Component {
<Drawer
closeDrawer={ toggleMapDrawer }
isOpen={ isOpen }
>
>
<NoSSR>
<div>
{ isAlreadyLoaded || isOpen ? <ShowMap /> : null }

View File

@ -0,0 +1,80 @@
import React, { PropTypes } from 'react';
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 {
constructor(...props) {
super(...props);
this.handleKeyDown = this.handleKeyDown.bind(this);
}
static displayName = 'MapHeader';
static propTypes = {
filter: PropTypes.string,
clearFilter: PropTypes.func,
updateFilter: PropTypes.func
};
handleKeyDown(e) {
if (e.keyCode === ESC) {
this.props.clearFilter();
}
}
renderSearchAddon(filter, clearFilter) {
if (!filter) {
return searchIcon;
}
return <span onClick={ clearFilter }>{ clearIcon }</span>;
}
render() {
const {
updateFilter,
clearFilter,
filter
} = this.props;
const inputClass = classnames({
'map-filter': true,
filled: !!filter
});
return (
<div className='map-wrapper'>
<div
className='text-center map-fixed-header'
style={{ top: '50px' }}
>
<p>Challenges required for certifications are marked with a *</p>
<Row className='map-buttons'>
<Button
block={ true }
bsStyle='primary'
className='center-block'
>
Collapse all challenges
</Button>
</Row>
<Row className='map-buttons'>
<InputGroup>
<FormControl
autocompleted='off'
className={ inputClass }
onChange={ updateFilter }
onKeyDown={ this.handleKeyDown }
placeholder='Type a challenge name'
type='text'
value={ filter }
/>
<InputGroup.Addon>
{ this.renderSearchAddon(filter, clearFilter) }
</InputGroup.Addon>
</InputGroup>
</Row>
<hr />
</div>
</div>
);
}
}

View File

@ -1,20 +1,72 @@
import React, { PropTypes } from 'react';
import classnames from 'classnames';
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 { InputGroup, FormControl, Button, Row } from 'react-bootstrap';
import MapHeader from './Header.jsx';
import SuperBlock from './Super-Block.jsx';
import FullStack from './Full-Stack.jsx';
import CodingPrep from './Coding-Prep.jsx';
import {
clearFilter,
updateFilter,
fetchChallenges
} from '../../redux/actions';
const clearIcon = <i className='fa fa-times' />;
const searchIcon = <i className='fa fa-search' />;
const ESC = 27;
export default class ShowMap extends PureComponent {
constructor(...props) {
super(...props);
this.handleKeyDown = this.handleKeyDown.bind(this);
const bindableActions = {
clearFilter,
fetchChallenges,
updateFilter
};
const superBlocksSelector = createSelector(
state => state.challengesApp.superBlocks,
state => state.entities.superBlock,
state => state.entities.block,
state => state.entities.challenge,
(superBlocks, superBlockMap, blockMap, challengeMap) => {
if (!superBlockMap || !blockMap || !challengeMap) {
return {
superBlocks: []
};
}
return {
superBlocks: superBlocks
.map(superBlockName => superBlockMap[superBlockName])
.map(superBlock => ({
...superBlock,
blocks: superBlock.blocks
.map(blockName => blockMap[blockName])
.map(block => ({
...block,
challenges: block.challenges
.map(dashedName => challengeMap[dashedName])
}))
}))
};
}
);
const mapStateToProps = createSelector(
superBlocksSelector,
state => state.challengesApp.filter,
({ superBlocks }, filter) => {
return {
superBlocks,
filter
};
}
);
const fetchOptions = {
fetchAction: 'fetchChallenges',
isPrimed({ superBlocks }) {
return Array.isArray(superBlocks) && superBlocks.length > 1;
}
};
export class ShowMap extends PureComponent {
static displayName = 'Map';
static propTypes = {
clearFilter: PropTypes.func,
@ -23,12 +75,6 @@ export default class ShowMap extends PureComponent {
updateFilter: PropTypes.func
};
handleKeyDown(e) {
if (e.keyCode === ESC) {
this.props.clearFilter();
}
}
renderSuperBlocks(superBlocks, updateCurrentChallenge) {
if (!Array.isArray(superBlocks) || !superBlocks.length) {
return <div>No Super Blocks</div>;
@ -44,13 +90,6 @@ export default class ShowMap extends PureComponent {
});
}
renderSearchAddon(filter, clearFilter) {
if (!filter) {
return searchIcon;
}
return <span onClick={ clearFilter }>{ clearIcon }</span>;
}
render() {
const {
updateCurrentChallenge,
@ -59,46 +98,13 @@ export default class ShowMap extends PureComponent {
clearFilter,
filter
} = this.props;
const inputClass = classnames({
'map-filter': true,
filled: !!filter
});
return (
<div>
<div className='map-wrapper'>
<div
className='text-center map-fixed-header'
style={{ top: '50px' }}
>
<p>Challenges required for certifications are marked with a *</p>
<Row className='map-buttons'>
<Button
block={ true }
bsStyle='primary'
className='center-block'
>
Collapse all challenges
</Button>
</Row>
<Row className='map-buttons'>
<InputGroup>
<FormControl
autocompleted='off'
className={ inputClass }
onChange={ updateFilter }
onKeyDown={ this.handleKeyDown }
placeholder='Type a challenge name'
type='text'
value={ filter }
/>
<InputGroup.Addon>
{ this.renderSearchAddon(filter, clearFilter) }
</InputGroup.Addon>
</InputGroup>
</Row>
<hr />
</div>
</div>
<MapHeader
clearFilter={ clearFilter }
filter={ filter }
updateFilter={ updateFilter }
/>
<div
className='map-accordion'
>
@ -110,3 +116,8 @@ export default class ShowMap extends PureComponent {
);
}
}
export default compose(
connect(mapStateToProps, bindableActions),
contain(fetchOptions)
)(ShowMap);

View File

@ -1,86 +0,0 @@
import React, { PropTypes } from 'react';
import { compose } from 'redux';
import { contain } from 'redux-epic';
import { connect } from 'react-redux';
import PureComponent from 'react-pure-render/component';
import { createSelector } from 'reselect';
import Map from './Map.jsx';
import {
clearFilter,
updateFilter,
fetchChallenges
} from '../../redux/actions';
const bindableActions = {
clearFilter,
fetchChallenges,
updateFilter
};
const superBlocksSelector = createSelector(
state => state.challengesApp.superBlocks,
state => state.entities.superBlock,
state => state.entities.block,
state => state.entities.challenge,
(superBlocks, superBlockMap, blockMap, challengeMap) => {
if (!superBlockMap || !blockMap || !challengeMap) {
return {
superBlocks: []
};
}
return {
superBlocks: superBlocks
.map(superBlockName => superBlockMap[superBlockName])
.map(superBlock => ({
...superBlock,
blocks: superBlock.blocks
.map(blockName => blockMap[blockName])
.map(block => ({
...block,
challenges: block.challenges
.map(dashedName => challengeMap[dashedName])
}))
}))
};
}
);
const mapStateToProps = createSelector(
superBlocksSelector,
state => state.challengesApp.filter,
({ superBlocks }, filter) => {
return {
superBlocks,
filter
};
}
);
const fetchOptions = {
fetchAction: 'fetchChallenges',
isPrimed({ superBlocks }) {
return Array.isArray(superBlocks) && superBlocks.length > 1;
}
};
export class ShowMap extends PureComponent {
static displayName = 'ShowMap';
static propTypes = {
clearFilter: PropTypes.func,
filter: PropTypes.string,
superBlocks: PropTypes.array,
updateFilter: PropTypes.func
};
render() {
return (
<Map { ...this.props } />
);
}
}
export default compose(
connect(mapStateToProps, bindableActions),
contain(fetchOptions)
)(ShowMap);

View File

@ -1,5 +1,5 @@
import Show from './components/Show.jsx';
import ShowMap from './components/map/Show.jsx';
import Map from './components/map/Map.jsx';
export const challenges = {
path: 'challenges(/:dashedName)',
@ -19,5 +19,5 @@ export const modernChallenges = {
export const map = {
path: 'map',
component: ShowMap
component: Map
};