Skip to content

Commit a23a4da

Browse files
authored
feat: add content in location in course outline [FC-0114] (#2820)
- Add container in-location in course outline using new add sidebar. - Creates the placeholder card while creating a container
1 parent 4cda17e commit a23a4da

27 files changed

Lines changed: 935 additions & 434 deletions

src/CourseAuthoringContext.tsx

Lines changed: 16 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { useCreateCourseBlock } from '@src/course-outline/data/apiHooks';
55
import { getCourseItem } from '@src/course-outline/data/api';
66
import { useDispatch, useSelector } from 'react-redux';
77
import { addSection, addSubsection, updateSavingStatus } from '@src/course-outline/data/slice';
8-
import { addNewSectionQuery, addNewSubsectionQuery, addNewUnitQuery } from '@src/course-outline/data/thunk';
98
import { useNavigate } from 'react-router';
109
import { getOutlineIndexData } from '@src/course-outline/data/selectors';
1110
import { RequestStatus, RequestStatusType } from './data/constants';
@@ -19,12 +18,9 @@ export type CourseAuthoringContextData = {
1918
courseDetails?: CourseDetailsData;
2019
courseDetailStatus: RequestStatusType;
2120
canChangeProviders: boolean;
22-
handleAddSectionFromLibrary: ReturnType<typeof useCreateCourseBlock>;
23-
handleAddSubsectionFromLibrary: ReturnType<typeof useCreateCourseBlock>;
24-
handleAddUnitFromLibrary: ReturnType<typeof useCreateCourseBlock>;
25-
handleNewSectionSubmit: () => void;
26-
handleNewSubsectionSubmit: (sectionId: string) => void;
27-
handleNewUnitSubmit: (subsectionId: string) => void;
21+
handleAddSection: ReturnType<typeof useCreateCourseBlock>;
22+
handleAddSubsection: ReturnType<typeof useCreateCourseBlock>;
23+
handleAddUnit: ReturnType<typeof useCreateCourseBlock>;
2824
openUnitPage: (locator: string) => void;
2925
getUnitUrl: (locator: string) => string;
3026
};
@@ -76,19 +72,7 @@ export const CourseAuthoringProvider = ({
7672
}
7773
};
7874

79-
const handleNewSectionSubmit = () => {
80-
dispatch(addNewSectionQuery(courseUsageKey));
81-
};
82-
83-
const handleNewSubsectionSubmit = (sectionId: string) => {
84-
dispatch(addNewSubsectionQuery(sectionId));
85-
};
86-
87-
const handleNewUnitSubmit = (subsectionId: string) => {
88-
dispatch(addNewUnitQuery(subsectionId, openUnitPage));
89-
};
90-
91-
const handleAddSectionFromLibrary = useCreateCourseBlock(async (locator) => {
75+
const addSectionToCourse = async (locator: string) => {
9276
try {
9377
const data = await getCourseItem(locator);
9478
// instanbul ignore next
@@ -98,9 +82,9 @@ export const CourseAuthoringProvider = ({
9882
} catch {
9983
dispatch(updateSavingStatus({ status: RequestStatus.FAILED }));
10084
}
101-
});
85+
};
10286

103-
const handleAddSubsectionFromLibrary = useCreateCourseBlock(async (locator, parentLocator) => {
87+
const addSubsectionToCourse = async (locator: string, parentLocator: string) => {
10488
try {
10589
const data = await getCourseItem(locator);
10690
data.shouldScroll = true;
@@ -109,25 +93,24 @@ export const CourseAuthoringProvider = ({
10993
} catch {
11094
dispatch(updateSavingStatus({ status: RequestStatus.FAILED }));
11195
}
112-
});
96+
};
11397

98+
const handleAddSection = useCreateCourseBlock(addSectionToCourse);
99+
const handleAddSubsection = useCreateCourseBlock(addSubsectionToCourse);
114100
/**
115101
* import a unit block from library and redirect user to this unit page.
116102
*/
117-
const handleAddUnitFromLibrary = useCreateCourseBlock(openUnitPage);
103+
const handleAddUnit = useCreateCourseBlock(openUnitPage);
118104

119105
const context = useMemo<CourseAuthoringContextData>(() => ({
120106
courseId,
121107
courseUsageKey,
122108
courseDetails,
123109
courseDetailStatus,
124110
canChangeProviders,
125-
handleNewSectionSubmit,
126-
handleNewSubsectionSubmit,
127-
handleNewUnitSubmit,
128-
handleAddSectionFromLibrary,
129-
handleAddSubsectionFromLibrary,
130-
handleAddUnitFromLibrary,
111+
handleAddSection,
112+
handleAddSubsection,
113+
handleAddUnit,
131114
getUnitUrl,
132115
openUnitPage,
133116
}), [
@@ -136,12 +119,9 @@ export const CourseAuthoringProvider = ({
136119
courseDetails,
137120
courseDetailStatus,
138121
canChangeProviders,
139-
handleNewSectionSubmit,
140-
handleNewSubsectionSubmit,
141-
handleNewUnitSubmit,
142-
handleAddSectionFromLibrary,
143-
handleAddSubsectionFromLibrary,
144-
handleAddUnitFromLibrary,
122+
handleAddSection,
123+
handleAddSubsection,
124+
handleAddUnit,
145125
getUnitUrl,
146126
openUnitPage,
147127
]);

src/course-libraries/LegacyLibContentBlockAlert.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,15 @@ const LegacyLibContentBlockAlert = ({ courseId }: Props) => {
7070
target="_blank"
7171
as={Hyperlink}
7272
variant="tertiary"
73+
key="learn-more"
7374
showLaunchIcon={false}
7475
destination={learnMoreUrl}
7576
>
7677
{intl.formatMessage(messages.legacyLibReadyToMigrateAlertLearnMoreBtn)}
7778
</Button>,
7879
<LoadingButton
7980
onClick={migrateFn}
81+
key="migrate-button"
8082
label={intl.formatMessage(messages.legacyLibReadyToMigrateAlertActionBtn)}
8183
/>,
8284
]}

src/course-outline/CourseOutline.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,7 @@
88
@import "./publish-modal/PublishModal";
99
@import "./xblock-status/XBlockStatus";
1010
@import "./drag-helper/SortableItem";
11+
12+
.border-dashed {
13+
border: dashed;
14+
}

src/course-outline/CourseOutline.test.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -354,8 +354,9 @@ describe('<CourseOutline />', () => {
354354
});
355355

356356
it('adds new section correctly', async () => {
357-
const { findAllByTestId } = renderComponent();
358-
let elements = await findAllByTestId('section-card');
357+
const user = userEvent.setup();
358+
renderComponent();
359+
let elements = await screen.findAllByTestId('section-card');
359360
window.HTMLElement.prototype.getBoundingClientRect = jest.fn(() => ({
360361
top: 0,
361362
bottom: 4000,
@@ -378,9 +379,9 @@ describe('<CourseOutline />', () => {
378379
.onGet(getXBlockApiUrl(courseSectionMock.id))
379380
.reply(200, courseSectionMock);
380381
const newSectionButton = (await screen.findAllByRole('button', { name: 'New section' }))[0];
381-
await act(async () => fireEvent.click(newSectionButton));
382+
await user.click(newSectionButton);
382383

383-
elements = await findAllByTestId('section-card');
384+
elements = await screen.findAllByTestId('section-card');
384385
expect(elements.length).toBe(5);
385386
expect(window.HTMLElement.prototype.scrollIntoView).toHaveBeenCalled();
386387
});

src/course-outline/CourseOutline.tsx

Lines changed: 13 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import { useState, useEffect, useCallback } from 'react';
1+
import { useState, useEffect } from 'react';
22
import { useIntl } from '@edx/frontend-platform/i18n';
33
import {
44
Container,
55
Row,
66
TransitionReplace,
77
Toast,
8-
StandardModal,
98
Button,
109
ActionRow,
1110
} from '@openedx/paragon';
@@ -32,14 +31,11 @@ import { UnlinkModal } from '@src/generic/unlink-modal';
3231
import AlertMessage from '@src/generic/alert-message';
3332
import getPageHeadTitle from '@src/generic/utils';
3433
import CourseOutlineHeaderActionsSlot from '@src/plugin-slots/CourseOutlineHeaderActionsSlot';
35-
import { ContainerType } from '@src/generic/key-utils';
36-
import { LibraryAndComponentPicker, SelectedComponent } from '@src/library-authoring';
37-
import { ContentType } from '@src/library-authoring/routes';
3834
import { NOTIFICATION_MESSAGES } from '@src/constants';
39-
import { COMPONENT_TYPES } from '@src/generic/block-type-utils/constants';
4035
import { XBlock } from '@src/data/types';
4136
import { useCourseAuthoringContext } from '@src/CourseAuthoringContext';
4237
import LegacyLibContentBlockAlert from '@src/course-libraries/LegacyLibContentBlockAlert';
38+
import { ContainerType } from '@src/generic/key-utils';
4339
import {
4440
getCurrentItem,
4541
getProctoredExamsFlag,
@@ -75,14 +71,13 @@ const CourseOutline = () => {
7571
const location = useLocation();
7672
const {
7773
courseId,
78-
handleAddSubsectionFromLibrary,
79-
handleAddUnitFromLibrary,
80-
handleAddSectionFromLibrary,
81-
handleNewSectionSubmit,
74+
courseUsageKey,
75+
handleAddSubsection,
76+
handleAddUnit,
77+
handleAddSection,
8278
} = useCourseAuthoringContext();
8379

8480
const {
85-
courseUsageKey,
8681
courseName,
8782
savingStatus,
8883
statusBarData,
@@ -114,9 +109,6 @@ const CourseOutline = () => {
114109
headerNavigationsActions,
115110
openEnableHighlightsModal,
116111
closeEnableHighlightsModal,
117-
isAddLibrarySectionModalOpen,
118-
openAddLibrarySectionModal,
119-
closeAddLibrarySectionModal,
120112
handleEnableHighlightsSubmit,
121113
handleInternetConnectionFailed,
122114
handleOpenHighlightsModal,
@@ -243,16 +235,6 @@ const CourseOutline = () => {
243235
}
244236
};
245237

246-
const handleSelectLibrarySection = useCallback((selectedSection: SelectedComponent) => {
247-
handleAddSectionFromLibrary.mutateAsync({
248-
type: COMPONENT_TYPES.libraryV2,
249-
category: ContainerType.Chapter,
250-
parentLocator: courseUsageKey,
251-
libraryContentKey: selectedSection.usageKey,
252-
});
253-
closeAddLibrarySectionModal();
254-
}, [closeAddLibrarySectionModal, handleAddSectionFromLibrary.mutateAsync, courseId, courseUsageKey]);
255-
256238
useEffect(() => {
257239
setSections(sectionsList);
258240
}, [sectionsList]);
@@ -489,19 +471,19 @@ const CourseOutline = () => {
489471
</DraggableList>
490472
{courseActions.childAddable && (
491473
<OutlineAddChildButtons
492-
handleNewButtonClick={handleNewSectionSubmit}
493-
handleUseFromLibraryClick={openAddLibrarySectionModal}
494474
childType={ContainerType.Section}
475+
parentLocator={courseUsageKey}
476+
parentTitle={courseName}
495477
/>
496478
)}
497479
</>
498480
) : (
499481
<EmptyPlaceholder>
500482
{courseActions.childAddable && (
501483
<OutlineAddChildButtons
502-
handleNewButtonClick={handleNewSectionSubmit}
503-
handleUseFromLibraryClick={openAddLibrarySectionModal}
504484
childType={ContainerType.Section}
485+
parentLocator={courseUsageKey}
486+
parentTitle={courseName}
505487
btnVariant="primary"
506488
btnClasses="mt-1"
507489
/>
@@ -558,30 +540,15 @@ const CourseOutline = () => {
558540
close={closeUnlinkModal}
559541
onUnlinkSubmit={handleUnlinkItemSubmit}
560542
/>
561-
<StandardModal
562-
title={intl.formatMessage(messages.sectionPickerModalTitle)}
563-
isOpen={isAddLibrarySectionModalOpen}
564-
onClose={closeAddLibrarySectionModal}
565-
isOverflowVisible={false}
566-
size="xl"
567-
>
568-
<LibraryAndComponentPicker
569-
showOnlyPublished
570-
extraFilter={['block_type = "section"']}
571-
componentPickerMode="single"
572-
onComponentSelected={handleSelectLibrarySection}
573-
visibleTabs={[ContentType.sections]}
574-
/>
575-
</StandardModal>
576543
</Container>
577544
<div className="alert-toast">
578545
<ProcessingNotification
579546
// Show processing toast if any mutation is running
580547
isShow={
581548
isShowProcessingNotification
582-
|| handleAddUnitFromLibrary.isPending
583-
|| handleAddSubsectionFromLibrary.isPending
584-
|| handleAddSectionFromLibrary.isPending
549+
|| handleAddUnit.isPending
550+
|| handleAddSubsection.isPending
551+
|| handleAddSection.isPending
585552
}
586553
// HACK: Use saving as default title till we have a need for better messages
587554
title={processingNotificationTitle || NOTIFICATION_MESSAGES.saving}

0 commit comments

Comments
 (0)