Compare commits
1 Commits
bw/hackath
...
abutterwor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93a569f9ec |
@@ -9,6 +9,7 @@ import CourseStructureContext from './CourseStructureContext';
|
|||||||
import { useLoadCourseStructure, useMissingSubSectionRedirect } from './data/hooks';
|
import { useLoadCourseStructure, useMissingSubSectionRedirect } from './data/hooks';
|
||||||
import SubSection from './sub-section/SubSection';
|
import SubSection from './sub-section/SubSection';
|
||||||
import { history } from '@edx/frontend-platform';
|
import { history } from '@edx/frontend-platform';
|
||||||
|
import Sequence from './sequence/Sequence';
|
||||||
|
|
||||||
function LearningSequencePage({ match, intl }) {
|
function LearningSequencePage({ match, intl }) {
|
||||||
const {
|
const {
|
||||||
@@ -37,7 +38,163 @@ function LearningSequencePage({ match, intl }) {
|
|||||||
/>}
|
/>}
|
||||||
|
|
||||||
{loaded && unitId && <CourseBreadcrumbs />}
|
{loaded && unitId && <CourseBreadcrumbs />}
|
||||||
{subSectionId && <SubSection />}
|
{subSectionId && (
|
||||||
|
<Sequence
|
||||||
|
courseId={courseId}
|
||||||
|
id={subSectionId}
|
||||||
|
unitIds={[
|
||||||
|
"block-v1:edX+DemoX+Demo_Course+type@vertical+block@867dddb6f55d410caaa9c1eb9c6743ec",
|
||||||
|
"block-v1:edX+DemoX+Demo_Course+type@vertical+block@3e3f9b5199ba4e96b2fc6539087cfe2c",
|
||||||
|
"block-v1:edX+DemoX+Demo_Course+type@vertical+block@4f6c1b4e316a419ab5b6bf30e6c708e9",
|
||||||
|
"block-v1:edX+DemoX+Demo_Course+type@vertical+block@3dc16db8d14842e38324e95d4030b8a0",
|
||||||
|
"block-v1:edX+DemoX+Demo_Course+type@vertical+block@4a1bba2a403f40bca5ec245e945b0d76",
|
||||||
|
"block-v1:edX+DemoX+Demo_Course+type@vertical+block@f0e6d90842c44cc7a50fd1a18a7dd982",
|
||||||
|
"block-v1:edX+DemoX+Demo_Course+type@vertical+block@256f17a44983429fb1a60802203ee4e0",
|
||||||
|
"block-v1:edX+DemoX+Demo_Course+type@vertical+block@e3601c0abee6427d8c17e6d6f8fdddd1",
|
||||||
|
"block-v1:edX+DemoX+Demo_Course+type@vertical+block@a79d59cd72034188a71d388f4954a606",
|
||||||
|
"block-v1:edX+DemoX+Demo_Course+type@vertical+block@134df56c516a4a0dbb24dd5facef746e",
|
||||||
|
"block-v1:edX+DemoX+Demo_Course+type@vertical+block@d91b9e5d8bc64d57a1332d06bf2f2193",
|
||||||
|
]}
|
||||||
|
units={{
|
||||||
|
"block-v1:edX+DemoX+Demo_Course+type@vertical+block@867dddb6f55d410caaa9c1eb9c6743ec": {
|
||||||
|
"type": "other",
|
||||||
|
"path": "Example Week 1: Getting Started > Lesson 1 - Getting Started > Getting Started",
|
||||||
|
"bookmarked": false,
|
||||||
|
"graded": false,
|
||||||
|
"page_title": "Getting Started",
|
||||||
|
"href": "",
|
||||||
|
"complete": true,
|
||||||
|
"content": "",
|
||||||
|
"id": "block-v1:edX+DemoX+Demo_Course+type@vertical+block@867dddb6f55d410caaa9c1eb9c6743ec"
|
||||||
|
},
|
||||||
|
"block-v1:edX+DemoX+Demo_Course+type@vertical+block@3e3f9b5199ba4e96b2fc6539087cfe2c": {
|
||||||
|
"type": "other",
|
||||||
|
"path": "Example Week 1: Getting Started > Lesson 1 - Getting Started > MY unit",
|
||||||
|
"bookmarked": false,
|
||||||
|
"graded": false,
|
||||||
|
"page_title": "MY unit",
|
||||||
|
"href": "",
|
||||||
|
"complete": false,
|
||||||
|
"content": "",
|
||||||
|
"id": "block-v1:edX+DemoX+Demo_Course+type@vertical+block@3e3f9b5199ba4e96b2fc6539087cfe2c"
|
||||||
|
},
|
||||||
|
"block-v1:edX+DemoX+Demo_Course+type@vertical+block@4f6c1b4e316a419ab5b6bf30e6c708e9": {
|
||||||
|
"type": "video",
|
||||||
|
"path": "Example Week 1: Getting Started > Lesson 1 - Getting Started > Working with Videos",
|
||||||
|
"bookmarked": false,
|
||||||
|
"graded": false,
|
||||||
|
"page_title": "Working with Videos",
|
||||||
|
"href": "",
|
||||||
|
"complete": false,
|
||||||
|
"content": "",
|
||||||
|
"id": "block-v1:edX+DemoX+Demo_Course+type@vertical+block@4f6c1b4e316a419ab5b6bf30e6c708e9"
|
||||||
|
},
|
||||||
|
"block-v1:edX+DemoX+Demo_Course+type@vertical+block@3dc16db8d14842e38324e95d4030b8a0": {
|
||||||
|
"type": "video",
|
||||||
|
"path": "Example Week 1: Getting Started > Lesson 1 - Getting Started > Videos on edX",
|
||||||
|
"bookmarked": false,
|
||||||
|
"graded": false,
|
||||||
|
"page_title": "Videos on edX",
|
||||||
|
"href": "",
|
||||||
|
"complete": false,
|
||||||
|
"content": "",
|
||||||
|
"id": "block-v1:edX+DemoX+Demo_Course+type@vertical+block@3dc16db8d14842e38324e95d4030b8a0"
|
||||||
|
},
|
||||||
|
"block-v1:edX+DemoX+Demo_Course+type@vertical+block@4a1bba2a403f40bca5ec245e945b0d76": {
|
||||||
|
"type": "other",
|
||||||
|
"path": "Example Week 1: Getting Started > Lesson 1 - Getting Started > Video Demonstrations",
|
||||||
|
"bookmarked": false,
|
||||||
|
"graded": false,
|
||||||
|
"page_title": "Video Demonstrations",
|
||||||
|
"href": "",
|
||||||
|
"complete": false,
|
||||||
|
"content": "",
|
||||||
|
"id": "block-v1:edX+DemoX+Demo_Course+type@vertical+block@4a1bba2a403f40bca5ec245e945b0d76"
|
||||||
|
},
|
||||||
|
"block-v1:edX+DemoX+Demo_Course+type@vertical+block@f0e6d90842c44cc7a50fd1a18a7dd982": {
|
||||||
|
"type": "video",
|
||||||
|
"path": "Example Week 1: Getting Started > Lesson 1 - Getting Started > Video Demonstrations",
|
||||||
|
"bookmarked": false,
|
||||||
|
"graded": false,
|
||||||
|
"page_title": "Video Demonstrations",
|
||||||
|
"href": "",
|
||||||
|
"complete": false,
|
||||||
|
"content": "",
|
||||||
|
"id": "block-v1:edX+DemoX+Demo_Course+type@vertical+block@f0e6d90842c44cc7a50fd1a18a7dd982"
|
||||||
|
},
|
||||||
|
"block-v1:edX+DemoX+Demo_Course+type@vertical+block@256f17a44983429fb1a60802203ee4e0": {
|
||||||
|
"type": "video",
|
||||||
|
"path": "Example Week 1: Getting Started > Lesson 1 - Getting Started > Video Presentation Styles",
|
||||||
|
"bookmarked": false,
|
||||||
|
"graded": false,
|
||||||
|
"page_title": "Video Presentation Styles",
|
||||||
|
"href": "",
|
||||||
|
"complete": false,
|
||||||
|
"content": "",
|
||||||
|
"id": "block-v1:edX+DemoX+Demo_Course+type@vertical+block@256f17a44983429fb1a60802203ee4e0"
|
||||||
|
},
|
||||||
|
"block-v1:edX+DemoX+Demo_Course+type@vertical+block@e3601c0abee6427d8c17e6d6f8fdddd1": {
|
||||||
|
"type": "problem",
|
||||||
|
"path": "Example Week 1: Getting Started > Lesson 1 - Getting Started > Interactive Questions",
|
||||||
|
"bookmarked": false,
|
||||||
|
"graded": false,
|
||||||
|
"page_title": "Interactive Questions",
|
||||||
|
"href": "",
|
||||||
|
"complete": false,
|
||||||
|
"content": "",
|
||||||
|
"id": "block-v1:edX+DemoX+Demo_Course+type@vertical+block@e3601c0abee6427d8c17e6d6f8fdddd1"
|
||||||
|
},
|
||||||
|
"block-v1:edX+DemoX+Demo_Course+type@vertical+block@a79d59cd72034188a71d388f4954a606": {
|
||||||
|
"type": "other",
|
||||||
|
"path": "Example Week 1: Getting Started > Lesson 1 - Getting Started > Exciting Labs and Tools",
|
||||||
|
"bookmarked": false,
|
||||||
|
"graded": false,
|
||||||
|
"page_title": "Exciting Labs and Tools",
|
||||||
|
"href": "",
|
||||||
|
"complete": false,
|
||||||
|
"content": "",
|
||||||
|
"id": "block-v1:edX+DemoX+Demo_Course+type@vertical+block@a79d59cd72034188a71d388f4954a606"
|
||||||
|
},
|
||||||
|
"block-v1:edX+DemoX+Demo_Course+type@vertical+block@134df56c516a4a0dbb24dd5facef746e": {
|
||||||
|
"type": "problem",
|
||||||
|
"path": "Example Week 1: Getting Started > Lesson 1 - Getting Started > Reading Assignments",
|
||||||
|
"bookmarked": false,
|
||||||
|
"graded": false,
|
||||||
|
"page_title": "Reading Assignments",
|
||||||
|
"href": "",
|
||||||
|
"complete": false,
|
||||||
|
"content": "",
|
||||||
|
"id": "block-v1:edX+DemoX+Demo_Course+type@vertical+block@134df56c516a4a0dbb24dd5facef746e"
|
||||||
|
},
|
||||||
|
"block-v1:edX+DemoX+Demo_Course+type@vertical+block@d91b9e5d8bc64d57a1332d06bf2f2193": {
|
||||||
|
"type": "other",
|
||||||
|
"path": "Example Week 1: Getting Started > Lesson 1 - Getting Started > When Are Your Exams? ",
|
||||||
|
"bookmarked": false,
|
||||||
|
"graded": false,
|
||||||
|
"page_title": "When Are Your Exams? ",
|
||||||
|
"href": "",
|
||||||
|
"complete": false,
|
||||||
|
"content": "",
|
||||||
|
"id": "block-v1:edX+DemoX+Demo_Course+type@vertical+block@d91b9e5d8bc64d57a1332d06bf2f2193"
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
displayName="Sequence Name"
|
||||||
|
activeUnitId="block-v1:edX+DemoX+Demo_Course+type@vertical+block@867dddb6f55d410caaa9c1eb9c6743ec"
|
||||||
|
showCompletion={true}
|
||||||
|
isTimeLimited={false}
|
||||||
|
bannerText={null}
|
||||||
|
onNext={() => {}}
|
||||||
|
onPrevious={() => {}}
|
||||||
|
onNavigateUnit={() => {}}
|
||||||
|
isGated={false}
|
||||||
|
prerequisite={{
|
||||||
|
name: 'Prerequisite name',
|
||||||
|
url: 'url? or id',
|
||||||
|
id: 'asdasd',
|
||||||
|
}}
|
||||||
|
savePosition={true}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</CourseStructureContext.Provider>
|
</CourseStructureContext.Provider>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
7
src/learning-sequence/sequence/CompleteIcon.jsx
Normal file
7
src/learning-sequence/sequence/CompleteIcon.jsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import { faCheckCircle } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
|
export default function CompleteIcon(props) {
|
||||||
|
return <FontAwesomeIcon icon={faCheckCircle} {...props} />
|
||||||
|
}
|
||||||
139
src/learning-sequence/sequence/Sequence.jsx
Normal file
139
src/learning-sequence/sequence/Sequence.jsx
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import React, { useState, useEffect, Suspense } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import Unit from './Unit';
|
||||||
|
import SequenceNavigation from './SequenceNavigation';
|
||||||
|
import PageLoading from '../PageLoading';
|
||||||
|
import { getBlockCompletion, saveSequencePosition } from './api';
|
||||||
|
const ContentLock = React.lazy(() => import('./content-lock'));
|
||||||
|
import messages from './messages';
|
||||||
|
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
|
function Sequence({
|
||||||
|
courseId,
|
||||||
|
id,
|
||||||
|
unitIds,
|
||||||
|
units: initialUnits,
|
||||||
|
displayName,
|
||||||
|
showCompletion,
|
||||||
|
isTimeLimited,
|
||||||
|
bannerText,
|
||||||
|
onNext,
|
||||||
|
onPrevious,
|
||||||
|
onNavigateUnit,
|
||||||
|
isGated,
|
||||||
|
prerequisite,
|
||||||
|
savePosition,
|
||||||
|
activeUnitId: initialActiveUnitId,
|
||||||
|
intl,
|
||||||
|
}) {
|
||||||
|
const [units, setUnits] = useState(initialUnits);
|
||||||
|
const [activeUnitId, setActiveUnitId] = useState(initialActiveUnitId);
|
||||||
|
|
||||||
|
const activeUnitIndex = unitIds.indexOf(activeUnitId);
|
||||||
|
const activeUnit = units[activeUnitId];
|
||||||
|
const unitsArr = unitIds.map((unitId) => ({
|
||||||
|
...units[unitId],
|
||||||
|
id: unitId,
|
||||||
|
isActive: unitId === activeUnitId,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const updateUnitCompletion = (unitId) => {
|
||||||
|
// If the unit is already complete, don't check.
|
||||||
|
if (units[unitId].complete) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getBlockCompletion(courseId, id, unitId).then((isComplete) => {
|
||||||
|
if (isComplete) {
|
||||||
|
setUnits({
|
||||||
|
...units,
|
||||||
|
[unitId]: { ...units[unitId], complete: isComplete },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNext = () => {
|
||||||
|
if (activeUnitIndex < unitIds.length - 1) {
|
||||||
|
handleNavigate(activeUnitIndex + 1);
|
||||||
|
} else {
|
||||||
|
onNext();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePrevious = () => {
|
||||||
|
if (activeUnitIndex > 0) {
|
||||||
|
handleNavigate(activeUnitIndex - 1);
|
||||||
|
} else {
|
||||||
|
onPrevious();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNavigate = (unitIndex) => {
|
||||||
|
const newUnitId = unitIds[unitIndex];
|
||||||
|
if (showCompletion) {
|
||||||
|
updateUnitCompletion(activeUnitId);
|
||||||
|
}
|
||||||
|
setActiveUnitId(newUnitId);
|
||||||
|
onNavigateUnit(newUnitId, units[newUnitId]);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (savePosition) {
|
||||||
|
saveSequencePosition(courseId, id, activeUnitIndex);
|
||||||
|
}
|
||||||
|
}, [activeUnitId]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="d-flex flex-column flex-grow-1">
|
||||||
|
<SequenceNavigation
|
||||||
|
onNext={handleNext}
|
||||||
|
onNavigate={handleNavigate}
|
||||||
|
onPrevious={handlePrevious}
|
||||||
|
units={unitsArr}
|
||||||
|
isLocked={isGated}
|
||||||
|
showCompletion={showCompletion}
|
||||||
|
/>
|
||||||
|
{isGated? (
|
||||||
|
<Suspense fallback={<PageLoading
|
||||||
|
srMessage={intl.formatMessage(messages['learn.loading.content.lock'])}
|
||||||
|
/>}>
|
||||||
|
<ContentLock
|
||||||
|
courseId={courseId}
|
||||||
|
sectionName={displayName}
|
||||||
|
prereqSectionName={prerequisite.name}
|
||||||
|
prereqId={prerequisite.id}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
|
): (
|
||||||
|
<Unit key={activeUnitId} {...activeUnit} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Sequence.propTypes = {
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
courseId: PropTypes.string.isRequired,
|
||||||
|
unitIds: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||||
|
units: PropTypes.objectOf(PropTypes.shape({
|
||||||
|
})),
|
||||||
|
displayName: PropTypes.string.isRequired,
|
||||||
|
activeUnitId: PropTypes.string.isRequired,
|
||||||
|
showCompletion: PropTypes.bool.isRequired,
|
||||||
|
isTimeLimited: PropTypes.bool.isRequired,
|
||||||
|
bannerText: PropTypes.string,
|
||||||
|
onNext: PropTypes.func.isRequired,
|
||||||
|
onPrevious: PropTypes.func.isRequired,
|
||||||
|
onNavigateUnit: PropTypes.func.isRequired,
|
||||||
|
isGated: PropTypes.bool.isRequired,
|
||||||
|
prerequisite: PropTypes.shape({
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
url: PropTypes.string.isRequired,
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
}),
|
||||||
|
savePosition: PropTypes.bool.isRequired,
|
||||||
|
intl: intlShape.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default injectIntl(Sequence);
|
||||||
35
src/learning-sequence/sequence/SequenceNavigation.jsx
Normal file
35
src/learning-sequence/sequence/SequenceNavigation.jsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Button } from '@edx/paragon';
|
||||||
|
import UnitButton from './UnitButton';
|
||||||
|
|
||||||
|
export default function SequenceNavigation({
|
||||||
|
onNext,
|
||||||
|
onPrevious,
|
||||||
|
onNavigate,
|
||||||
|
units,
|
||||||
|
isLocked,
|
||||||
|
showCompletion,
|
||||||
|
}) {
|
||||||
|
const unitButtons = units.map((unit, index) => (
|
||||||
|
<UnitButton
|
||||||
|
key={unit.id}
|
||||||
|
{...unit}
|
||||||
|
isComplete={showCompletion && unit.complete}
|
||||||
|
onClick={onNavigate.bind(null, index)}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav className="flex-grow-0 d-flex w-100 mb-3 btn-group">
|
||||||
|
<Button className="btn-outline-primary" onClick={onPrevious}>
|
||||||
|
Previous
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{isLocked ? <UnitButton type="lock" isActive={true} /> : unitButtons}
|
||||||
|
|
||||||
|
<Button className="btn-outline-primary" onClick={onNext}>
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
22
src/learning-sequence/sequence/Unit.jsx
Normal file
22
src/learning-sequence/sequence/Unit.jsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import React, { useRef } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
|
||||||
|
export default function Unit({ id, title }) {
|
||||||
|
const iframeRef = useRef(null);
|
||||||
|
const iframeUrl = `${getConfig().LMS_BASE_URL}/xblock/${id}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
className="flex-grow-1"
|
||||||
|
title={title}
|
||||||
|
ref={iframeRef}
|
||||||
|
src={iframeUrl}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Unit.propTypes = {
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
29
src/learning-sequence/sequence/UnitButton.jsx
Normal file
29
src/learning-sequence/sequence/UnitButton.jsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { Button } from '@edx/paragon';
|
||||||
|
import UnitIcon from './UnitIcon';
|
||||||
|
import CompleteIcon from './CompleteIcon';
|
||||||
|
|
||||||
|
export default function UnitButton({
|
||||||
|
onClick,
|
||||||
|
title,
|
||||||
|
type,
|
||||||
|
isActive,
|
||||||
|
isComplete,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className={classNames({
|
||||||
|
active: isActive,
|
||||||
|
"btn-outline-primary": !isActive,
|
||||||
|
"btn-outline-secondary": isActive,
|
||||||
|
})}
|
||||||
|
disabled={isActive}
|
||||||
|
onClick={onClick}
|
||||||
|
title={title}
|
||||||
|
>
|
||||||
|
<UnitIcon type={type} />
|
||||||
|
{isComplete ? <CompleteIcon className="text-success ml-2" /> : null}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
35
src/learning-sequence/sequence/UnitIcon.jsx
Normal file
35
src/learning-sequence/sequence/UnitIcon.jsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import { faFilm, faBook, faPencilAlt, faTasks, faLock } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
|
export default function UnitIcon({ type }) {
|
||||||
|
let icon = null;
|
||||||
|
switch (type) {
|
||||||
|
case 'video':
|
||||||
|
icon = faFilm;
|
||||||
|
break;
|
||||||
|
case 'other':
|
||||||
|
icon = faBook;
|
||||||
|
break;
|
||||||
|
case 'vertical':
|
||||||
|
icon = faTasks;
|
||||||
|
break;
|
||||||
|
case 'problem':
|
||||||
|
icon = faPencilAlt;
|
||||||
|
break;
|
||||||
|
case 'lock':
|
||||||
|
icon = faLock;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
icon = faBook;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FontAwesomeIcon icon={icon} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
UnitIcon.propTypes = {
|
||||||
|
type: PropTypes.oneOf(['video', 'other', 'vertical', 'problem', 'lock']).isRequired,
|
||||||
|
};
|
||||||
50
src/learning-sequence/sequence/api.js
Normal file
50
src/learning-sequence/sequence/api.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||||
|
|
||||||
|
/* eslint-disable import/prefer-default-export */
|
||||||
|
|
||||||
|
const getSequenceXModuleHandlerUrl = (courseId, sequenceId) =>
|
||||||
|
`${getConfig().LMS_BASE_URL}/courses/${courseId}/xblock/${sequenceId}/handler/xmodule_handler`;
|
||||||
|
|
||||||
|
|
||||||
|
export async function saveSequencePosition(courseId, sequenceId, position) {
|
||||||
|
// Post data sent to this endpoint must be url encoded
|
||||||
|
// TODO: Remove the need for this to be the case.
|
||||||
|
// TODO: Ensure this usage of URLSearchParams is working in Internet Explorer
|
||||||
|
const urlEncoded = new URLSearchParams();
|
||||||
|
urlEncoded.append('position', position + 1);
|
||||||
|
const requestConfig = {
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = await getAuthenticatedHttpClient().post(
|
||||||
|
`${getSequenceXModuleHandlerUrl(courseId, sequenceId)}/goto_position`,
|
||||||
|
urlEncoded.toString(),
|
||||||
|
requestConfig,
|
||||||
|
);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getBlockCompletion(courseId, sequenceId, usageKey) {
|
||||||
|
// Post data sent to this endpoint must be url encoded
|
||||||
|
// TODO: Remove the need for this to be the case.
|
||||||
|
// TODO: Ensure this usage of URLSearchParams is working in Internet Explorer
|
||||||
|
const urlEncoded = new URLSearchParams();
|
||||||
|
urlEncoded.append('usage_key', usageKey);
|
||||||
|
const requestConfig = {
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = await getAuthenticatedHttpClient().post(
|
||||||
|
`${getSequenceXModuleHandlerUrl(courseId, sequenceId)}/get_completion`,
|
||||||
|
urlEncoded.toString(),
|
||||||
|
requestConfig,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (data.complete) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
37
src/learning-sequence/sequence/content-lock/ContentLock.jsx
Normal file
37
src/learning-sequence/sequence/content-lock/ContentLock.jsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import React, { useContext, useCallback } from 'react';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import { faLock } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
|
import { history } from '@edx/frontend-platform';
|
||||||
|
import { Button } from '@edx/paragon';
|
||||||
|
|
||||||
|
import messages from './messages';
|
||||||
|
|
||||||
|
function ContentLock({ intl, courseId, prereqSectionName, prereqId, sectionName }) {
|
||||||
|
const handleClick = useCallback(() => {
|
||||||
|
history.push(`/course/${courseId}/${prereqId}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h3>
|
||||||
|
<FontAwesomeIcon icon={faLock} />{' '}
|
||||||
|
{sectionName}
|
||||||
|
</h3>
|
||||||
|
<h4>{intl.formatMessage(messages['learn.contentLock.content.locked'])}</h4>
|
||||||
|
<p>{intl.formatMessage(messages['learn.contentLock.complete.prerequisite'], {
|
||||||
|
prereqSectionName,
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<Button className="btn-primary" onClick={handleClick}>{intl.formatMessage(messages['learn.contentLock.goToSection'])}</Button>
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentLock.propTypes = {
|
||||||
|
intl: intlShape.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default injectIntl(ContentLock);
|
||||||
1
src/learning-sequence/sequence/content-lock/index.js
Normal file
1
src/learning-sequence/sequence/content-lock/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './ContentLock';
|
||||||
21
src/learning-sequence/sequence/content-lock/messages.js
Normal file
21
src/learning-sequence/sequence/content-lock/messages.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
'learn.contentLock.content.locked': {
|
||||||
|
id: 'learn.contentLock.content.locked',
|
||||||
|
defaultMessage: 'Content Locked',
|
||||||
|
description: 'Message shown to indicate that a piece of content is unavailable and has a prerequisite.',
|
||||||
|
},
|
||||||
|
'learn.contentLock.complete.prerequisite': {
|
||||||
|
id: 'learn.contentLock.complete.prerequisite',
|
||||||
|
defaultMessage: "You must complete the prerequisite: '{prereqSectionName}' to access this content.",
|
||||||
|
description: 'Message shown to indicate which prerequisite the student must complete prior to accessing the locked content. {prereqSectionName} is the name of the prerequisite.',
|
||||||
|
},
|
||||||
|
'learn.contentLock.goToSection': {
|
||||||
|
id: 'learn.contentLock.goToSection',
|
||||||
|
defaultMessage: 'Go To Prerequisite Section',
|
||||||
|
description: 'A button users can click that navigates their browser to the prerequisite of this section.',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default messages;
|
||||||
11
src/learning-sequence/sequence/messages.js
Normal file
11
src/learning-sequence/sequence/messages.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
'learn.loading.content.lock': {
|
||||||
|
id: 'learn.loading.content.lock',
|
||||||
|
defaultMessage: 'Loading locked content messaging...',
|
||||||
|
description: 'Message shown when an interface about locked content is being loaded',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default messages;
|
||||||
Reference in New Issue
Block a user