feat: Add drag and drop support to subsections
feat: Update tests, fix bugs in drag and drop elements chore: address review feedback
This commit is contained in:
@@ -91,13 +91,14 @@ const CourseOutline = ({ courseId }) => {
|
||||
handleNewSubsectionSubmit,
|
||||
handleNewUnitSubmit,
|
||||
getUnitUrl,
|
||||
handleDragNDrop,
|
||||
handleSectionDragAndDrop,
|
||||
handleSubsectionDragAndDrop,
|
||||
handleVideoSharingOptionChange,
|
||||
} = useCourseOutline({ courseId });
|
||||
|
||||
const [sections, setSections] = useState(sectionsList);
|
||||
|
||||
const initialSections = [...sectionsList];
|
||||
let initialSections = [...sectionsList];
|
||||
|
||||
const {
|
||||
isShow: isShowProcessingNotification,
|
||||
@@ -105,7 +106,22 @@ const CourseOutline = ({ courseId }) => {
|
||||
} = useSelector(getProcessingNotification);
|
||||
|
||||
const finalizeSectionOrder = () => (newSections) => {
|
||||
handleDragNDrop(newSections.map((section) => section.id), () => {
|
||||
initialSections = [...sectionsList];
|
||||
handleSectionDragAndDrop(newSections.map(section => section.id), () => {
|
||||
setSections(() => initialSections);
|
||||
});
|
||||
};
|
||||
|
||||
const setSubsection = (index) => (updatedSubsection) => {
|
||||
const section = { ...sections[index] };
|
||||
section.childInfo = { ...section.childInfo };
|
||||
section.childInfo.children = updatedSubsection();
|
||||
setSections([...sections.slice(0, index), section, ...sections.slice(index + 1)]);
|
||||
};
|
||||
|
||||
const finalizeSubsectionOrder = (section) => () => (newSubsections) => {
|
||||
initialSections = [...sectionsList];
|
||||
handleSubsectionDragAndDrop(section.id, newSubsections.map(subsection => subsection.id), () => {
|
||||
setSections(() => initialSections);
|
||||
});
|
||||
};
|
||||
@@ -184,19 +200,19 @@ const CourseOutline = ({ courseId }) => {
|
||||
{sections.length ? (
|
||||
<>
|
||||
<DraggableList itemList={sections} setState={setSections} updateOrder={finalizeSectionOrder}>
|
||||
{sections.map((section) => (
|
||||
{sections.map((section, index) => (
|
||||
<SortableItem
|
||||
id={section.id}
|
||||
key={section.id}
|
||||
componentStyle={{
|
||||
background: 'white',
|
||||
borderRadius: '6px',
|
||||
padding: '1.75rem',
|
||||
marginBottom: '1.5rem',
|
||||
boxShadow: '0px 1px 5px #ADADAD',
|
||||
boxShadow: '0 0 .125rem rgba(0, 0, 0, .15), 0 0 .25rem rgba(0, 0, 0, .15)',
|
||||
}}
|
||||
>
|
||||
<SectionCard
|
||||
id={section.id}
|
||||
key={section.id}
|
||||
section={section}
|
||||
savingStatus={savingStatus}
|
||||
@@ -209,34 +225,51 @@ const CourseOutline = ({ courseId }) => {
|
||||
isSectionsExpanded={isSectionsExpanded}
|
||||
onNewSubsectionSubmit={handleNewSubsectionSubmit}
|
||||
>
|
||||
{section.childInfo.children.map((subsection) => (
|
||||
<SubsectionCard
|
||||
key={subsection.id}
|
||||
section={section}
|
||||
subsection={subsection}
|
||||
savingStatus={savingStatus}
|
||||
onOpenPublishModal={openPublishModal}
|
||||
onOpenDeleteModal={openDeleteModal}
|
||||
onEditSubmit={handleEditSubmit}
|
||||
onDuplicateSubmit={handleDuplicateSubsectionSubmit}
|
||||
onNewUnitSubmit={handleNewUnitSubmit}
|
||||
>
|
||||
{subsection.childInfo.children.map((unit) => (
|
||||
<UnitCard
|
||||
key={unit.id}
|
||||
unit={unit}
|
||||
subsection={subsection}
|
||||
<DraggableList
|
||||
itemList={section.childInfo.children}
|
||||
setState={setSubsection(index)}
|
||||
updateOrder={finalizeSubsectionOrder(section)}
|
||||
>
|
||||
{section.childInfo.children.map((subsection) => (
|
||||
<SortableItem
|
||||
id={subsection.id}
|
||||
key={subsection.id}
|
||||
componentStyle={{
|
||||
background: '#f8f7f6',
|
||||
padding: '1rem 1.5rem',
|
||||
marginBottom: '1.5rem',
|
||||
boxShadow: '0 0 .125rem rgba(0, 0, 0, .15), 0 0 .25rem rgba(0, 0, 0, .15)',
|
||||
}}
|
||||
>
|
||||
<SubsectionCard
|
||||
key={subsection.id}
|
||||
section={section}
|
||||
subsection={subsection}
|
||||
savingStatus={savingStatus}
|
||||
onOpenPublishModal={openPublishModal}
|
||||
onOpenDeleteModal={openDeleteModal}
|
||||
onEditSubmit={handleEditSubmit}
|
||||
onDuplicateSubmit={handleDuplicateUnitSubmit}
|
||||
getTitleLink={getUnitUrl}
|
||||
/>
|
||||
))}
|
||||
</SubsectionCard>
|
||||
))}
|
||||
onDuplicateSubmit={handleDuplicateSubsectionSubmit}
|
||||
onNewUnitSubmit={handleNewUnitSubmit}
|
||||
>
|
||||
{subsection.childInfo.children.map((unit) => (
|
||||
<UnitCard
|
||||
key={unit.id}
|
||||
unit={unit}
|
||||
subsection={subsection}
|
||||
section={section}
|
||||
savingStatus={savingStatus}
|
||||
onOpenPublishModal={openPublishModal}
|
||||
onOpenDeleteModal={openDeleteModal}
|
||||
onEditSubmit={handleEditSubmit}
|
||||
onDuplicateSubmit={handleDuplicateUnitSubmit}
|
||||
getTitleLink={getUnitUrl}
|
||||
/>
|
||||
))}
|
||||
</SubsectionCard>
|
||||
</SortableItem>
|
||||
))}
|
||||
</DraggableList>
|
||||
</SectionCard>
|
||||
</SortableItem>
|
||||
))}
|
||||
|
||||
@@ -17,14 +17,15 @@ import {
|
||||
getCourseBlockApiUrl,
|
||||
getCourseItemApiUrl,
|
||||
getXBlockBaseApiUrl,
|
||||
getChapterBlockApiUrl,
|
||||
} from './data/api';
|
||||
import { RequestStatus } from '../data/constants';
|
||||
import {
|
||||
configureCourseSectionQuery,
|
||||
fetchCourseBestPracticesQuery,
|
||||
fetchCourseLaunchQuery,
|
||||
fetchCourseOutlineIndexQuery,
|
||||
updateCourseSectionHighlightsQuery,
|
||||
setSectionOrderListQuery,
|
||||
} from './data/thunk';
|
||||
import initializeStore from '../store';
|
||||
import {
|
||||
@@ -210,7 +211,7 @@ describe('<CourseOutline />', () => {
|
||||
const { findAllByTestId } = render(<RootWrapper />);
|
||||
const [section] = await findAllByTestId('section-card');
|
||||
let subsections = await within(section).findAllByTestId('subsection-card');
|
||||
expect(subsections.length).toBe(1);
|
||||
expect(subsections.length).toBe(2);
|
||||
window.HTMLElement.prototype.getBoundingClientRect = jest.fn(() => ({
|
||||
top: 0,
|
||||
bottom: 4000,
|
||||
@@ -230,7 +231,7 @@ describe('<CourseOutline />', () => {
|
||||
});
|
||||
|
||||
subsections = await within(section).findAllByTestId('subsection-card');
|
||||
expect(subsections.length).toBe(2);
|
||||
expect(subsections.length).toBe(3);
|
||||
expect(window.HTMLElement.prototype.scrollIntoView).toBeCalled();
|
||||
});
|
||||
|
||||
@@ -525,7 +526,7 @@ describe('<CourseOutline />', () => {
|
||||
// check unit
|
||||
await checkDuplicateBtn(unit, subsectionElement, unitElement, 'unit', 2);
|
||||
// check subsection
|
||||
await checkDuplicateBtn(subsection, sectionElement, subsectionElement, 'subsection', 2);
|
||||
await checkDuplicateBtn(subsection, sectionElement, subsectionElement, 'subsection', 3);
|
||||
// check section
|
||||
await checkDuplicateBtn(section, null, sectionElement, 'section', 5);
|
||||
});
|
||||
@@ -689,46 +690,108 @@ describe('<CourseOutline />', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('check section list is ordered successfully', async () => {
|
||||
const { getAllByTestId } = render(<RootWrapper />);
|
||||
it('check that new section list is saved when dragged', async () => {
|
||||
const { getAllByRole } = render(<RootWrapper />);
|
||||
|
||||
const courseBlockId = courseOutlineIndexMock.courseStructure.id;
|
||||
let { children } = courseOutlineIndexMock.courseStructure.childInfo;
|
||||
children = children.splice(2, 0, children.splice(0, 1)[0]);
|
||||
await waitFor(async () => {
|
||||
const sectionsDraggers = await getAllByRole('button', { name: 'Drag to reorder' });
|
||||
|
||||
axiosMock
|
||||
.onPut(getCourseBlockApiUrl(courseBlockId), { children })
|
||||
.reply(200, { dummy: 'value' });
|
||||
axiosMock
|
||||
.onPut(getCourseBlockApiUrl(courseBlockId))
|
||||
.reply(200, { dummy: 'value' });
|
||||
|
||||
await executeThunk(setSectionOrderListQuery(courseBlockId, children, () => {}), store.dispatch);
|
||||
const section1 = store.getState().courseOutline.sectionsList[0].id;
|
||||
const draggableButton = sectionsDraggers[7];
|
||||
fireEvent.keyDown(draggableButton, { code: 'Space' });
|
||||
fireEvent.keyDown(draggableButton, { key: 'ArrowUp' });
|
||||
await act(async () => fireEvent.keyDown(draggableButton, { code: 'Space' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getAllByTestId('section-card')).toHaveLength(4);
|
||||
const newSections = getAllByTestId('section-card');
|
||||
for (let i; i < children.length; i++) {
|
||||
expect(children[i].id === newSections[i].id);
|
||||
}
|
||||
const saveStatus = store.getState().courseOutline.savingStatus;
|
||||
expect(saveStatus).toEqual(RequestStatus.SUCCESSFUL);
|
||||
|
||||
const section2 = store.getState().courseOutline.sectionsList[1].id;
|
||||
expect(section1).toBe(section2);
|
||||
});
|
||||
});
|
||||
|
||||
it('check section list is restored to original order when API call fails', async () => {
|
||||
const { getAllByTestId } = render(<RootWrapper />);
|
||||
const { getAllByRole } = render(<RootWrapper />);
|
||||
|
||||
const courseBlockId = courseOutlineIndexMock.courseStructure.id;
|
||||
const { children } = courseOutlineIndexMock.courseStructure.childInfo;
|
||||
const newChildren = children.splice(2, 0, children.splice(0, 1)[0]);
|
||||
await waitFor(async () => {
|
||||
const sectionsDraggers = await getAllByRole('button', { name: 'Drag to reorder' });
|
||||
|
||||
axiosMock
|
||||
.onPut(getCourseBlockApiUrl(courseBlockId), { children })
|
||||
.reply(500);
|
||||
axiosMock
|
||||
.onPut(getCourseBlockApiUrl(courseBlockId))
|
||||
.reply(500);
|
||||
|
||||
await executeThunk(setSectionOrderListQuery(courseBlockId, undefined, () => children), store.dispatch);
|
||||
const section1 = store.getState().courseOutline.sectionsList[0].id;
|
||||
const draggableButton = sectionsDraggers[6];
|
||||
fireEvent.keyDown(draggableButton, { code: 'Space' });
|
||||
fireEvent.keyDown(draggableButton, { key: 'ArrowUp' });
|
||||
await act(async () => fireEvent.keyDown(draggableButton, { code: 'Space' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getAllByTestId('section-card')).toHaveLength(4);
|
||||
const newSections = getAllByTestId('section-card');
|
||||
for (let i; i < children.length; i++) {
|
||||
expect(children[i].id === newSections[i].id);
|
||||
expect(newChildren[i].id !== newSections[i].id);
|
||||
}
|
||||
const saveStatus = store.getState().courseOutline.savingStatus;
|
||||
expect(saveStatus).toEqual(RequestStatus.FAILED);
|
||||
|
||||
const section1New = store.getState().courseOutline.sectionsList[0].id;
|
||||
expect(section1).toBe(section1New);
|
||||
});
|
||||
});
|
||||
|
||||
it('check that new subsection list is saved when dragged', async () => {
|
||||
const { findAllByTestId } = render(<RootWrapper />);
|
||||
|
||||
const courseBlockId = courseOutlineIndexMock.courseStructure.id;
|
||||
await waitFor(async () => {
|
||||
const [section] = await findAllByTestId('section-card');
|
||||
const subsectionsDraggers = within(section).getAllByRole('button', { name: 'Drag to reorder' });
|
||||
|
||||
axiosMock
|
||||
.onPut(getChapterBlockApiUrl(courseBlockId, store.getState().courseOutline.sectionsList[0].id))
|
||||
.reply(200, { dummy: 'value' });
|
||||
|
||||
const subsection1 = store.getState().courseOutline.sectionsList[0].childInfo.children[0].id;
|
||||
|
||||
// Move the second subsection up
|
||||
const draggableButton = subsectionsDraggers[1];
|
||||
fireEvent.keyDown(draggableButton, { code: 'Space' });
|
||||
fireEvent.keyDown(draggableButton, { key: 'ArrowUp' });
|
||||
await act(async () => fireEvent.keyDown(draggableButton, { code: 'Space' }));
|
||||
const saveStatus = store.getState().courseOutline.savingStatus;
|
||||
expect(saveStatus).toEqual(RequestStatus.SUCCESSFUL);
|
||||
|
||||
const subsection2 = store.getState().courseOutline.sectionsList[0].childInfo.children[1].id;
|
||||
expect(subsection1).toBe(subsection2);
|
||||
});
|
||||
});
|
||||
|
||||
it('check that new subsection list is restored to original order when API call fails', async () => {
|
||||
const { findAllByTestId } = render(<RootWrapper />);
|
||||
|
||||
const courseBlockId = courseOutlineIndexMock.courseStructure.id;
|
||||
await waitFor(async () => {
|
||||
const [section] = await findAllByTestId('section-card');
|
||||
const subsectionsDraggers = within(section).getAllByRole('button', { name: 'Drag to reorder' });
|
||||
|
||||
axiosMock
|
||||
.onPut(getChapterBlockApiUrl(courseBlockId, store.getState().courseOutline.sectionsList[0].id))
|
||||
.reply(500);
|
||||
|
||||
const subsection1 = store.getState().courseOutline.sectionsList[0].childInfo.children[0].id;
|
||||
|
||||
// Move the second subsection up
|
||||
const draggableButton = subsectionsDraggers[1];
|
||||
fireEvent.keyDown(draggableButton, { code: 'Space' });
|
||||
fireEvent.keyDown(draggableButton, { key: 'ArrowUp' });
|
||||
await act(async () => fireEvent.keyDown(draggableButton, { code: 'Space' }));
|
||||
|
||||
const saveStatus = store.getState().courseOutline.savingStatus;
|
||||
expect(saveStatus).toEqual(RequestStatus.FAILED);
|
||||
|
||||
const subsection1New = store.getState().courseOutline.sectionsList[0].childInfo.children[0].id;
|
||||
expect(subsection1).toBe(subsection1New);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -318,6 +318,107 @@ module.exports = {
|
||||
selectedGroupsLabel: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@7f75de8dcc261249250b71925f49810f',
|
||||
display_name: 'Sample Subsection',
|
||||
category: 'sequential',
|
||||
has_children: true,
|
||||
edited_on: 'Dec 05, 2023 at 10:35 UTC',
|
||||
published: true,
|
||||
published_on: 'Dec 05, 2023 at 10:35 UTC',
|
||||
studio_url: '/course/course-v1:edX+DemoX+Demo_Course?show=block-v1%3AedX%2BDemoX%2BDemo_Course%2Btype%40sequential%2Bblock%407f75de8dcc261249250b71925f49810f',
|
||||
released_to_students: true,
|
||||
release_date: 'Feb 05, 2013 at 05:00 UTC',
|
||||
visibility_state: 'live',
|
||||
has_explicit_staff_lock: false,
|
||||
start: '2013-02-05T05:00:00Z',
|
||||
graded: false,
|
||||
due_date: '',
|
||||
due: null,
|
||||
relative_weeks_due: null,
|
||||
format: null,
|
||||
course_graders: [
|
||||
'Homework',
|
||||
'Exam',
|
||||
],
|
||||
has_changes: false,
|
||||
actions: {
|
||||
deletable: true,
|
||||
draggable: true,
|
||||
childAddable: true,
|
||||
duplicable: true,
|
||||
},
|
||||
explanatory_message: null,
|
||||
group_access: {},
|
||||
user_partitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
show_correctness: 'always',
|
||||
hide_after_due: false,
|
||||
is_proctored_exam: false,
|
||||
was_exam_ever_linked_with_external: false,
|
||||
online_proctoring_rules: '',
|
||||
is_practice_exam: false,
|
||||
is_onboarding_exam: false,
|
||||
is_time_limited: false,
|
||||
exam_review_rules: '',
|
||||
default_time_limit_minutes: null,
|
||||
proctoring_exam_configuration_link: null,
|
||||
supports_onboarding: false,
|
||||
show_review_rules: true,
|
||||
child_info: {
|
||||
category: 'vertical',
|
||||
display_name: 'Unit',
|
||||
children: [],
|
||||
},
|
||||
ancestor_has_staff_lock: false,
|
||||
staff_only_message: false,
|
||||
enable_copy_paste_units: false,
|
||||
has_partition_group_components: false,
|
||||
user_partition_info: {
|
||||
selectable_partitions: [
|
||||
{
|
||||
id: 50,
|
||||
name: 'Enrollment Track Groups',
|
||||
scheme: 'enrollment_track',
|
||||
groups: [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Verified Certificate',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Audit',
|
||||
selected: false,
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selected_partition_index: -1,
|
||||
selected_groups_label: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
ancestorHasStaffLock: false,
|
||||
|
||||
@@ -24,6 +24,12 @@ export const getCourseBlockApiUrl = (courseId) => {
|
||||
return `${getApiBaseUrl()}/xblock/block-v1:${formattedCourseId}+type@course+block@course`;
|
||||
};
|
||||
|
||||
export const getChapterBlockApiUrl = (courseId, chapterId) => {
|
||||
const formattedCourseId = courseId.split('course-v1:')[1];
|
||||
const formattedChapterId = chapterId.split('@').slice(-1)[0];
|
||||
return `${getApiBaseUrl()}/xblock/block-v1:${formattedCourseId}+type@chapter+block@${formattedChapterId}`;
|
||||
};
|
||||
|
||||
export const getCourseReindexApiUrl = (reindexLink) => `${getApiBaseUrl()}${reindexLink}`;
|
||||
export const getXBlockBaseApiUrl = () => `${getApiBaseUrl()}/xblock/`;
|
||||
export const getCourseItemApiUrl = (itemId) => `${getXBlockBaseApiUrl()}${itemId}`;
|
||||
@@ -312,6 +318,22 @@ export async function setSectionOrderList(courseId, children) {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set order for the list of the subsections
|
||||
* @param {string} courseId
|
||||
* @param {string} sectionId
|
||||
* @param {Array<string>} children list of sections id's
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export async function setSubsectionOrderList(courseId, sectionId, children) {
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.put(getChapterBlockApiUrl(courseId, sectionId), {
|
||||
children,
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set video sharing setting
|
||||
* @param {string} courseId
|
||||
|
||||
@@ -85,6 +85,13 @@ const slice = createSlice({
|
||||
|
||||
state.sectionsList = [...sectionsList];
|
||||
},
|
||||
reorderSubsectionList: (state, { payload }) => {
|
||||
const { sectionId, subsectionListIds } = payload;
|
||||
const sections = [...state.sectionsList];
|
||||
const i = sections.findIndex(section => section.id === sectionId);
|
||||
sections[i].childInfo.children.sort((a, b) => subsectionListIds.indexOf(a.id) - subsectionListIds.indexOf(b.id));
|
||||
state.sectionsList = [...sections];
|
||||
},
|
||||
setCurrentSection: (state, { payload }) => {
|
||||
state.currentSection = payload;
|
||||
},
|
||||
@@ -172,6 +179,7 @@ export const {
|
||||
deleteUnit,
|
||||
duplicateSection,
|
||||
reorderSectionList,
|
||||
reorderSubsectionList,
|
||||
} = slice.actions;
|
||||
|
||||
export const {
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
updateCourseSectionHighlights,
|
||||
setSectionOrderList,
|
||||
setVideoSharingOption,
|
||||
setSubsectionOrderList,
|
||||
} from './api';
|
||||
import {
|
||||
addSection,
|
||||
@@ -43,6 +44,7 @@ import {
|
||||
deleteUnit,
|
||||
duplicateSection,
|
||||
reorderSectionList,
|
||||
reorderSubsectionList,
|
||||
} from './slice';
|
||||
|
||||
export function fetchCourseOutlineIndexQuery(courseId) {
|
||||
@@ -442,15 +444,36 @@ export function addNewUnitQuery(parentLocator, callback) {
|
||||
};
|
||||
}
|
||||
|
||||
export function setSectionOrderListQuery(courseId, newListId, restoreCallback) {
|
||||
export function setSectionOrderListQuery(courseId, sectionListIds, restoreCallback) {
|
||||
return async (dispatch) => {
|
||||
dispatch(updateSavingStatus({ status: RequestStatus.PENDING }));
|
||||
dispatch(showProcessingNotification(NOTIFICATION_MESSAGES.saving));
|
||||
|
||||
try {
|
||||
await setSectionOrderList(courseId, newListId).then(async (result) => {
|
||||
await setSectionOrderList(courseId, sectionListIds).then(async (result) => {
|
||||
if (result) {
|
||||
dispatch(reorderSectionList(newListId));
|
||||
dispatch(reorderSectionList(sectionListIds));
|
||||
dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL }));
|
||||
dispatch(hideProcessingNotification());
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
restoreCallback();
|
||||
dispatch(hideProcessingNotification());
|
||||
dispatch(updateSavingStatus({ status: RequestStatus.FAILED }));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function setSubsectionOrderListQuery(courseId, sectionId, subsectionListIds, restoreCallback) {
|
||||
return async (dispatch) => {
|
||||
dispatch(updateSavingStatus({ status: RequestStatus.PENDING }));
|
||||
dispatch(showProcessingNotification(NOTIFICATION_MESSAGES.saving));
|
||||
|
||||
try {
|
||||
await setSubsectionOrderList(courseId, sectionId, subsectionListIds).then(async (result) => {
|
||||
if (result) {
|
||||
dispatch(reorderSubsectionList({ sectionId, subsectionListIds }));
|
||||
dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL }));
|
||||
dispatch(hideProcessingNotification());
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
configureCourseSectionQuery,
|
||||
setSectionOrderListQuery,
|
||||
setVideoSharingOptionQuery,
|
||||
setSubsectionOrderListQuery,
|
||||
} from './data/thunk';
|
||||
|
||||
const useCourseOutline = ({ courseId }) => {
|
||||
@@ -183,8 +184,12 @@ const useCourseOutline = ({ courseId }) => {
|
||||
dispatch(duplicateUnitQuery(currentItem.id, currentSubsection.id, currentSection.id));
|
||||
};
|
||||
|
||||
const handleDragNDrop = (newListId, restoreCallback) => {
|
||||
dispatch(setSectionOrderListQuery(courseId, newListId, restoreCallback));
|
||||
const handleSectionDragAndDrop = (sectionListIds, restoreCallback) => {
|
||||
dispatch(setSectionOrderListQuery(courseId, sectionListIds, restoreCallback));
|
||||
};
|
||||
|
||||
const handleSubsectionDragAndDrop = (sectionId, subsectionListIds, restoreCallback) => {
|
||||
dispatch(setSubsectionOrderListQuery(courseId, sectionId, subsectionListIds, restoreCallback));
|
||||
};
|
||||
|
||||
const handleVideoSharingOptionChange = (value) => {
|
||||
@@ -250,7 +255,8 @@ const useCourseOutline = ({ courseId }) => {
|
||||
getUnitUrl,
|
||||
openUnitPage,
|
||||
handleNewUnitSubmit,
|
||||
handleDragNDrop,
|
||||
handleSectionDragAndDrop,
|
||||
handleSubsectionDragAndDrop,
|
||||
handleVideoSharingOptionChange,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
.subsection-card {
|
||||
@include pgn-box-shadow(1, "centered");
|
||||
|
||||
padding: $spacer 2rem;
|
||||
margin-bottom: 1.5rem;
|
||||
background: $light-200;
|
||||
|
||||
.subsection-card__content {
|
||||
margin: $spacer;
|
||||
}
|
||||
flex-grow: 1;
|
||||
|
||||
.subsection-card__units {
|
||||
padding-top: $spacer;
|
||||
margin-top: $spacer;
|
||||
margin-right: -2.75rem;
|
||||
}
|
||||
|
||||
.item-card-header__badge-status {
|
||||
|
||||
Reference in New Issue
Block a user