Route and breadcrumb cleanup.
This commit is contained in:
@@ -31,9 +31,7 @@ subscribe(APP_READY, () => {
|
||||
path="/"
|
||||
render={() => <Link to="/course/course-v1%3AedX%2BDemoX%2BDemo_Course/block-v1:edX+DemoX+Demo_Course+type@sequential+block@edx_introduction/block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_0270f6de40fc">Visit Demo Course</Link>}
|
||||
/>
|
||||
<Route path="/course/:courseUsageKey/:sequenceId/:unitId" component={LearningSequencePage} />
|
||||
<Route path="/course/:courseUsageKey/:sequenceId" component={LearningSequencePage} />
|
||||
<Route path="/course/:courseUsageKey" component={LearningSequencePage} />
|
||||
<Route path="/course" component={LearningSequencePage} />
|
||||
</Switch>
|
||||
<Footer />
|
||||
</AppProvider>,
|
||||
|
||||
@@ -48,9 +48,13 @@ function useLoadCourse(courseUsageKey) {
|
||||
}
|
||||
|
||||
|
||||
function CourseContainer({
|
||||
courseUsageKey, sequenceId, unitId, intl,
|
||||
}) {
|
||||
function CourseContainer(props) {
|
||||
const { intl, match } = props;
|
||||
const {
|
||||
courseUsageKey,
|
||||
sequenceId,
|
||||
unitId,
|
||||
} = match.params;
|
||||
const { models, courseId } = useLoadCourse(courseUsageKey);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -81,15 +85,14 @@ function CourseContainer({
|
||||
}
|
||||
|
||||
CourseContainer.propTypes = {
|
||||
courseUsageKey: PropTypes.string.isRequired,
|
||||
sequenceId: PropTypes.string,
|
||||
unitId: PropTypes.string,
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
CourseContainer.defaultProps = {
|
||||
sequenceId: null,
|
||||
unitId: null,
|
||||
match: PropTypes.shape({
|
||||
params: PropTypes.shape({
|
||||
courseUsageKey: PropTypes.string.isRequired,
|
||||
sequenceId: PropTypes.string,
|
||||
unitId: PropTypes.string,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(CourseContainer);
|
||||
|
||||
@@ -1,30 +1,19 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Switch, Route } from 'react-router';
|
||||
|
||||
import CourseContainer from './CourseContainer';
|
||||
|
||||
export default function LearningSequencePage({ match }) {
|
||||
const {
|
||||
courseUsageKey,
|
||||
sequenceId,
|
||||
unitId,
|
||||
} = match.params;
|
||||
|
||||
export default function LearningSequencePage() {
|
||||
// In spirit, we determine which sort of course we're rendering here based on the URL parameters.
|
||||
// Currently we only have one type, so this file doesn't have much to do.
|
||||
return (
|
||||
<CourseContainer
|
||||
courseUsageKey={courseUsageKey}
|
||||
sequenceId={sequenceId}
|
||||
unitId={unitId}
|
||||
<Route
|
||||
path={[
|
||||
'/course/:courseUsageKey/:sequenceId/:unitId',
|
||||
'/course/:courseUsageKey/:sequenceId',
|
||||
'/course/:courseUsageKey',
|
||||
]}
|
||||
component={CourseContainer}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
LearningSequencePage.propTypes = {
|
||||
match: PropTypes.shape({
|
||||
params: PropTypes.shape({
|
||||
courseUsageKey: PropTypes.string.isRequired,
|
||||
sequenceId: PropTypes.string,
|
||||
unitId: PropTypes.string,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useMemo, useCallback } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getConfig, history } from '@edx/frontend-platform';
|
||||
import { history } from '@edx/frontend-platform';
|
||||
|
||||
import CourseBreadcrumbs from './CourseBreadcrumbs';
|
||||
import SequenceContainer from './SequenceContainer';
|
||||
@@ -9,21 +9,6 @@ import { createSequenceIdList } from '../utils';
|
||||
export default function Course({
|
||||
courseUsageKey, courseId, sequenceId, unitId, models,
|
||||
}) {
|
||||
const breadcrumbs = useMemo(() => {
|
||||
const sectionId = models[sequenceId].parentId;
|
||||
if (!unitId) {
|
||||
return [];
|
||||
}
|
||||
return [courseId, sectionId, sequenceId, unitId].map((nodeId) => {
|
||||
const node = models[nodeId];
|
||||
return {
|
||||
id: node.id,
|
||||
label: node.displayName,
|
||||
url: `${getConfig().LMS_BASE_URL}/courses/${courseUsageKey}/course/#${node.id}`,
|
||||
};
|
||||
});
|
||||
}, [courseUsageKey, courseId, sequenceId, unitId, models]);
|
||||
|
||||
const nextSequenceHandler = useCallback(() => {
|
||||
const sequenceIds = createSequenceIdList(models, courseId);
|
||||
const currentIndex = sequenceIds.indexOf(sequenceId);
|
||||
@@ -48,7 +33,13 @@ export default function Course({
|
||||
|
||||
return (
|
||||
<main className="container-fluid d-flex flex-column flex-grow-1">
|
||||
<CourseBreadcrumbs links={breadcrumbs} />
|
||||
<CourseBreadcrumbs
|
||||
courseUsageKey={courseUsageKey}
|
||||
courseId={courseId}
|
||||
sequenceId={sequenceId}
|
||||
unitId={unitId}
|
||||
models={models}
|
||||
/>
|
||||
<SequenceContainer
|
||||
key={sequenceId}
|
||||
courseUsageKey={courseUsageKey}
|
||||
@@ -71,10 +62,11 @@ Course.propTypes = {
|
||||
models: PropTypes.objectOf(PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
displayName: PropTypes.string.isRequired,
|
||||
children: PropTypes.arrayOf(PropTypes.string),
|
||||
parentId: PropTypes.string,
|
||||
})).isRequired,
|
||||
};
|
||||
|
||||
Course.defaultProps = {
|
||||
sequenceId: null,
|
||||
unitId: null,
|
||||
};
|
||||
|
||||
25
src/learning-sequence/course/CourseBreadcrumb.jsx
Normal file
25
src/learning-sequence/course/CourseBreadcrumb.jsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faChevronRight } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
export default function CourseBreadcrumb({ url, label, last }) {
|
||||
return (
|
||||
<React.Fragment key={`${label}-${url}`}>
|
||||
<li className="list-inline-item">
|
||||
{last ? label : (<a href={url}>{label}</a>)}
|
||||
</li>
|
||||
{!last &&
|
||||
<li className="list-inline-item" role="presentation" aria-label="spacer">
|
||||
<FontAwesomeIcon icon={faChevronRight} />
|
||||
</li>
|
||||
}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
CourseBreadcrumb.propTypes = {
|
||||
url: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
last: PropTypes.bool.isRequired,
|
||||
};
|
||||
@@ -1,10 +1,27 @@
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faChevronRight } from '@fortawesome/free-solid-svg-icons';
|
||||
import CourseBreadcrumb from './CourseBreadcrumb';
|
||||
|
||||
export default function CourseBreadcrumbs({
|
||||
courseUsageKey, courseId, sequenceId, unitId, models,
|
||||
}) {
|
||||
const links = useMemo(() => {
|
||||
const sectionId = models[sequenceId].parentId;
|
||||
if (!unitId) {
|
||||
return [];
|
||||
}
|
||||
return [courseId, sectionId, sequenceId, unitId].map((nodeId) => {
|
||||
const node = models[nodeId];
|
||||
return {
|
||||
id: node.id,
|
||||
label: node.displayName,
|
||||
url: `${getConfig().LMS_BASE_URL}/courses/${courseUsageKey}/course/#${node.id}`,
|
||||
};
|
||||
});
|
||||
}, [courseUsageKey, courseId, sequenceId, unitId, models]);
|
||||
|
||||
export default function CourseBreadcrumbs({ links }) {
|
||||
return (
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol className="list-inline">
|
||||
@@ -17,30 +34,18 @@ export default function CourseBreadcrumbs({ links }) {
|
||||
}
|
||||
|
||||
CourseBreadcrumbs.propTypes = {
|
||||
links: PropTypes.arrayOf(PropTypes.shape({
|
||||
courseUsageKey: PropTypes.string.isRequired,
|
||||
courseId: PropTypes.string.isRequired,
|
||||
sequenceId: PropTypes.string.isRequired,
|
||||
unitId: PropTypes.string,
|
||||
models: PropTypes.objectOf(PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
url: PropTypes.string.isRequired,
|
||||
displayName: PropTypes.string.isRequired,
|
||||
children: PropTypes.arrayOf(PropTypes.string),
|
||||
parentId: PropTypes.string,
|
||||
})).isRequired,
|
||||
};
|
||||
|
||||
function CourseBreadcrumb({ url, label, last }) {
|
||||
return (
|
||||
<React.Fragment key={`${label}-${url}`}>
|
||||
<li className="list-inline-item">
|
||||
{last ? label : (<a href={url}>{label}</a>)}
|
||||
</li>
|
||||
{!last &&
|
||||
<li className="list-inline-item" role="presentation" aria-label="spacer">
|
||||
<FontAwesomeIcon icon={faChevronRight} />
|
||||
</li>
|
||||
}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
CourseBreadcrumb.propTypes = {
|
||||
url: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
last: PropTypes.bool.isRequired,
|
||||
CourseBreadcrumbs.defaultProps = {
|
||||
unitId: null,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user