item.id)}
+ units={units}
+ displayName={displayName}
+ activeUnitId={unitId}
+ showCompletion={showCompletion}
+ isTimeLimited={isTimeLimited}
+ isGated={gatedContent.gated}
+ savePosition={savePosition}
+ bannerText={bannerText}
+ onNext={onNext}
+ onPrevious={onPrevious}
+ onNavigateUnit={() => console.log('hah2')}
+ prerequisite={prerequisite}
+ />
+ );
+}
+
+SequenceContainer.propTypes = {
+ courseId: PropTypes.string.isRequired,
+ sequenceId: PropTypes.string.isRequired,
+ unitId: PropTypes.string.isRequired,
+ intl: intlShape.isRequired,
+};
+
+export default injectIntl(SequenceContainer);
diff --git a/src/learning-sequence/sub-section/data/api.js b/src/learning-sequence/course/api.js
similarity index 60%
rename from src/learning-sequence/sub-section/data/api.js
rename to src/learning-sequence/course/api.js
index 5ca1cbe7..ac1f6301 100644
--- a/src/learning-sequence/sub-section/data/api.js
+++ b/src/learning-sequence/course/api.js
@@ -3,17 +3,17 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
/* eslint-disable import/prefer-default-export */
-const getSubSectionXModuleHandlerUrl = (courseId, subSectionId) =>
- `${getConfig().LMS_BASE_URL}/courses/${courseId}/xblock/${subSectionId}/handler/xmodule_handler`;
+const getSequenceXModuleHandlerUrl = (courseUsageKey, sequenceId) =>
+ `${getConfig().LMS_BASE_URL}/courses/${courseUsageKey}/xblock/${sequenceId}/handler/xmodule_handler`;
-export async function getSubSectionMetadata(courseId, subSectionId) {
+export async function getSequenceMetadata(courseUsageKey, sequenceId) {
const { data } = await getAuthenticatedHttpClient()
- .get(`${getSubSectionXModuleHandlerUrl(courseId, subSectionId)}/metadata`, {});
+ .get(`${getSequenceXModuleHandlerUrl(courseUsageKey, sequenceId)}/metadata`, {});
return data;
}
-export async function saveSubSectionPosition(courseId, subSectionId, position) {
+export async function saveSequencePosition(courseUsageKey, 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
@@ -24,7 +24,7 @@ export async function saveSubSectionPosition(courseId, subSectionId, position) {
};
const { data } = await getAuthenticatedHttpClient().post(
- `${getSubSectionXModuleHandlerUrl(courseId, subSectionId)}/goto_position`,
+ `${getSequenceXModuleHandlerUrl(courseUsageKey, sequenceId)}/goto_position`,
urlEncoded.toString(),
requestConfig,
);
diff --git a/src/learning-sequence/sub-section/data/hooks.js b/src/learning-sequence/course/hooks.js
similarity index 58%
rename from src/learning-sequence/sub-section/data/hooks.js
rename to src/learning-sequence/course/hooks.js
index b1e2e2c0..0f2df1b2 100644
--- a/src/learning-sequence/sub-section/data/hooks.js
+++ b/src/learning-sequence/course/hooks.js
@@ -1,24 +1,34 @@
+/* eslint-disable no-plusplus */
import { useState, useEffect, useContext } from 'react';
import { camelCaseObject, history } from '@edx/frontend-platform';
-import { getSubSectionMetadata, saveSubSectionPosition } from './api';
-import CourseStructureContext from '../../CourseStructureContext';
+import { getSequenceMetadata, saveSequencePosition } from './api';
+import CourseStructureContext from '../CourseStructureContext';
-export function useLoadSubSectionMetadata(courseId, subSectionId) {
+export function useLoadSequenceMetadata(courseUsageKey, sequenceId) {
const [metadata, setMetadata] = useState(null);
+ const [units, setUnits] = useState(null);
const [loaded, setLoaded] = useState(false);
useEffect(() => {
setLoaded(false);
setMetadata(null);
- getSubSectionMetadata(courseId, subSectionId).then((data) => {
+ getSequenceMetadata(courseUsageKey, sequenceId).then((data) => {
+ const unitsMap = {};
+ for (let i = 0; i < data.items.length; i++) {
+ const item = data.items[i];
+ unitsMap[item.id] = camelCaseObject(item);
+ }
+
setMetadata(camelCaseObject(data));
+ setUnits(unitsMap);
setLoaded(true);
});
- }, [courseId, subSectionId]);
+ }, [courseUsageKey, sequenceId]);
return {
metadata,
+ units,
loaded,
};
}
@@ -36,15 +46,15 @@ export function useExamRedirect(metadata, blocks) {
/**
* Save the position of current unit the subsection
*/
-export function usePersistentUnitPosition(courseId, subSectionId, unitId, subSectionMetadata) {
+export function usePersistentUnitPosition(courseUsageKey, sequenceId, unitId, sequenceMetadata) {
useEffect(() => {
// All values must be defined to function
- const hasNeededData = courseId && subSectionId && unitId && subSectionMetadata;
+ const hasNeededData = courseUsageKey && sequenceId && unitId && sequenceMetadata;
if (!hasNeededData) {
return;
}
- const { items, savePosition } = subSectionMetadata;
+ const { items, savePosition } = sequenceMetadata;
// A sub-section can individually specify whether positions should be saved
if (!savePosition) {
@@ -58,18 +68,18 @@ export function usePersistentUnitPosition(courseId, subSectionId, unitId, subSec
// TODO: update the local understanding of the position and
// don't make requests to update the position if they still match?
- saveSubSectionPosition(courseId, subSectionId, newPosition);
- }, [courseId, subSectionId, unitId, subSectionMetadata]);
+ saveSequencePosition(courseUsageKey, sequenceId, newPosition);
+ }, [courseUsageKey, sequenceId, unitId, sequenceMetadata]);
}
export function useMissingUnitRedirect(metadata, loaded) {
- const { courseId, subSectionId, unitId } = useContext(CourseStructureContext);
+ const { courseUsageKey, sequenceId, unitId } = useContext(CourseStructureContext);
useEffect(() => {
- if (loaded && metadata.itemId === subSectionId && !unitId) {
+ if (loaded && metadata.itemId === sequenceId && !unitId) {
// Position comes from the server as a 1-indexed array index. Convert it to 0-indexed.
const position = metadata.position - 1;
const nextUnitId = metadata.items[position].id;
- history.push(`/course/${courseId}/${subSectionId}/${nextUnitId}`);
+ history.push(`/course/${courseUsageKey}/${sequenceId}/${nextUnitId}`);
}
}, [loaded, metadata, unitId]);
}
diff --git a/src/learning-sequence/data/api.js b/src/learning-sequence/data/api.js
index ef989478..ee567a4c 100644
--- a/src/learning-sequence/data/api.js
+++ b/src/learning-sequence/data/api.js
@@ -13,3 +13,10 @@ export async function getCourseBlocks(courseId, username) {
return data;
}
+
+export async function getCourse(courseId) {
+ const url = `${getConfig().LMS_BASE_URL}/api/courses/v2/courses/${courseId}`;
+ const { data } = await getAuthenticatedHttpClient().get(url);
+
+ return data;
+}
diff --git a/src/learning-sequence/data/hooks.js b/src/learning-sequence/data/hooks.js
index fc9d2be0..9316a49a 100644
--- a/src/learning-sequence/data/hooks.js
+++ b/src/learning-sequence/data/hooks.js
@@ -4,7 +4,7 @@ import { AppContext } from '@edx/frontend-platform/react';
import CourseStructureContext from '../CourseStructureContext';
import { getCourseBlocks } from './api';
-import { findBlockAncestry, createBlocksMap, createSubSectionIdList, createUnitIdList } from './utils';
+import { findBlockAncestry, createBlocksMap, createSequenceIdList, createUnitIdList } from './utils';
export function useBlockAncestry(blockId) {
const { blocks, loaded } = useContext(CourseStructureContext);
@@ -19,63 +19,66 @@ export function useBlockAncestry(blockId) {
}, [blocks, blockId, loaded]);
}
-export function useMissingSubSectionRedirect(
+export function useMissingSequenceRedirect(
loaded,
blocks,
+ courseUsageKey,
courseId,
- courseBlockId,
- subSectionId,
+ sequenceId,
) {
useEffect(() => {
- if (loaded && !subSectionId) {
- const course = blocks[courseBlockId];
+ if (loaded && !sequenceId) {
+ const course = blocks[courseId];
const nextSectionId = course.children[0];
const nextSection = blocks[nextSectionId];
- const nextSubSectionId = nextSection.children[0];
- const nextSubSection = blocks[nextSubSectionId];
- const nextUnitId = nextSubSection.children[0];
- history.push(`/course/${courseId}/${nextSubSectionId}/${nextUnitId}`);
+ const nextSequenceId = nextSection.children[0];
+ const nextSequence = blocks[nextSequenceId];
+ const nextUnitId = nextSequence.children[0];
+ history.push(`/course/${courseUsageKey}/${nextSequenceId}/${nextUnitId}`);
}
- }, [loaded, subSectionId]);
+ }, [loaded, sequenceId]);
}
-export function useLoadCourseStructure(courseId) {
+export function useLoadCourseStructure(courseUsageKey) {
const { authenticatedUser } = useContext(AppContext);
const [blocks, setBlocks] = useState(null);
const [loaded, setLoaded] = useState(false);
- const [courseBlockId, setCourseBlockId] = useState();
+ const [courseId, setCourseId] = useState();
useEffect(() => {
setLoaded(false);
- getCourseBlocks(courseId, authenticatedUser.username).then((blocksData) => {
+ getCourseBlocks(courseUsageKey, authenticatedUser.username).then((blocksData) => {
setBlocks(createBlocksMap(blocksData.blocks));
- setCourseBlockId(blocksData.root);
+ setCourseId(blocksData.root);
setLoaded(true);
});
- }, [courseId]);
+ // getCourse(courseUsageKey).then((courseData) => {
+
+ // });
+ }, [courseUsageKey]);
return {
- blocks, loaded, courseBlockId,
+ blocks, loaded, courseId,
};
}
export function useCurrentCourse() {
- const { loaded, courseBlockId, blocks } = useContext(CourseStructureContext);
+ const { loaded, courseId, blocks } = useContext(CourseStructureContext);
- return loaded ? blocks[courseBlockId] : null;
+ return loaded ? blocks[courseId] : null;
}
-export function useCurrentSubSection() {
- const { loaded, blocks, subSectionId } = useContext(CourseStructureContext);
+export function useCurrentSequence() {
+ const { loaded, blocks, sequenceId } = useContext(CourseStructureContext);
- return loaded && subSectionId ? blocks[subSectionId] : null;
+ return loaded && sequenceId ? blocks[sequenceId] : null;
}
export function useCurrentSection() {
const { loaded, blocks } = useContext(CourseStructureContext);
- const subSection = useCurrentSubSection();
- return loaded ? blocks[subSection.parentId] : null;
+ const sequence = useCurrentSequence();
+ return loaded ? blocks[sequence.parentId] : null;
}
export function useCurrentUnit() {
@@ -86,11 +89,11 @@ export function useCurrentUnit() {
export function useUnitIds() {
- const { loaded, blocks, courseBlockId } = useContext(CourseStructureContext);
+ const { loaded, blocks, courseId } = useContext(CourseStructureContext);
return useMemo(
- () => (loaded ? createUnitIdList(blocks, courseBlockId) : []),
- [loaded, blocks, courseBlockId],
+ () => (loaded ? createUnitIdList(blocks, courseId) : []),
+ [loaded, blocks, courseId],
);
}
@@ -117,20 +120,20 @@ export function useNextUnit() {
return loaded ? blocks[unitIds[currentUnitIndex + 1]] : null;
}
-export function useCurrentSubSectionUnits() {
+export function useCurrentSequenceUnits() {
const { loaded, blocks } = useContext(CourseStructureContext);
- const subSection = useCurrentSubSection();
+ const sequence = useCurrentSequence();
- return loaded ? subSection.children.map(id => blocks[id]) : [];
+ return loaded ? sequence.children.map(id => blocks[id]) : [];
}
-export function useSubSectionIdList() {
- const { loaded, blocks, courseBlockId } = useContext(CourseStructureContext);
+export function useSequenceIdList() {
+ const { loaded, blocks, courseId } = useContext(CourseStructureContext);
- const subSectionIdList = useMemo(
- () => (loaded ? createSubSectionIdList(blocks, courseBlockId) : []),
- [blocks, courseBlockId],
+ const sequenceIdList = useMemo(
+ () => (loaded ? createSequenceIdList(blocks, courseId) : []),
+ [blocks, courseId],
);
- return subSectionIdList;
+ return sequenceIdList;
}
diff --git a/src/learning-sequence/data/utils.js b/src/learning-sequence/data/utils.js
index 83eacf27..be976519 100644
--- a/src/learning-sequence/data/utils.js
+++ b/src/learning-sequence/data/utils.js
@@ -29,18 +29,18 @@ export function createBlocksMap(blocksData) {
return blocks;
}
-export function createSubSectionIdList(blocks, entryPointId, subSections = []) {
+export function createSequenceIdList(blocks, entryPointId, sequences = []) {
const block = blocks[entryPointId];
if (block.type === 'sequential') {
- subSections.push(block.id);
+ sequences.push(block.id);
}
if (Array.isArray(block.children)) {
for (let i = 0; i < block.children.length; i++) {
const childId = block.children[i];
- createSubSectionIdList(blocks, childId, subSections);
+ createSequenceIdList(blocks, childId, sequences);
}
}
- return subSections;
+ return sequences;
}
export function createUnitIdList(blocks, entryPointId, units = []) {
diff --git a/src/learning-sequence/sequence/CompleteIcon.jsx b/src/learning-sequence/sequence/CompleteIcon.jsx
new file mode 100644
index 00000000..150779d4
--- /dev/null
+++ b/src/learning-sequence/sequence/CompleteIcon.jsx
@@ -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 ;
+}
diff --git a/src/learning-sequence/sequence/Sequence.jsx b/src/learning-sequence/sequence/Sequence.jsx
new file mode 100644
index 00000000..bccfdfa5
--- /dev/null
+++ b/src/learning-sequence/sequence/Sequence.jsx
@@ -0,0 +1,173 @@
+/* eslint-disable no-use-before-define */
+import React, { useState, useEffect, Suspense } from 'react';
+import PropTypes from 'prop-types';
+import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
+
+import Unit from './Unit';
+import SequenceNavigation from './SequenceNavigation';
+import PageLoading from '../PageLoading';
+import { getBlockCompletion, saveSequencePosition } from './api';
+import messages from './messages';
+
+const ContentLock = React.lazy(() => import('./content-lock'));
+
+function Sequence({
+ courseUsageKey,
+ 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,
+ }));
+
+ // TODO: Use callback
+ const updateUnitCompletion = (unitId) => {
+ // If the unit is already complete, don't check.
+ if (units[unitId].complete) {
+ return;
+ }
+
+ getBlockCompletion(courseUsageKey, 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(courseUsageKey, id, activeUnitIndex);
+ }
+ }, [activeUnitId]);
+
+ return (
+
+
+ {isGated ? (
+ }
+ >
+
+
+ ) : (
+
+ )}
+
+ );
+}
+
+Sequence.propTypes = {
+ id: PropTypes.string.isRequired,
+ courseUsageKey: PropTypes.string.isRequired,
+ unitIds: PropTypes.arrayOf(PropTypes.string).isRequired,
+ units: PropTypes.objectOf(PropTypes.shape({
+ id: PropTypes.string.isRequired,
+ complete: PropTypes.bool,
+ pageTitle: PropTypes.string.isRequired,
+ })).isRequired,
+ 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,
+ id: PropTypes.string,
+ }).isRequired,
+ savePosition: PropTypes.bool.isRequired,
+ intl: intlShape.isRequired,
+};
+
+Sequence.defaultProps = {
+ bannerText: null,
+};
+
+export default injectIntl(Sequence);
+
+// Sequence.propTypes = {
+// id: PropTypes.string.isRequired,
+// courseUsageKey: Pro
+// 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,
+// isGated: PropTypes.bool.isRequired,
+// savePosition: PropTypes.bool.isRequired,
+// bannerText: PropTypes.string,
+// onNext: PropTypes.func.isRequired,
+// onPrevious: PropTypes.func.isRequired,
+// onNavigateUnit: PropTypes.func.isRequired,
+// prerequisite: PropTypes.shape({
+// name: PropTypes.string,
+// id: PropTypes.string,
+// }),
+// };
diff --git a/src/learning-sequence/sequence/SequenceNavigation.jsx b/src/learning-sequence/sequence/SequenceNavigation.jsx
new file mode 100644
index 00000000..f3174223
--- /dev/null
+++ b/src/learning-sequence/sequence/SequenceNavigation.jsx
@@ -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) => (
+
+ ));
+
+ return (
+
+ );
+}
diff --git a/src/learning-sequence/sequence/Unit.jsx b/src/learning-sequence/sequence/Unit.jsx
new file mode 100644
index 00000000..53b34118
--- /dev/null
+++ b/src/learning-sequence/sequence/Unit.jsx
@@ -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, pageTitle }) {
+ const iframeRef = useRef(null);
+ const iframeUrl = `${getConfig().LMS_BASE_URL}/xblock/${id}`;
+
+ return (
+
+ );
+}
+
+Unit.propTypes = {
+ id: PropTypes.string.isRequired,
+ pageTitle: PropTypes.string.isRequired,
+};
diff --git a/src/learning-sequence/sequence/UnitButton.jsx b/src/learning-sequence/sequence/UnitButton.jsx
new file mode 100644
index 00000000..29cf2f2f
--- /dev/null
+++ b/src/learning-sequence/sequence/UnitButton.jsx
@@ -0,0 +1,30 @@
+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,
+ pageTitle,
+ type,
+ isActive,
+ isComplete,
+}) {
+ return (
+
+ );
+}
diff --git a/src/learning-sequence/sequence/UnitIcon.jsx b/src/learning-sequence/sequence/UnitIcon.jsx
new file mode 100644
index 00000000..5568e6ff
--- /dev/null
+++ b/src/learning-sequence/sequence/UnitIcon.jsx
@@ -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 (
+
+ );
+}
+
+UnitIcon.propTypes = {
+ type: PropTypes.oneOf(['video', 'other', 'vertical', 'problem', 'lock']).isRequired,
+};
diff --git a/src/learning-sequence/sequence/api.js b/src/learning-sequence/sequence/api.js
new file mode 100644
index 00000000..499403eb
--- /dev/null
+++ b/src/learning-sequence/sequence/api.js
@@ -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 = (courseUsageKey, sequenceId) =>
+ `${getConfig().LMS_BASE_URL}/courses/${courseUsageKey}/xblock/${sequenceId}/handler/xmodule_handler`;
+
+
+export async function saveSequencePosition(courseUsageKey, 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(courseUsageKey, sequenceId)}/goto_position`,
+ urlEncoded.toString(),
+ requestConfig,
+ );
+
+ return data;
+}
+
+export async function getBlockCompletion(courseUsageKey, 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(courseUsageKey, sequenceId)}/get_completion`,
+ urlEncoded.toString(),
+ requestConfig,
+ );
+
+ if (data.complete) {
+ return true;
+ }
+
+ return false;
+}
diff --git a/src/learning-sequence/sub-section/content-lock/ContentLock.jsx b/src/learning-sequence/sequence/content-lock/ContentLock.jsx
similarity index 59%
rename from src/learning-sequence/sub-section/content-lock/ContentLock.jsx
rename to src/learning-sequence/sequence/content-lock/ContentLock.jsx
index 622a49e7..aa09fcbd 100644
--- a/src/learning-sequence/sub-section/content-lock/ContentLock.jsx
+++ b/src/learning-sequence/sequence/content-lock/ContentLock.jsx
@@ -1,33 +1,28 @@
-import React, { useContext, useCallback } from 'react';
+import React, { 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 SubSectionMetadataContext from '../SubSectionMetadataContext';
import messages from './messages';
-import { useCurrentSubSection } from '../../data/hooks';
-import CourseStructureContext from '../../CourseStructureContext';
-
-function ContentLock({ intl }) {
- const { courseId } = useContext(CourseStructureContext);
- const metadata = useContext(SubSectionMetadataContext);
- const subSection = useCurrentSubSection();
+function ContentLock({
+ intl, courseUsageKey, prereqSectionName, prereqId, sectionName,
+}) {
const handleClick = useCallback(() => {
- history.push(`/course/${courseId}/${metadata.gatedContent.prereqId}`);
+ history.push(`/course/${courseUsageKey}/${prereqId}`);
});
return (
<>
{' '}
- {subSection.displayName}
+ {sectionName}
{intl.formatMessage(messages['learn.contentLock.content.locked'])}
{intl.formatMessage(messages['learn.contentLock.complete.prerequisite'], {
- prereqSectionName: metadata.gatedContent.prereqSectionName,
+ prereqSectionName,
})}
@@ -36,9 +31,7 @@ function ContentLock({ intl }) {
>
);
}
-
ContentLock.propTypes = {
intl: intlShape.isRequired,
};
-
export default injectIntl(ContentLock);
diff --git a/src/learning-sequence/sub-section/content-lock/index.js b/src/learning-sequence/sequence/content-lock/index.js
similarity index 100%
rename from src/learning-sequence/sub-section/content-lock/index.js
rename to src/learning-sequence/sequence/content-lock/index.js
diff --git a/src/learning-sequence/sub-section/content-lock/messages.js b/src/learning-sequence/sequence/content-lock/messages.js
similarity index 100%
rename from src/learning-sequence/sub-section/content-lock/messages.js
rename to src/learning-sequence/sequence/content-lock/messages.js
diff --git a/src/learning-sequence/sub-section/messages.js b/src/learning-sequence/sequence/messages.js
similarity index 100%
rename from src/learning-sequence/sub-section/messages.js
rename to src/learning-sequence/sequence/messages.js
diff --git a/src/learning-sequence/sub-section/SequenceMetadataContext.jsx b/src/learning-sequence/sub-section/SequenceMetadataContext.jsx
new file mode 100644
index 00000000..dce711da
--- /dev/null
+++ b/src/learning-sequence/sub-section/SequenceMetadataContext.jsx
@@ -0,0 +1,5 @@
+import React from 'react';
+
+const SequenceMetadataContext = React.createContext({});
+
+export default SequenceMetadataContext;
diff --git a/src/learning-sequence/sub-section/SubSection.jsx b/src/learning-sequence/sub-section/SubSection.jsx
index 25a2790d..a8f50e2a 100644
--- a/src/learning-sequence/sub-section/SubSection.jsx
+++ b/src/learning-sequence/sub-section/SubSection.jsx
@@ -1,31 +1,31 @@
import React, { useContext, Suspense } from 'react';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
-import SubSectionNavigation from './SubSectionNavigation';
+import SequenceNavigation from './SequenceNavigation';
import CourseStructureContext from '../CourseStructureContext';
import Unit from './Unit';
import {
- useLoadSubSectionMetadata,
+ useLoadSequenceMetadata,
useExamRedirect,
usePersistentUnitPosition,
useMissingUnitRedirect,
} from './data/hooks';
-import SubSectionMetadataContext from './SubSectionMetadataContext';
+import SequenceMetadataContext from './SequenceMetadataContext';
import PageLoading from '../PageLoading';
import messages from './messages';
import { useCurrentUnit } from '../data/hooks';
const ContentLock = React.lazy(() => import('./content-lock'));
-function SubSection({ intl }) {
+function Sequence({ intl }) {
const {
- courseId,
- subSectionId,
+ courseUsageKey,
+ sequenceId,
unitId,
blocks,
} = useContext(CourseStructureContext);
- const { metadata, loaded } = useLoadSubSectionMetadata(courseId, subSectionId);
- usePersistentUnitPosition(courseId, subSectionId, unitId, metadata);
+ const { metadata, loaded } = useLoadSequenceMetadata(courseUsageKey, sequenceId);
+ usePersistentUnitPosition(courseUsageKey, sequenceId, unitId, metadata);
useExamRedirect(metadata, blocks);
@@ -41,9 +41,9 @@ function SubSection({ intl }) {
const isGated = metadata.gatedContent.gated;
return (
-
+
-
+
);
}
-SubSection.propTypes = {
+Sequence.propTypes = {
intl: intlShape.isRequired,
};
-export default injectIntl(SubSection);
+export default injectIntl(Sequence);
diff --git a/src/learning-sequence/sub-section/SubSectionMetadataContext.jsx b/src/learning-sequence/sub-section/SubSectionMetadataContext.jsx
deleted file mode 100644
index 1143f829..00000000
--- a/src/learning-sequence/sub-section/SubSectionMetadataContext.jsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import React from 'react';
-
-const SubSectionMetadataContext = React.createContext({});
-
-export default SubSectionMetadataContext;
diff --git a/src/learning-sequence/sub-section/SubSectionNavigation.jsx b/src/learning-sequence/sub-section/SubSectionNavigation.jsx
index 0caa786d..e4dc5909 100644
--- a/src/learning-sequence/sub-section/SubSectionNavigation.jsx
+++ b/src/learning-sequence/sub-section/SubSectionNavigation.jsx
@@ -5,9 +5,9 @@ import { Button } from '@edx/paragon';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faFilm, faBook, faPencilAlt, faTasks, faLock } from '@fortawesome/free-solid-svg-icons';
-import { usePreviousUnit, useNextUnit, useCurrentSubSectionUnits, useCurrentUnit } from '../data/hooks';
+import { usePreviousUnit, useNextUnit, useCurrentSequenceUnits, useCurrentUnit } from '../data/hooks';
import CourseStructureContext from '../CourseStructureContext';
-import SubSectionMetadataContext from './SubSectionMetadataContext';
+import SequenceMetadataContext from './SequenceMetadataContext';
function UnitIcon({ type }) {
let icon = null;
@@ -40,24 +40,24 @@ UnitIcon.propTypes = {
type: PropTypes.oneOf(['video', 'other', 'vertical', 'problem', 'lock']).isRequired,
};
-export default function SubSectionNavigation() {
- const { courseId, unitId } = useContext(CourseStructureContext);
+export default function SequenceNavigation() {
+ const { courseUsageKey, unitId } = useContext(CourseStructureContext);
const previousUnit = usePreviousUnit();
const nextUnit = useNextUnit();
const handlePreviousClick = useCallback(() => {
if (previousUnit) {
- history.push(`/course/${courseId}/${previousUnit.parentId}/${previousUnit.id}`);
+ history.push(`/course/${courseUsageKey}/${previousUnit.parentId}/${previousUnit.id}`);
}
});
const handleNextClick = useCallback(() => {
if (nextUnit) {
- history.push(`/course/${courseId}/${nextUnit.parentId}/${nextUnit.id}`);
+ history.push(`/course/${courseUsageKey}/${nextUnit.parentId}/${nextUnit.id}`);
}
});
const handleUnitClick = useCallback((unit) => {
- history.push(`/course/${courseId}/${unit.parentId}/${unit.id}`);
+ history.push(`/course/${courseUsageKey}/${unit.parentId}/${unit.id}`);
});
if (!unitId) {
@@ -87,8 +87,8 @@ export default function SubSectionNavigation() {
function UnitNavigation({ clickHandler }) {
const currentUnit = useCurrentUnit();
- const units = useCurrentSubSectionUnits();
- const metadata = useContext(SubSectionMetadataContext);
+ const units = useCurrentSequenceUnits();
+ const metadata = useContext(SequenceMetadataContext);
const isGated = metadata.gatedContent.gated;