{(isVertical || isSequential) && (
-
+
)}
{ getConfig().ENABLE_TAGGING_TAXONOMY_PAGES === 'true' && !!contentTagCount && (
@@ -260,67 +302,4 @@ const CardHeader = ({
);
};
-CardHeader.defaultProps = {
- enableCopyPasteUnits: false,
- isVertical: false,
- isSequential: false,
- onClickCopy: null,
- proctoringExamConfigurationLink: null,
- discussionEnabled: false,
- discussionsSettings: {},
- parentInfo: {},
- cardId: '',
- extraActionsComponent: null,
- readyToSync: false,
- onClickSync: null,
-};
-
-CardHeader.propTypes = {
- title: PropTypes.string.isRequired,
- status: PropTypes.string.isRequired,
- cardId: PropTypes.string,
- hasChanges: PropTypes.bool.isRequired,
- onClickPublish: PropTypes.func.isRequired,
- onClickConfigure: PropTypes.func.isRequired,
- onClickMenuButton: PropTypes.func.isRequired,
- onClickEdit: PropTypes.func.isRequired,
- isFormOpen: PropTypes.bool.isRequired,
- onEditSubmit: PropTypes.func.isRequired,
- closeForm: PropTypes.func.isRequired,
- isDisabledEditField: PropTypes.bool.isRequired,
- onClickDelete: PropTypes.func.isRequired,
- onClickDuplicate: PropTypes.func.isRequired,
- onClickMoveUp: PropTypes.func.isRequired,
- onClickMoveDown: PropTypes.func.isRequired,
- onClickCopy: PropTypes.func,
- titleComponent: PropTypes.node.isRequired,
- namePrefix: PropTypes.string.isRequired,
- proctoringExamConfigurationLink: PropTypes.string,
- actions: PropTypes.shape({
- deletable: PropTypes.bool.isRequired,
- draggable: PropTypes.bool.isRequired,
- childAddable: PropTypes.bool.isRequired,
- duplicable: PropTypes.bool.isRequired,
- allowMoveUp: PropTypes.bool,
- allowMoveDown: PropTypes.bool,
- }).isRequired,
- enableCopyPasteUnits: PropTypes.bool,
- isVertical: PropTypes.bool,
- isSequential: PropTypes.bool,
- discussionEnabled: PropTypes.bool,
- discussionsSettings: PropTypes.shape({
- providerType: PropTypes.string,
- enableGradedUnits: PropTypes.bool,
- }),
- parentInfo: PropTypes.shape({
- isTimeLimited: PropTypes.bool,
- graded: PropTypes.bool,
- }),
- // An optional component that is rendered before the dropdown. This is used by the Subsection
- // and Unit card components to render their plugin slots.
- extraActionsComponent: PropTypes.node,
- onClickSync: PropTypes.func,
- readyToSync: PropTypes.bool,
-};
-
export default CardHeader;
diff --git a/src/course-outline/data/api.js b/src/course-outline/data/api.ts
similarity index 67%
rename from src/course-outline/data/api.js
rename to src/course-outline/data/api.ts
index 69bf8f018..d0e6dc17a 100644
--- a/src/course-outline/data/api.js
+++ b/src/course-outline/data/api.ts
@@ -1,15 +1,22 @@
-// @ts-check
import { camelCaseObject, getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
+import { XBlock } from '@src/data/types';
+import { CourseOutline } from './types';
const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL;
-export const getCourseOutlineIndexApiUrl = (courseId) => `${getApiBaseUrl()}/api/contentstore/v1/course_index/${courseId}`;
+export const getCourseOutlineIndexApiUrl = (
+ courseId: string,
+) => `${getApiBaseUrl()}/api/contentstore/v1/course_index/${courseId}`;
export const getCourseBestPracticesApiUrl = ({
courseId,
excludeGraded,
all,
+}: {
+ courseId: string,
+ excludeGraded: boolean,
+ all: boolean,
}) => `${getApiBaseUrl()}/api/courses/v1/quality/${courseId}/?exclude_graded=${excludeGraded}&all=${all}`;
export const getCourseLaunchApiUrl = ({
@@ -17,42 +24,31 @@ export const getCourseLaunchApiUrl = ({
gradedOnly,
validateOras,
all,
+}:{
+ courseId: string,
+ gradedOnly: boolean,
+ validateOras: boolean,
+ all: boolean,
}) => `${getApiBaseUrl()}/api/courses/v1/validation/${courseId}/?graded_only=${gradedOnly}&validate_oras=${validateOras}&all=${all}`;
-export const getCourseBlockApiUrl = (courseId) => {
+export const getCourseBlockApiUrl = (courseId: string) => {
const formattedCourseId = courseId.split('course-v1:')[1];
return `${getApiBaseUrl()}/xblock/block-v1:${formattedCourseId}+type@course+block@course`;
};
-export const getCourseReindexApiUrl = (reindexLink) => `${getApiBaseUrl()}${reindexLink}`;
+export const getCourseReindexApiUrl = (reindexLink: string) => `${getApiBaseUrl()}${reindexLink}`;
export const getXBlockBaseApiUrl = () => `${getApiBaseUrl()}/xblock/`;
-export const getCourseItemApiUrl = (itemId) => `${getXBlockBaseApiUrl()}${itemId}`;
-export const getXBlockApiUrl = (blockId) => `${getXBlockBaseApiUrl()}outline/${blockId}`;
-export const exportTags = (courseId) => `${getApiBaseUrl()}/api/content_tagging/v1/object_tags/${courseId}/export/`;
-
-/**
- * @typedef {Object} courseOutline
- * @property {string} courseReleaseDate
- * @property {Object} courseStructure
- * @property {Object} deprecatedBlocksInfo
- * @property {string} discussionsIncontextLearnmoreUrl
- * @property {Object} initialState
- * @property {Object} initialUserClipboard
- * @property {string} languageCode
- * @property {string} lmsLink
- * @property {string} mfeProctoredExamSettingsUrl
- * @property {string} notificationDismissUrl
- * @property {string[]} proctoringErrors
- * @property {string} reindexLink
- * @property {null} rerunNotificationId
- */
+export const getCourseItemApiUrl = (itemId: string) => `${getXBlockBaseApiUrl()}${itemId}`;
+export const getXBlockApiUrl = (blockId: string) => `${getXBlockBaseApiUrl()}outline/${blockId}`;
+export const exportTags = (courseId: string) => `${getApiBaseUrl()}/api/content_tagging/v1/object_tags/${courseId}/export/`;
+export const createDiscussionsTopicsUrl = (courseId: string) => `${getApiBaseUrl()}/api/discussions/v0/course/${courseId}/sync_discussion_topics`;
/**
* Get course outline index.
* @param {string} courseId
* @returns {Promise
}
*/
-export async function getCourseOutlineIndex(courseId) {
+export async function getCourseOutlineIndex(courseId: string): Promise {
const { data } = await getAuthenticatedHttpClient()
.get(getCourseOutlineIndexApiUrl(courseId));
@@ -64,9 +60,9 @@ export async function getCourseOutlineIndex(courseId) {
* @param courseId
* @returns {Promise}
*/
-export async function createDiscussionsTopics(courseId) {
+export async function createDiscussionsTopics(courseId: string): Promise | object> {
const { data } = await getAuthenticatedHttpClient()
- .post(`${getApiBaseUrl()}/api/discussions/v0/course/${courseId}/sync_discussion_topics`);
+ .post(createDiscussionsTopicsUrl(courseId));
return camelCaseObject(data);
}
@@ -79,35 +75,46 @@ export async function getCourseBestPractices({
courseId,
excludeGraded,
all,
-}) {
+}: {
+ courseId: string;
+ excludeGraded: boolean;
+ all: boolean;
+}): Promise<{
+ isSelfPaced: boolean;
+ sections: any;
+ subsection: any;
+ units: any;
+ videos: any;
+ }> {
const { data } = await getAuthenticatedHttpClient()
.get(getCourseBestPracticesApiUrl({ courseId, excludeGraded, all }));
return camelCaseObject(data);
}
-/** @typedef {object} courseLaunchData
- * @property {boolean} isSelfPaced
- * @property {object} dates
- * @property {object} assignments
- * @property {object} grades
- * @property {number} grades.sum_of_weights
- * @property {object} certificates
- * @property {object} updates
- * @property {object} proctoring
- */
+interface CourseLaunchData {
+ isSelfPaced: boolean;
+ dates: object;
+ assignments: object;
+ grades: {
+ sum_of_weights: number;
+ };
+ certificates: object;
+ updates: object;
+ proctoring: object;
+}
/**
* Get course launch.
* @param {{courseId: string, gradedOnly: boolean, validateOras: boolean, all: boolean}} options
- * @returns {Promise}
+ * @returns {Promise}
*/
export async function getCourseLaunch({
courseId,
gradedOnly,
validateOras,
all,
-}) {
+}: { courseId: string; gradedOnly: boolean; validateOras: boolean; all: boolean; }): Promise {
const { data } = await getAuthenticatedHttpClient()
.get(getCourseLaunchApiUrl({
courseId, gradedOnly, validateOras, all,
@@ -121,7 +128,7 @@ export async function getCourseLaunch({
* @param {string} courseId
* @returns {Promise
@@ -320,60 +325,4 @@ const SubsectionCard = ({
);
};
-SubsectionCard.defaultProps = {
- children: null,
-};
-
-SubsectionCard.propTypes = {
- section: PropTypes.shape({
- id: PropTypes.string.isRequired,
- displayName: PropTypes.string.isRequired,
- published: PropTypes.bool.isRequired,
- hasChanges: PropTypes.bool.isRequired,
- visibilityState: PropTypes.string.isRequired,
- shouldScroll: PropTypes.bool,
- }).isRequired,
- subsection: PropTypes.shape({
- id: PropTypes.string.isRequired,
- displayName: PropTypes.string.isRequired,
- category: PropTypes.string.isRequired,
- published: PropTypes.bool.isRequired,
- hasChanges: PropTypes.bool.isRequired,
- visibilityState: PropTypes.string.isRequired,
- shouldScroll: PropTypes.bool,
- enableCopyPasteUnits: PropTypes.bool,
- proctoringExamConfigurationLink: PropTypes.string,
- actions: PropTypes.shape({
- deletable: PropTypes.bool.isRequired,
- draggable: PropTypes.bool.isRequired,
- childAddable: PropTypes.bool.isRequired,
- duplicable: PropTypes.bool.isRequired,
- }).isRequired,
- isHeaderVisible: PropTypes.bool,
- childInfo: PropTypes.shape({
- children: PropTypes.arrayOf(
- PropTypes.shape({
- id: PropTypes.string.isRequired,
- }),
- ).isRequired,
- }).isRequired,
- }).isRequired,
- children: PropTypes.node,
- isSectionsExpanded: PropTypes.bool.isRequired,
- isSelfPaced: PropTypes.bool.isRequired,
- isCustomRelativeDatesActive: PropTypes.bool.isRequired,
- onOpenPublishModal: PropTypes.func.isRequired,
- onEditSubmit: PropTypes.func.isRequired,
- savingStatus: PropTypes.string.isRequired,
- onOpenDeleteModal: PropTypes.func.isRequired,
- onDuplicateSubmit: PropTypes.func.isRequired,
- onNewUnitSubmit: PropTypes.func.isRequired,
- onAddUnitFromLibrary: PropTypes.func.isRequired,
- index: PropTypes.number.isRequired,
- getPossibleMoves: PropTypes.func.isRequired,
- onOrderChange: PropTypes.func.isRequired,
- onOpenConfigureModal: PropTypes.func.isRequired,
- onPasteClick: PropTypes.func.isRequired,
-};
-
export default SubsectionCard;
diff --git a/src/course-outline/subsection-card/messages.js b/src/course-outline/subsection-card/messages.js
index d932382d4..b4d251fb4 100644
--- a/src/course-outline/subsection-card/messages.js
+++ b/src/course-outline/subsection-card/messages.js
@@ -1,21 +1,11 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
- newUnitButton: {
- id: 'course-authoring.course-outline.subsection.button.new-unit',
- defaultMessage: 'New unit',
- description: 'Message of the button to create a new unit in a subsection.',
- },
pasteButton: {
id: 'course-authoring.course-outline.subsection.button.paste-unit',
defaultMessage: 'Paste unit',
description: 'Message of the button to paste a new unit in a subsection.',
},
- useUnitFromLibraryButton: {
- id: 'course-authoring.course-outline.subsection.button.use-unit-from-library',
- defaultMessage: 'Use unit from library',
- description: 'Message of the button to add a new unit from a library in a subsection.',
- },
unitPickerModalTitle: {
id: 'course-authoring.course-outline.subsection.unit.modal.single-title.text',
defaultMessage: 'Select unit',
diff --git a/src/course-outline/unit-card/UnitCard.test.jsx b/src/course-outline/unit-card/UnitCard.test.tsx
similarity index 77%
rename from src/course-outline/unit-card/UnitCard.test.jsx
rename to src/course-outline/unit-card/UnitCard.test.tsx
index 663fa59c2..06f8c0a60 100644
--- a/src/course-outline/unit-card/UnitCard.test.jsx
+++ b/src/course-outline/unit-card/UnitCard.test.tsx
@@ -1,21 +1,15 @@
import {
- act, render, fireEvent, within, screen,
- waitFor,
-} from '@testing-library/react';
-import { IntlProvider } from '@edx/frontend-platform/i18n';
-import { AppProvider } from '@edx/frontend-platform/react';
-import { initializeMockApp } from '@edx/frontend-platform';
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+ act, fireEvent, initializeMocks, render, screen, waitFor, within,
+} from '@src/testUtils';
-import initializeStore from '../../store';
+import { XBlock } from '@src/data/types';
import UnitCard from './UnitCard';
import cardMessages from '../card-header/messages';
-let store;
const mockUseAcceptLibraryBlockChanges = jest.fn();
const mockUseIgnoreLibraryBlockChanges = jest.fn();
-jest.mock('../../course-unit/data/apiHooks', () => ({
+jest.mock('@src/course-unit/data/apiHooks', () => ({
useAcceptLibraryBlockChanges: () => ({
mutateAsync: mockUseAcceptLibraryBlockChanges,
}),
@@ -31,7 +25,7 @@ const section = {
visibilityState: 'live',
hasChanges: false,
highlights: ['highlight 1', 'highlight 2'],
-};
+} as XBlock;
const subsection = {
id: '12',
@@ -39,7 +33,7 @@ const subsection = {
published: true,
visibilityState: 'live',
hasChanges: false,
-};
+} as XBlock;
const unit = {
id: '123',
@@ -60,49 +54,36 @@ const unit = {
upstreamRef: 'lct:org1:lib1:unit:1',
versionSynced: 1,
},
-};
+} as XBlock;
-const queryClient = new QueryClient();
-
-const renderComponent = (props) => render(
- `/some/${id}`}
+ isSelfPaced={false}
+ isCustomRelativeDatesActive={false}
+ discussionsSettings={{
+ providerType: '',
+ enableGradedUnits: false,
+ }}
+ {...props}
+ />,
);
describe('', () => {
beforeEach(() => {
- initializeMockApp({
- authenticatedUser: {
- userId: 3,
- username: 'abc123',
- administrator: true,
- roles: [],
- },
- });
-
- store = initializeStore();
+ initializeMocks();
});
it('render UnitCard component correctly', async () => {
diff --git a/src/course-outline/unit-card/UnitCard.jsx b/src/course-outline/unit-card/UnitCard.tsx
similarity index 62%
rename from src/course-outline/unit-card/UnitCard.jsx
rename to src/course-outline/unit-card/UnitCard.tsx
index 25c9bfd5b..9b1a1a85d 100644
--- a/src/course-outline/unit-card/UnitCard.jsx
+++ b/src/course-outline/unit-card/UnitCard.tsx
@@ -1,28 +1,49 @@
-// @ts-check
-import React, {
+import {
useCallback,
useEffect,
useMemo,
useRef,
} from 'react';
-import PropTypes from 'prop-types';
import { useDispatch } from 'react-redux';
import { useToggle } from '@openedx/paragon';
import { isEmpty } from 'lodash';
import { useSearchParams } from 'react-router-dom';
-import CourseOutlineUnitCardExtraActionsSlot from '../../plugin-slots/CourseOutlineUnitCardExtraActionsSlot';
-import { setCurrentItem, setCurrentSection, setCurrentSubsection } from '../data/slice';
-import { fetchCourseSectionQuery } from '../data/thunk';
-import { RequestStatus } from '../../data/constants';
-import { isUnitReadOnly } from '../../course-unit/data/utils';
-import CardHeader from '../card-header/CardHeader';
-import SortableItem from '../drag-helper/SortableItem';
-import TitleLink from '../card-header/TitleLink';
-import XBlockStatus from '../xblock-status/XBlockStatus';
-import { getItemStatus, getItemStatusBorder, scrollToElement } from '../utils';
-import { useClipboard } from '../../generic/clipboard';
-import { PreviewLibraryXBlockChanges } from '../../course-unit/preview-changes';
+import CourseOutlineUnitCardExtraActionsSlot from '@src/plugin-slots/CourseOutlineUnitCardExtraActionsSlot';
+import { setCurrentItem, setCurrentSection, setCurrentSubsection } from '@src/course-outline/data/slice';
+import { fetchCourseSectionQuery } from '@src/course-outline/data/thunk';
+import { RequestStatus } from '@src/data/constants';
+import { isUnitReadOnly } from '@src/course-unit/data/utils';
+import CardHeader from '@src/course-outline/card-header/CardHeader';
+import SortableItem from '@src/course-outline/drag-helper/SortableItem';
+import TitleLink from '@src/course-outline/card-header/TitleLink';
+import XBlockStatus from '@src/course-outline/xblock-status/XBlockStatus';
+import { getItemStatus, getItemStatusBorder, scrollToElement } from '@src/course-outline/utils';
+import { useClipboard } from '@src/generic/clipboard';
+import { PreviewLibraryXBlockChanges } from '@src/course-unit/preview-changes';
+import { XBlock } from '@src/data/types';
+
+interface UnitCardProps {
+ unit: XBlock;
+ subsection: XBlock;
+ section: XBlock;
+ onOpenPublishModal: () => void;
+ onOpenConfigureModal: () => void;
+ onEditSubmit: (itemId: string, sectionId: string, displayName: string) => void,
+ savingStatus: string;
+ onOpenDeleteModal: () => void;
+ onDuplicateSubmit: () => void;
+ getTitleLink: (locator: string) => string;
+ index: number;
+ getPossibleMoves: (index: number, step: number) => void,
+ onOrderChange: (section: XBlock, moveDetails: any) => void,
+ isSelfPaced: boolean;
+ isCustomRelativeDatesActive: boolean;
+ discussionsSettings: {
+ providerType: string;
+ enableGradedUnits: boolean;
+ };
+}
const UnitCard = ({
unit,
@@ -41,7 +62,7 @@ const UnitCard = ({
getTitleLink,
onOrderChange,
discussionsSettings,
-}) => {
+}: UnitCardProps) => {
const currentRef = useRef(null);
const dispatch = useDispatch();
const [searchParams] = useSearchParams();
@@ -68,7 +89,7 @@ const UnitCard = ({
} = unit;
const blockSyncData = useMemo(() => {
- if (!upstreamInfo.readyToSync) {
+ if (!upstreamInfo?.readyToSync) {
return undefined;
}
return {
@@ -108,7 +129,7 @@ const UnitCard = ({
dispatch(setCurrentSubsection(subsection));
};
- const handleEditSubmit = (titleValue) => {
+ const handleEditSubmit = (titleValue: string) => {
if (displayName !== titleValue) {
onEditSubmit(id, section.id, titleValue);
return;
@@ -129,8 +150,8 @@ const UnitCard = ({
copyToClipboard(id);
};
- const handleOnPostChangeSync = useCallback(async () => {
- await dispatch(fetchCourseSectionQuery([section.id]));
+ const handleOnPostChangeSync = useCallback(() => {
+ dispatch(fetchCourseSectionQuery([section.id]));
}, [dispatch, section]);
const titleComponent = (
@@ -151,12 +172,10 @@ const UnitCard = ({
useEffect(() => {
// if this items has been newly added, scroll to it.
- // we need to check section.shouldScroll as whole section is fetched when a
- // unit is duplicated under it.
- if (currentRef.current && (section.shouldScroll || unit.shouldScroll || isScrolledToElement)) {
+ if (currentRef.current && (unit.shouldScroll || isScrolledToElement)) {
// Align element closer to the top of the screen if scrolling for search result
const alignWithTop = !!isScrolledToElement;
- scrollToElement(currentRef.current, alignWithTop);
+ scrollToElement(currentRef.current, alignWithTop, true);
}
}, [isScrolledToElement]);
@@ -218,7 +237,7 @@ const UnitCard = ({
discussionsSettings={discussionsSettings}
parentInfo={parentInfo}
extraActionsComponent={extraActionsComponent}
- readyToSync={upstreamInfo.readyToSync}
+ readyToSync={upstreamInfo?.readyToSync}
/>
{
* @param {Object} target - DOM Element
* @param {boolean} alignWithTop (optional) - Whether top of the target will be aligned to
* the top of viewpoint. (default: false)
+ * @param {boolean} highlight (optional) - Whether highlight the target after scrolling.
+ * (default: false)
* @returns {undefined}
*/
-const scrollToElement = (target, alignWithTop = false) => {
+const scrollToElement = (target, alignWithTop = false, highlight = false) => {
if (target.getBoundingClientRect().bottom > window.innerHeight) {
// if alignWithTop is set, the top of the target will be aligned to the top of visible area
// of the scrollable ancestor, Otherwise, the bottom of the target will be aligned to the
@@ -186,6 +188,10 @@ const scrollToElement = (target, alignWithTop = false) => {
// The top of the target will be aligned to the top of the visible area of the scrollable ancestor
target.scrollIntoView({ behavior: 'smooth' });
}
+
+ if (highlight && !target.classList.contains('highlight')) {
+ target.classList.add('highlight');
+ }
};
/**
diff --git a/src/course-outline/xblock-status/StatusMessages.jsx b/src/course-outline/xblock-status/StatusMessages.tsx
similarity index 69%
rename from src/course-outline/xblock-status/StatusMessages.jsx
rename to src/course-outline/xblock-status/StatusMessages.tsx
index dd0fc5390..547cc8441 100644
--- a/src/course-outline/xblock-status/StatusMessages.jsx
+++ b/src/course-outline/xblock-status/StatusMessages.tsx
@@ -1,5 +1,4 @@
import React from 'react';
-import PropTypes from 'prop-types';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Icon } from '@openedx/paragon';
import {
@@ -7,8 +6,23 @@ import {
Groups as GroupsIcon,
} from '@openedx/paragon/icons';
+import { UserPartitionInfoTypes, XBlockPrereqs } from '@src/data/types';
import messages from './messages';
+interface StatusMessagesProps {
+ isVertical: boolean;
+ staffOnlyMessage?: boolean,
+ prereq?: string,
+ prereqs?: XBlockPrereqs[],
+ userPartitionInfo?: UserPartitionInfoTypes,
+ hasPartitionGroupComponents?: boolean,
+}
+
+interface StatusMessagesText {
+ icon: React.ComponentType;
+ text: string;
+}
+
const StatusMessages = ({
isVertical,
staffOnlyMessage,
@@ -16,13 +30,13 @@ const StatusMessages = ({
prereqs,
userPartitionInfo,
hasPartitionGroupComponents,
-}) => {
+}: StatusMessagesProps) => {
const intl = useIntl();
- const statusMessages = [];
+ const statusMessages: StatusMessagesText[] = [];
if (prereq) {
let prereqDisplayName = '';
- prereqs.forEach((block) => {
+ prereqs?.forEach((block) => {
if (block.blockUsageKey === prereq) {
prereqDisplayName = block.blockDisplayName;
}
@@ -34,7 +48,7 @@ const StatusMessages = ({
}
if (!staffOnlyMessage && isVertical) {
- const { selectedPartitionIndex, selectedGroupsLabel } = userPartitionInfo;
+ const { selectedPartitionIndex, selectedGroupsLabel } = userPartitionInfo || {};
if (selectedPartitionIndex !== -1 && !Number.isNaN(selectedPartitionIndex)) {
statusMessages.push({
icon: GroupsIcon,
@@ -63,27 +77,4 @@ const StatusMessages = ({
return null;
};
-StatusMessages.defaultProps = {
- staffOnlyMessage: false,
- prereq: '',
- prereqs: [],
- userPartitionInfo: {},
- hasPartitionGroupComponents: false,
-};
-
-StatusMessages.propTypes = {
- isVertical: PropTypes.bool.isRequired,
- staffOnlyMessage: PropTypes.bool,
- prereq: PropTypes.string,
- prereqs: PropTypes.arrayOf(PropTypes.shape({
- blockUsageKey: PropTypes.string.isRequired,
- blockDisplayName: PropTypes.string.isRequired,
- })),
- userPartitionInfo: PropTypes.shape({
- selectedPartitionIndex: PropTypes.number,
- selectedGroupsLabel: PropTypes.string,
- }),
- hasPartitionGroupComponents: PropTypes.bool,
-};
-
export default StatusMessages;
diff --git a/src/course-outline/xblock-status/XBlockStatus.jsx b/src/course-outline/xblock-status/XBlockStatus.tsx
similarity index 65%
rename from src/course-outline/xblock-status/XBlockStatus.jsx
rename to src/course-outline/xblock-status/XBlockStatus.tsx
index 77e81e071..1f30f49ce 100644
--- a/src/course-outline/xblock-status/XBlockStatus.jsx
+++ b/src/course-outline/xblock-status/XBlockStatus.tsx
@@ -1,6 +1,5 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
+import { ShowAnswerTypesKeys } from '@src/editors/data/constants/problem';
+import { XBlock } from '@src/data/types';
import { COURSE_BLOCK_NAMES } from '../constants';
import ReleaseStatus from './ReleaseStatus';
import GradingPolicyAlert from './GradingPolicyAlert';
@@ -8,13 +7,18 @@ import GradingTypeAndDueDate from './GradingTypeAndDueDate';
import StatusMessages from './StatusMessages';
import HideAfterDueMessage from './HideAfterDueMessage';
import NeverShowAssessmentResultMessage from './NeverShowAssessmentResultMessage';
-import { ShowAnswerTypesKeys } from '../../editors/data/constants/problem';
+
+interface XBlockStatusProps {
+ isSelfPaced: boolean;
+ isCustomRelativeDatesActive: boolean,
+ blockData: XBlock,
+}
const XBlockStatus = ({
isSelfPaced,
isCustomRelativeDatesActive,
blockData,
-}) => {
+}: XBlockStatusProps) => {
const {
category,
explanatoryMessage,
@@ -89,41 +93,4 @@ const XBlockStatus = ({
);
};
-XBlockStatus.defaultProps = {
- isCustomRelativeDatesActive: false,
-};
-
-XBlockStatus.propTypes = {
- isSelfPaced: PropTypes.bool.isRequired,
- isCustomRelativeDatesActive: PropTypes.bool,
- blockData: PropTypes.shape({
- category: PropTypes.string.isRequired,
- explanatoryMessage: PropTypes.string,
- releasedToStudents: PropTypes.bool,
- releaseDate: PropTypes.string,
- isProctoredExam: PropTypes.bool,
- isOnboardingExam: PropTypes.bool,
- isPracticeExam: PropTypes.bool,
- prereq: PropTypes.string,
- prereqs: PropTypes.arrayOf(PropTypes.shape({
- blockUsageKey: PropTypes.string.isRequired,
- blockDisplayName: PropTypes.string.isRequired,
- })),
- staffOnlyMessage: PropTypes.bool,
- userPartitionInfo: PropTypes.shape({
- selectedPartitionIndex: PropTypes.number,
- selectedGroupsLabel: PropTypes.string,
- }),
- hasPartitionGroupComponents: PropTypes.bool,
- format: PropTypes.string,
- dueDate: PropTypes.string,
- relativeWeeksDue: PropTypes.number,
- isTimeLimited: PropTypes.bool,
- graded: PropTypes.bool,
- courseGraders: PropTypes.arrayOf(PropTypes.string.isRequired),
- hideAfterDue: PropTypes.bool,
- showCorrectness: PropTypes.string,
- }).isRequired,
-};
-
export default XBlockStatus;
diff --git a/src/course-unit/xblock-container-iframe/types.ts b/src/course-unit/xblock-container-iframe/types.ts
index 084577d16..89ac1b79b 100644
--- a/src/course-unit/xblock-container-iframe/types.ts
+++ b/src/course-unit/xblock-container-iframe/types.ts
@@ -1,16 +1,4 @@
-export interface GroupTypes {
- id: number;
- name: string;
- selected: boolean;
- deleted: boolean;
-}
-
-export interface UserPartitionTypes {
- id: number;
- name: string;
- scheme: string;
- groups: Array;
-}
+import { UserPartitionInfoTypes, UserPartitionTypes, XBlockPrereqs } from '@src/data/types';
export interface XBlockActionsTypes {
canCopy: boolean;
@@ -50,27 +38,6 @@ export interface XBlockContainerIframeProps {
handleConfigureSubmit: (XBlockId: string, ...args: any[]) => void;
}
-export type UserPartitionInfoTypes = {
- selectablePartitions: Array<{
- groups: Array<{
- deleted: boolean;
- id: number;
- name: string;
- selected: boolean;
- }>;
- id: number;
- name: string;
- scheme: string;
- }>;
- selectedPartitionIndex: number;
- selectedGroupsLabel: string;
-};
-
-export type PrereqTypes = {
- blockDisplayName: string;
- blockUsageKey: string;
-};
-
export type AccessManagedXBlockDataTypes = {
id: string;
displayName?: string;
@@ -88,7 +55,7 @@ export type AccessManagedXBlockDataTypes = {
userPartitionInfo?: UserPartitionInfoTypes;
ancestorHasStaffLock?: boolean;
isPrereq?: boolean;
- prereqs?: PrereqTypes[];
+ prereqs?: XBlockPrereqs[];
prereq?: number;
prereqMinScore?: number;
prereqMinCompletion?: number;
diff --git a/src/data/types.ts b/src/data/types.ts
new file mode 100644
index 000000000..f7fedfd82
--- /dev/null
+++ b/src/data/types.ts
@@ -0,0 +1,110 @@
+export interface GroupTypes {
+ id: number;
+ name: string;
+ selected: boolean;
+ deleted: boolean;
+}
+
+export interface UserPartitionTypes {
+ id: number;
+ name: string;
+ scheme: string;
+ groups: Array;
+}
+
+export type UserPartitionInfoTypes = {
+ selectablePartitions: Array<{
+ groups: Array<{
+ deleted: boolean;
+ id: number;
+ name: string;
+ selected: boolean;
+ }>;
+ id: number;
+ name: string;
+ scheme: string;
+ }>;
+ selectedPartitionIndex: number;
+ selectedGroupsLabel: string;
+};
+
+export interface XBlockActions {
+ deletable: boolean;
+ draggable: boolean;
+ childAddable: boolean;
+ duplicable: boolean;
+ allowMoveDown?: boolean;
+ allowMoveUp?: boolean;
+}
+
+export interface XblockChildInfo {
+ displayName: string;
+ children: Array;
+}
+
+export interface XBlockPrereqs {
+ blockUsageKey: string;
+ blockDisplayName: string;
+}
+
+export interface UpstreeamInfo {
+ readyToSync: boolean,
+ upstreamRef: string,
+ versionSynced: number,
+}
+
+export interface XBlock {
+ id: string;
+ locator: string;
+ usageKey: string;
+ displayName: string;
+ category: string;
+ hasChildren: boolean;
+ editedOn: string;
+ published: boolean;
+ publishedOn: string;
+ studioUrl: string;
+ releasedToStudents: boolean;
+ releaseDate: string;
+ visibilityState: string;
+ hasExplicitStaffLock: boolean;
+ start: string;
+ graded: boolean;
+ dueDate: string;
+ due?: string;
+ relativeWeeksDue?: number;
+ format?: string;
+ courseGraders: string[];
+ hasChanges: boolean;
+ actions: XBlockActions;
+ explanatoryMessage?: string;
+ userPartitions: UserPartitionTypes[];
+ showCorrectness: string;
+ highlights: string[];
+ highlightsEnabled: boolean;
+ highlightsPreviewOnly: boolean;
+ highlightsDocUrl: string;
+ childInfo: XblockChildInfo;
+ ancestorHasStaffLock: boolean;
+ staffOnlyMessage: boolean;
+ hasPartitionGroupComponents: boolean;
+ userPartitionInfo?: UserPartitionInfoTypes;
+ enableCopyPasteUnits: boolean;
+ shouldScroll: boolean;
+ isHeaderVisible: boolean;
+ proctoringExamConfigurationLink?: string;
+ isTimeLimited?: boolean;
+ defaultTimeLimitMinutes?: number;
+ hideAfterDue?: boolean;
+ isProctoredExam?: boolean;
+ isPracticeExam?: boolean;
+ isOnboardingExam?: boolean;
+ examReviewRules?: string;
+ isPrereq?: boolean;
+ prereq?: string;
+ prereqs?: XBlockPrereqs[];
+ prereqMinScore?: number;
+ prereqMinCompletion?: number;
+ discussionEnabled?: boolean;
+ upstreamInfo?: UpstreeamInfo;
+}
diff --git a/src/library-authoring/LibraryAuthoringPage.tsx b/src/library-authoring/LibraryAuthoringPage.tsx
index bfc13677c..4769bdb35 100644
--- a/src/library-authoring/LibraryAuthoringPage.tsx
+++ b/src/library-authoring/LibraryAuthoringPage.tsx
@@ -257,7 +257,8 @@ const LibraryAuthoringPage = ({
// or when inside a specific Section or Subsection.
const onlyOneType = (
insideCollections || insideUnits || insideSections || insideSubsections
- || insideSection || insideSubsection
+ || insideSection || insideSubsection
+ || !([ContentType.home, ContentType.components].includes(activeKey))
);
const overrideTypesFilter = onlyOneType
? new TypesFilterData()
diff --git a/src/library-authoring/index.tsx b/src/library-authoring/index.tsx
index 91100f090..6c519f3c2 100644
--- a/src/library-authoring/index.tsx
+++ b/src/library-authoring/index.tsx
@@ -1,5 +1,6 @@
export { default as LibraryLayout } from './LibraryLayout';
export { ComponentPicker } from './component-picker';
+export { type SelectedComponent } from './common/context/ComponentPickerContext';
export { CreateLibrary } from './create-library';
export { libraryAuthoringQueryKeys, useContentLibraryV2List } from './data/apiHooks';
export { default as PreviewChangesEmbed } from './legacy-integration/PreviewChangesEmbed';
diff --git a/webpack.prod.config.js b/webpack.prod.config.js
index c5e13b041..78bc2606c 100644
--- a/webpack.prod.config.js
+++ b/webpack.prod.config.js
@@ -4,6 +4,8 @@ const { createConfig } = require('@openedx/frontend-build');
const config = createConfig('webpack-prod', {
resolve: {
alias: {
+ // Within this app, we can use '@src/foo instead of relative URLs like '../../../foo'
+ '@src': path.resolve(__dirname, 'src/'),
// Plugins can use 'CourseAuthoring' as an import alias for this app:
CourseAuthoring: path.resolve(__dirname, 'src/'),
},