* Moving model-store into “generic” sub-directory. Also adding a README.md to explain what belongs in “generic” * Moving user-messages into “generic” sub-directory. * Moving PageLoading into “generic” sub-directory. * Moving “tabs” module into “generic” sub-directory. * Moving InstructorToolbar and MasqueradeWidget up to instructor-toolbar. The masquerade widget is a sub-module of instructor-toolbar. * Co-locating celebration APIs with celebration utils. Also adding an ADR about thunk/API naming conventions and making some other areas of the code adhere to it. * Moving courseware data (thunks, api) into the courseware module. Note that cousre-home/data/api still uses normalizeBlocks - this should be fixed so it’s not reaching across. Maybe we pull that particular API up top. This PR includes a few TODOs for things I saw, as well as a tiny bit of whitespace cleanup.
100 lines
2.8 KiB
JavaScript
100 lines
2.8 KiB
JavaScript
import React, { useMemo } from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import { getConfig } from '@edx/frontend-platform';
|
|
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
import { faHome } from '@fortawesome/free-solid-svg-icons';
|
|
import { useSelector } from 'react-redux';
|
|
import { useModel } from '../../generic/model-store';
|
|
|
|
function CourseBreadcrumb({
|
|
url, children, withSeparator, ...attrs
|
|
}) {
|
|
return (
|
|
<>
|
|
{withSeparator && (
|
|
<li className="mx-2 text-gray-300" role="presentation" aria-hidden>/</li>
|
|
)}
|
|
<li {...attrs}>
|
|
<a href={url}>{children}</a>
|
|
</li>
|
|
</>
|
|
);
|
|
}
|
|
|
|
CourseBreadcrumb.propTypes = {
|
|
url: PropTypes.string.isRequired,
|
|
children: PropTypes.node.isRequired,
|
|
withSeparator: PropTypes.bool,
|
|
};
|
|
|
|
CourseBreadcrumb.defaultProps = {
|
|
withSeparator: false,
|
|
};
|
|
|
|
export default function CourseBreadcrumbs({
|
|
courseId,
|
|
sectionId,
|
|
sequenceId,
|
|
}) {
|
|
const course = useModel('courses', courseId);
|
|
const sequence = useModel('sequences', sequenceId);
|
|
const section = useModel('sections', sectionId);
|
|
const courseStatus = useSelector(state => state.courseware.courseStatus);
|
|
const sequenceStatus = useSelector(state => state.courseware.sequenceStatus);
|
|
|
|
const links = useMemo(() => {
|
|
if (courseStatus === 'loaded' && sequenceStatus === 'loaded') {
|
|
return [section, sequence].filter(node => !!node).map((node) => ({
|
|
id: node.id,
|
|
label: node.title,
|
|
url: `${getConfig().LMS_BASE_URL}/courses/${course.id}/course/#${node.id}`,
|
|
}));
|
|
}
|
|
return [];
|
|
}, [courseStatus, sequenceStatus]);
|
|
|
|
return (
|
|
<nav aria-label="breadcrumb" className="my-4">
|
|
<ol className="list-unstyled d-flex m-0">
|
|
<CourseBreadcrumb
|
|
url={`${getConfig().LMS_BASE_URL}/courses/${course.id}/course/`}
|
|
className="flex-shrink-0"
|
|
>
|
|
<FontAwesomeIcon icon={faHome} className="mr-2" />
|
|
<FormattedMessage
|
|
id="learn.breadcrumb.navigation.course.home"
|
|
description="The course home link in breadcrumbs nav"
|
|
defaultMessage="Course"
|
|
/>
|
|
</CourseBreadcrumb>
|
|
{links.map(({ id, url, label }) => (
|
|
<CourseBreadcrumb
|
|
key={id}
|
|
url={url}
|
|
withSeparator
|
|
style={{
|
|
overflow: 'hidden',
|
|
textOverflow: 'ellipsis',
|
|
whiteSpace: 'nowrap',
|
|
}}
|
|
>
|
|
{label}
|
|
</CourseBreadcrumb>
|
|
))}
|
|
</ol>
|
|
</nav>
|
|
);
|
|
}
|
|
|
|
CourseBreadcrumbs.propTypes = {
|
|
courseId: PropTypes.string.isRequired,
|
|
sectionId: PropTypes.string,
|
|
sequenceId: PropTypes.string,
|
|
};
|
|
|
|
CourseBreadcrumbs.defaultProps = {
|
|
sectionId: null,
|
|
sequenceId: null,
|
|
};
|