feat: [FC-0044] Course unit - Copy/paste functionality (#884)
Implement copy/paste. Co-authored-by: monteri <36768631+monteri@users.noreply.github.com> Co-authored-by: ihor-romaniuk <ihor.romaniuk@raccoongang.com>
This commit is contained in:
@@ -11,7 +11,6 @@ export const getCourseUnitApiUrl = (itemId) => `${getStudioBaseUrl()}/xblock/con
|
||||
export const getXBlockBaseApiUrl = (itemId) => `${getStudioBaseUrl()}/xblock/${itemId}`;
|
||||
export const getCourseSectionVerticalApiUrl = (itemId) => `${getStudioBaseUrl()}/api/contentstore/v1/container_handler/${itemId}`;
|
||||
export const getCourseVerticalChildrenApiUrl = (itemId) => `${getStudioBaseUrl()}/api/contentstore/v1/container/vertical/${itemId}/children`;
|
||||
|
||||
export const postXBlockBaseApiUrl = () => `${getStudioBaseUrl()}/xblock/`;
|
||||
|
||||
/**
|
||||
@@ -60,13 +59,13 @@ export async function getCourseSectionVerticalData(unitId) {
|
||||
* @param {Object} options - The options for creating the XBlock.
|
||||
* @param {string} options.type - The type of the XBlock.
|
||||
* @param {string} [options.category] - The category of the XBlock. Defaults to the type if not provided.
|
||||
* @param {string} options.parentLocator - The parent locator of the XBlock.
|
||||
* @param {string} [options.displayName] - The display name for the XBlock.
|
||||
* @param {string} [options.boilerplate] - The boilerplate for the XBlock.
|
||||
* @returns {Promise<Object>} A Promise that resolves to the created XBlock data.
|
||||
* @param {string} options.parentLocator - The parent locator.
|
||||
* @param {string} [options.displayName] - The display name.
|
||||
* @param {string} [options.boilerplate] - The boilerplate.
|
||||
* @param {string} [options.stagedContent] - The staged content.
|
||||
*/
|
||||
export async function createCourseXblock({
|
||||
type, category, parentLocator, displayName, boilerplate,
|
||||
type, category, parentLocator, displayName, boilerplate, stagedContent,
|
||||
}) {
|
||||
const body = {
|
||||
type,
|
||||
@@ -74,6 +73,7 @@ export async function createCourseXblock({
|
||||
category: category || type,
|
||||
parent_locator: parentLocator,
|
||||
display_name: displayName,
|
||||
staged_content: stagedContent,
|
||||
};
|
||||
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
|
||||
import { RequestStatus } from '../../data/constants';
|
||||
|
||||
export const getCourseUnitData = (state) => state.courseUnit.unit;
|
||||
export const getCanEdit = (state) => state.courseUnit.canEdit;
|
||||
export const getStaticFileNotices = (state) => state.courseUnit.staticFileNotices;
|
||||
export const getCourseUnit = (state) => state.courseUnit;
|
||||
export const getSavingStatus = (state) => state.courseUnit.savingStatus;
|
||||
export const getLoadingStatus = (state) => state.courseUnit.loadingStatus;
|
||||
export const getSequenceStatus = (state) => state.courseUnit.sequenceStatus;
|
||||
@@ -7,3 +14,9 @@ export const getCourseSectionVertical = (state) => state.courseUnit.courseSectio
|
||||
export const getCourseId = (state) => state.courseDetail.courseId;
|
||||
export const getSequenceId = (state) => state.courseUnit.sequenceId;
|
||||
export const getCourseVerticalChildren = (state) => state.courseUnit.courseVerticalChildren;
|
||||
const getLoadingStatuses = (state) => state.courseUnit.loadingStatus;
|
||||
export const getIsLoading = createSelector(
|
||||
[getLoadingStatuses],
|
||||
loadingStatus => Object.values(loadingStatus)
|
||||
.some((status) => status === RequestStatus.IN_PROGRESS),
|
||||
);
|
||||
|
||||
@@ -9,6 +9,7 @@ const slice = createSlice({
|
||||
savingStatus: '',
|
||||
isQueryPending: false,
|
||||
isTitleEditFormOpen: false,
|
||||
canEdit: true,
|
||||
loadingStatus: {
|
||||
fetchUnitLoadingStatus: RequestStatus.IN_PROGRESS,
|
||||
courseSectionVerticalLoadingStatus: RequestStatus.IN_PROGRESS,
|
||||
@@ -16,7 +17,8 @@ const slice = createSlice({
|
||||
},
|
||||
unit: {},
|
||||
courseSectionVertical: {},
|
||||
courseVerticalChildren: {},
|
||||
courseVerticalChildren: { children: [], isPublished: true },
|
||||
staticFileNotices: {},
|
||||
},
|
||||
reducers: {
|
||||
fetchCourseItemSuccess: (state, { payload }) => {
|
||||
@@ -95,6 +97,9 @@ const slice = createSlice({
|
||||
}),
|
||||
};
|
||||
},
|
||||
fetchStaticFileNoticesSuccess: (state, { payload }) => {
|
||||
state.staticFileNotices = payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -115,6 +120,7 @@ export const {
|
||||
updateCourseVerticalChildrenLoadingStatus,
|
||||
deleteXBlock,
|
||||
duplicateXBlock,
|
||||
fetchStaticFileNoticesSuccess,
|
||||
} = slice.actions;
|
||||
|
||||
export const {
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
import { RequestStatus } from '../../data/constants';
|
||||
import { NOTIFICATION_MESSAGES } from '../../constants';
|
||||
import { updateModel, updateModels } from '../../generic/model-store';
|
||||
import { updateClipboardData } from '../../generic/data/slice';
|
||||
import {
|
||||
getCourseUnitData,
|
||||
editUnitDisplayName,
|
||||
@@ -32,6 +33,7 @@ import {
|
||||
updateQueryPendingStatus,
|
||||
deleteXBlock,
|
||||
duplicateXBlock,
|
||||
fetchStaticFileNoticesSuccess,
|
||||
} from './slice';
|
||||
import { getNotificationMessage } from './utils';
|
||||
|
||||
@@ -68,6 +70,9 @@ export function fetchCourseSectionVerticalData(courseId, sequenceId) {
|
||||
modelType: 'units',
|
||||
models: courseSectionVerticalData.units,
|
||||
}));
|
||||
dispatch(fetchStaticFileNoticesSuccess(JSON.parse(localStorage.getItem('staticFileNotices'))));
|
||||
localStorage.removeItem('staticFileNotices');
|
||||
dispatch(updateClipboardData(courseSectionVerticalData.userClipboard));
|
||||
dispatch(fetchSequenceSuccess({ sequenceId }));
|
||||
return true;
|
||||
} catch (error) {
|
||||
@@ -139,9 +144,14 @@ export function editCourseUnitVisibilityAndData(itemId, type, isVisible, groupAc
|
||||
export function createNewCourseXBlock(body, callback, blockId) {
|
||||
return async (dispatch) => {
|
||||
dispatch(updateLoadingCourseXblockStatus({ status: RequestStatus.IN_PROGRESS }));
|
||||
dispatch(showProcessingNotification(NOTIFICATION_MESSAGES.adding));
|
||||
dispatch(updateSavingStatus({ status: RequestStatus.PENDING }));
|
||||
|
||||
if (body.stagedContent) {
|
||||
dispatch(showProcessingNotification(NOTIFICATION_MESSAGES.pasting));
|
||||
} else {
|
||||
dispatch(showProcessingNotification(NOTIFICATION_MESSAGES.adding));
|
||||
}
|
||||
|
||||
try {
|
||||
await createCourseXblock(body).then(async (result) => {
|
||||
if (result) {
|
||||
@@ -150,6 +160,14 @@ export function createNewCourseXBlock(body, callback, blockId) {
|
||||
const courseSectionVerticalData = await getCourseSectionVerticalData(formattedResult.locator);
|
||||
dispatch(fetchCourseSectionVerticalDataSuccess(courseSectionVerticalData));
|
||||
}
|
||||
if (body.stagedContent) {
|
||||
localStorage.setItem('staticFileNotices', JSON.stringify(formattedResult.staticFileNotices));
|
||||
dispatch(fetchStaticFileNoticesSuccess(formattedResult.staticFileNotices));
|
||||
|
||||
if (body.parentLocator.includes('vertical')) {
|
||||
localStorage.removeItem('staticFileNotices');
|
||||
}
|
||||
}
|
||||
const courseVerticalChildrenData = await getCourseVerticalChildren(blockId);
|
||||
dispatch(updateCourseVerticalChildren(courseVerticalChildrenData));
|
||||
dispatch(hideProcessingNotification());
|
||||
@@ -162,8 +180,6 @@ export function createNewCourseXBlock(body, callback, blockId) {
|
||||
const courseUnit = await getCourseUnitData(currentBlockId);
|
||||
dispatch(fetchCourseItemSuccess(courseUnit));
|
||||
}
|
||||
const courseUnit = await getCourseUnitData(blockId);
|
||||
dispatch(fetchCourseItemSuccess(courseUnit));
|
||||
});
|
||||
} catch (error) {
|
||||
dispatch(hideProcessingNotification());
|
||||
@@ -195,6 +211,8 @@ export function deleteUnitItemQuery(itemId, xblockId) {
|
||||
try {
|
||||
await deleteUnitItem(xblockId);
|
||||
dispatch(deleteXBlock(xblockId));
|
||||
const { userClipboard } = await getCourseSectionVerticalData(itemId);
|
||||
dispatch(updateClipboardData(userClipboard));
|
||||
const courseUnit = await getCourseUnitData(itemId);
|
||||
dispatch(fetchCourseItemSuccess(courseUnit));
|
||||
dispatch(hideProcessingNotification());
|
||||
|
||||
Reference in New Issue
Block a user