diff --git a/src/index.jsx b/src/index.jsx index acf3755e..50acc91e 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -20,10 +20,12 @@ subscribe(APP_READY, () => {
+ {/* Staging: course-v1:UBCx+Water201x_2+2T2015 */} Visit Demo Course} + + render={() => Visit Demo Course} /> diff --git a/src/learning-sequence/Breadcrumbs.jsx b/src/learning-sequence/Breadcrumbs.jsx deleted file mode 100644 index 6fda8269..00000000 --- a/src/learning-sequence/Breadcrumbs.jsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faChevronRight } from '@fortawesome/free-solid-svg-icons'; - -const Breadcrumbs = ({ - links, activeLabel, spacer, clickHandler, -}) => { - const linkCount = links.length; - - return ( - - ); -}; - -Breadcrumbs.propTypes = { - /** an array of objects with the properties `label` and `url` as strings. */ - links: PropTypes.arrayOf(PropTypes.shape({ - label: PropTypes.string, - url: PropTypes.string, - })).isRequired, - /** allows to add a label that is not a link to the end of the breadcrumb. - * Defaults to `undefined`. - */ - activeLabel: PropTypes.string, - /** allows to add a custom element between the breadcrumb items. - * Defaults to `>` rendered using the `Icon` component. */ - spacer: PropTypes.element, - /** allows to add a custom function to be called `onClick` of a breadcrumb link. - * The use case for this is for adding custom analytics to the component. */ - clickHandler: PropTypes.func, -}; - -Breadcrumbs.defaultProps = { - activeLabel: undefined, - spacer: undefined, - clickHandler: undefined, -}; - -export default Breadcrumbs; diff --git a/src/learning-sequence/CourseBreadcrumbs.jsx b/src/learning-sequence/CourseBreadcrumbs.jsx new file mode 100644 index 00000000..68acd1df --- /dev/null +++ b/src/learning-sequence/CourseBreadcrumbs.jsx @@ -0,0 +1,54 @@ +import React, { useContext } from 'react'; +import PropTypes from 'prop-types'; + +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faChevronRight } from '@fortawesome/free-solid-svg-icons'; +import { getConfig } from '@edx/frontend-platform'; + +import CourseStructureContext from './CourseStructureContext'; +import { useBlockAncestry } from './hooks'; + +const CourseBreadcrumbs = () => { + const { courseId, unitId } = useContext(CourseStructureContext); + + const ancestry = useBlockAncestry(unitId); + + const links = ancestry.map(ancestor => ({ + id: ancestor.id, + label: ancestor.displayName, + url: `${getConfig().LMS_BASE_URL}/courses/${courseId}/course/#${ancestor.id}`, + })); + + return ( + + ); +}; + +export default CourseBreadcrumbs; + +function CourseBreadcrumb({ url, label, last }) { + return ( + +
  • + {last ? label : ({label})} +
  • + {!last && +
  • + +
  • + } +
    + ); +} + +CourseBreadcrumb.propTypes = { + url: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + last: PropTypes.bool.isRequired, +}; diff --git a/src/learning-sequence/CourseStructureContext.jsx b/src/learning-sequence/CourseStructureContext.jsx new file mode 100644 index 00000000..0f3f573d --- /dev/null +++ b/src/learning-sequence/CourseStructureContext.jsx @@ -0,0 +1,5 @@ +import React from 'react'; + +const CourseStructureContext = React.createContext({}); + +export default CourseStructureContext; diff --git a/src/learning-sequence/LearningSequencePage.jsx b/src/learning-sequence/LearningSequencePage.jsx index 68808912..e99b03ef 100644 --- a/src/learning-sequence/LearningSequencePage.jsx +++ b/src/learning-sequence/LearningSequencePage.jsx @@ -1,170 +1,49 @@ -/* eslint-disable no-plusplus */ -import React, { Component } from 'react'; +import React, { useState, useEffect, useContext } from 'react'; import PropTypes from 'prop-types'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { getConfig, history } from '@edx/frontend-platform'; -import { AppContext } from '@edx/frontend-platform/react'; import PageLoading from './PageLoading'; import messages from './messages'; -import SubSectionNavigation from './SubSectionNavigation'; -import { loadCourseSequence, findBlockAncestry, loadSubSectionMetadata } from './api'; -import Breadcrumbs from './Breadcrumbs'; -class LearningSequencePage extends Component { - constructor(props, context) { - super(props, context); +import CourseBreadcrumbs from './CourseBreadcrumbs'; +import SubSection from './SubSection'; - this.state = { - loading: true, - blocks: {}, - units: {}, - subSectionMetadata: null, - subSectionId: null, - subSectionIds: [], - unitId: null, - courseBlockId: null, - }; +import { useCourseStructure } from './hooks'; +import CourseStructureContext from './CourseStructureContext'; - this.iframeRef = React.createRef(); - } +function LearningSequencePage({ match, intl }) { + const { + courseId, + subSectionId, + unitId, + } = match.params; - componentDidMount() { - loadCourseSequence(this.props.match.params.courseId, this.props.match.params.subSectionId, this.props.match.params.unitId, this.context.authenticatedUser.username) - .then(({ - blocks, courseBlockId, subSectionIds, subSectionMetadata, units, unitId, - }) => { - this.setState({ - loading: false, - blocks, - units, - subSectionMetadata, - subSectionId: subSectionMetadata.itemId, - subSectionIds, - unitId, - courseBlockId, // TODO: Currently unused, but may be necessary. - }); - }); - } + const { blocks, loaded, courseBlockId } = useCourseStructure(courseId); - componentDidUpdate(prevProps, prevState) { - if ( - this.props.match.params.courseId !== prevProps.match.params.courseId || - this.state.subSectionId !== prevState.subSectionId || - this.state.unitId !== prevState.unitId - ) { - history.push(`/course/${this.props.match.params.courseId}/${this.state.subSectionId}/${this.state.unitId}`); - } - } - - handlePreviousClick = () => { - const index = this.state.subSectionMetadata.unitIds.indexOf(this.state.unitId); - if (index > 0) { - this.setState({ - unitId: this.state.subSectionMetadata.unitIds[index - 1], - }); - } else { - const subSectionIndex = this.state.subSectionIds.indexOf(this.state.subSectionId); - if (subSectionIndex > 0) { - const previousSubSectionId = this.state.subSectionIds[subSectionIndex - 1]; - - loadSubSectionMetadata(this.props.match.params.courseId, previousSubSectionId, { last: true }).then(({ subSectionMetadata, units, unitId }) => { - this.setState({ - subSectionId: subSectionMetadata.itemId, - subSectionMetadata, - units, + return ( +
    +
    + + {!loaded && } - handleNextClick = () => { - const index = this.state.subSectionMetadata.unitIds.indexOf(this.state.unitId); - if (index < this.state.subSectionMetadata.unitIds.length - 1) { - this.setState({ - unitId: this.state.subSectionMetadata.unitIds[index + 1], - }); - } else { - const subSectionIndex = this.state.subSectionIds.indexOf(this.state.subSectionId); - if (subSectionIndex < this.state.subSectionIds.length - 1) { - const nextSubSectionId = this.state.subSectionIds[subSectionIndex + 1]; - - loadSubSectionMetadata(this.props.match.params.courseId, nextSubSectionId, { first: true }) - .then(({ subSectionMetadata, units, unitId }) => { - this.setState({ - subSectionId: subSectionMetadata.itemId, - subSectionMetadata, - units, - unitId, - }); - }); - } else { - console.log('we are at the end!'); - } - } - } - - handleUnitChange = (unitId) => { - this.setState({ - unitId, - }); - } - - render() { - if (this.state.loading) { - return ( - - ); - } - - const [course, chapter, subSection] = findBlockAncestry( - this.state.blocks, - this.state.blocks[this.state.subSectionId], - ); - - const currentUnit = this.state.units[this.state.unitId]; - const iframeUrl = `${getConfig().LMS_BASE_URL}/xblock/${this.state.unitId}`; - - return ( -
    -
    -

    {course.displayName}

    - - -
    -