2019-09-21 19:09:48 +05:30
|
|
|
import React, { Component } from 'react';
|
|
|
|
import { Row, Col } from '@freecodecamp/react-bootstrap';
|
|
|
|
import { connect } from 'react-redux';
|
|
|
|
import { bindActionCreators } from 'redux';
|
|
|
|
import PropTypes from 'prop-types';
|
|
|
|
import uniq from 'lodash/uniq';
|
|
|
|
import { createSelector } from 'reselect';
|
|
|
|
|
|
|
|
import SuperBlock from './components/SuperBlock';
|
|
|
|
import Spacer from '../helpers/Spacer';
|
|
|
|
|
|
|
|
import './map.css';
|
|
|
|
import { ChallengeNode } from '../../redux/propTypes';
|
|
|
|
import { toggleSuperBlock, toggleBlock, resetExpansion } from './redux';
|
|
|
|
import { currentChallengeIdSelector } from '../../redux';
|
2019-10-04 18:32:35 +03:00
|
|
|
import { dasherize } from '../../../../utils/slugs';
|
2019-09-21 19:09:48 +05:30
|
|
|
|
|
|
|
const propTypes = {
|
|
|
|
currentChallengeId: PropTypes.string,
|
2019-10-04 18:32:35 +03:00
|
|
|
hash: PropTypes.string,
|
2019-09-21 19:09:48 +05:30
|
|
|
introNodes: PropTypes.arrayOf(
|
|
|
|
PropTypes.shape({
|
|
|
|
fields: PropTypes.shape({ slug: PropTypes.string.isRequired }),
|
|
|
|
frontmatter: PropTypes.shape({
|
|
|
|
title: PropTypes.string.isRequired,
|
|
|
|
block: PropTypes.string.isRequired
|
|
|
|
})
|
|
|
|
})
|
|
|
|
),
|
2019-10-22 13:38:19 +03:00
|
|
|
isSignedIn: PropTypes.bool,
|
2019-09-21 19:09:48 +05:30
|
|
|
nodes: PropTypes.arrayOf(ChallengeNode),
|
|
|
|
resetExpansion: PropTypes.func,
|
|
|
|
toggleBlock: PropTypes.func.isRequired,
|
|
|
|
toggleSuperBlock: PropTypes.func.isRequired
|
|
|
|
};
|
|
|
|
|
|
|
|
const mapStateToProps = state => {
|
|
|
|
return createSelector(
|
|
|
|
currentChallengeIdSelector,
|
|
|
|
currentChallengeId => ({
|
|
|
|
currentChallengeId
|
|
|
|
})
|
|
|
|
)(state);
|
|
|
|
};
|
|
|
|
|
|
|
|
function mapDispatchToProps(dispatch) {
|
|
|
|
return bindActionCreators(
|
|
|
|
{
|
|
|
|
resetExpansion,
|
|
|
|
toggleSuperBlock,
|
|
|
|
toggleBlock
|
|
|
|
},
|
|
|
|
dispatch
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export class Map extends Component {
|
2019-10-04 18:32:35 +03:00
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
this.initializeExpandedState();
|
|
|
|
}
|
|
|
|
|
|
|
|
// As this happens in the constructor, it's necessary to manipulate state
|
|
|
|
// directly.
|
|
|
|
initializeExpandedState() {
|
|
|
|
const {
|
|
|
|
currentChallengeId,
|
|
|
|
hash,
|
|
|
|
nodes,
|
|
|
|
resetExpansion,
|
|
|
|
toggleBlock,
|
2019-10-22 13:38:19 +03:00
|
|
|
toggleSuperBlock,
|
|
|
|
isSignedIn
|
2019-10-04 18:32:35 +03:00
|
|
|
} = this.props;
|
|
|
|
resetExpansion();
|
2019-10-22 13:38:19 +03:00
|
|
|
|
2019-10-04 18:32:35 +03:00
|
|
|
let node;
|
|
|
|
|
|
|
|
// find the challenge that has the same superblock with hash
|
|
|
|
if (hash) {
|
|
|
|
node = nodes.find(node => dasherize(node.superBlock) === hash);
|
|
|
|
}
|
|
|
|
|
2020-02-08 13:29:10 -05:00
|
|
|
// without hash only expand when signed in
|
2019-10-22 13:38:19 +03:00
|
|
|
if (isSignedIn) {
|
|
|
|
// if there is no hash or the hash did not match any challenge superblock
|
|
|
|
// and there was a currentChallengeId
|
|
|
|
if (!node && currentChallengeId) {
|
|
|
|
node = nodes.find(node => node.id === currentChallengeId);
|
|
|
|
}
|
|
|
|
if (!node) node = nodes[0];
|
2019-10-04 18:32:35 +03:00
|
|
|
}
|
|
|
|
|
2019-10-22 13:38:19 +03:00
|
|
|
if (!node) return;
|
2019-10-04 18:32:35 +03:00
|
|
|
|
|
|
|
toggleBlock(node.block);
|
|
|
|
toggleSuperBlock(node.superBlock);
|
2019-09-21 19:09:48 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
renderSuperBlocks(superBlocks) {
|
|
|
|
const { nodes, introNodes } = this.props;
|
|
|
|
return superBlocks.map(superBlock => (
|
|
|
|
<SuperBlock
|
|
|
|
introNodes={introNodes}
|
|
|
|
key={superBlock}
|
|
|
|
nodes={nodes}
|
|
|
|
superBlock={superBlock}
|
|
|
|
/>
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
const { nodes } = this.props;
|
|
|
|
const superBlocks = uniq(nodes.map(({ superBlock }) => superBlock));
|
|
|
|
return (
|
|
|
|
<Row>
|
|
|
|
<Col sm={10} smOffset={1} xs={12}>
|
|
|
|
<div className='map-ui'>
|
|
|
|
<ul>
|
|
|
|
{this.renderSuperBlocks(superBlocks)}
|
|
|
|
<Spacer />
|
|
|
|
</ul>
|
|
|
|
</div>
|
|
|
|
</Col>
|
|
|
|
</Row>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Map.displayName = 'Map';
|
|
|
|
Map.propTypes = propTypes;
|
|
|
|
|
|
|
|
export default connect(
|
|
|
|
mapStateToProps,
|
|
|
|
mapDispatchToProps
|
|
|
|
)(Map);
|