Refactor(map): move map header to own component

This commit is contained in:
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 NoSSR from 'react-no-ssr';
import Drawer from './Drawer.jsx'; 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 { export default class MapDrawer extends React.Component {
static displayName = 'MapDrawer'; static displayName = 'MapDrawer';
@ -18,7 +18,7 @@ export default class MapDrawer extends React.Component {
<Drawer <Drawer
closeDrawer={ toggleMapDrawer } closeDrawer={ toggleMapDrawer }
isOpen={ isOpen } isOpen={ isOpen }
> >
<NoSSR> <NoSSR>
<div> <div>
{ isAlreadyLoaded || isOpen ? <ShowMap /> : null } { 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 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 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 SuperBlock from './Super-Block.jsx';
import FullStack from './Full-Stack.jsx'; import FullStack from './Full-Stack.jsx';
import CodingPrep from './Coding-Prep.jsx'; import CodingPrep from './Coding-Prep.jsx';
import {
clearFilter,
updateFilter,
fetchChallenges
} from '../../redux/actions';
const clearIcon = <i className='fa fa-times' />; const bindableActions = {
const searchIcon = <i className='fa fa-search' />; clearFilter,
const ESC = 27; fetchChallenges,
export default class ShowMap extends PureComponent { updateFilter
constructor(...props) { };
super(...props); const superBlocksSelector = createSelector(
this.handleKeyDown = this.handleKeyDown.bind(this); 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 displayName = 'Map';
static propTypes = { static propTypes = {
clearFilter: PropTypes.func, clearFilter: PropTypes.func,
@ -23,12 +75,6 @@ export default class ShowMap extends PureComponent {
updateFilter: PropTypes.func updateFilter: PropTypes.func
}; };
handleKeyDown(e) {
if (e.keyCode === ESC) {
this.props.clearFilter();
}
}
renderSuperBlocks(superBlocks, updateCurrentChallenge) { renderSuperBlocks(superBlocks, updateCurrentChallenge) {
if (!Array.isArray(superBlocks) || !superBlocks.length) { if (!Array.isArray(superBlocks) || !superBlocks.length) {
return <div>No Super Blocks</div>; 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() { render() {
const { const {
updateCurrentChallenge, updateCurrentChallenge,
@ -59,46 +98,13 @@ export default class ShowMap extends PureComponent {
clearFilter, clearFilter,
filter filter
} = this.props; } = this.props;
const inputClass = classnames({
'map-filter': true,
filled: !!filter
});
return ( return (
<div> <div>
<div className='map-wrapper'> <MapHeader
<div clearFilter={ clearFilter }
className='text-center map-fixed-header' filter={ filter }
style={{ top: '50px' }} updateFilter={ updateFilter }
> />
<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>
<div <div
className='map-accordion' 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 Show from './components/Show.jsx';
import ShowMap from './components/map/Show.jsx'; import Map from './components/map/Map.jsx';
export const challenges = { export const challenges = {
path: 'challenges(/:dashedName)', path: 'challenges(/:dashedName)',
@ -19,5 +19,5 @@ export const modernChallenges = {
export const map = { export const map = {
path: 'map', path: 'map',
component: ShowMap component: Map
}; };