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:
@@ -8,6 +8,7 @@ export const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL;
|
||||
export const getCreateOrRerunCourseUrl = () => new URL('course/', getApiBaseUrl()).href;
|
||||
export const getCourseRerunUrl = (courseId) => new URL(`/api/contentstore/v1/course_rerun/${courseId}`, getApiBaseUrl()).href;
|
||||
export const getOrganizationsUrl = () => new URL('organizations', getApiBaseUrl()).href;
|
||||
export const getClipboardUrl = () => `${getApiBaseUrl()}/api/content-staging/v1/clipboard/`;
|
||||
export const getTagsCountApiUrl = (contentPattern) => new URL(`api/content_tagging/v1/object_tag_counts/${contentPattern}/?count_implicit`, getApiBaseUrl()).href;
|
||||
|
||||
/**
|
||||
@@ -45,6 +46,29 @@ export async function createOrRerunCourse(courseData) {
|
||||
return camelCaseObject(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves user's clipboard.
|
||||
* @returns {Promise<Object>} - A Promise that resolves clipboard data.
|
||||
*/
|
||||
export async function getClipboard() {
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.get(getClipboardUrl());
|
||||
|
||||
return camelCaseObject(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates user's clipboard.
|
||||
* @param {string} usageKey - The ID of the block.
|
||||
* @returns {Promise<Object>} - A Promise that resolves clipboard data.
|
||||
*/
|
||||
export async function updateClipboard(usageKey) {
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.post(getClipboardUrl(), { usage_key: usageKey });
|
||||
|
||||
return camelCaseObject(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tags count of multiple content by id separated by commas or a pattern using a '*' wildcard.
|
||||
* @param {string} contentPattern
|
||||
|
||||
@@ -5,3 +5,4 @@ export const getCourseData = (state) => state.generic.createOrRerunCourse.course
|
||||
export const getCourseRerunData = (state) => state.generic.createOrRerunCourse.courseRerunData;
|
||||
export const getRedirectUrlObj = (state) => state.generic.createOrRerunCourse.redirectUrlObj;
|
||||
export const getPostErrors = (state) => state.generic.createOrRerunCourse.postErrors;
|
||||
export const getClipboardData = (state) => state.generic.clipboardData;
|
||||
|
||||
@@ -18,6 +18,7 @@ const slice = createSlice({
|
||||
redirectUrlObj: {},
|
||||
postErrors: {},
|
||||
},
|
||||
clipboardData: null,
|
||||
},
|
||||
reducers: {
|
||||
fetchOrganizations: (state, { payload }) => {
|
||||
@@ -41,6 +42,9 @@ const slice = createSlice({
|
||||
updatePostErrors: (state, { payload }) => {
|
||||
state.createOrRerunCourse.postErrors = payload;
|
||||
},
|
||||
updateClipboardData: (state, { payload }) => {
|
||||
state.clipboardData = payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -52,6 +56,7 @@ export const {
|
||||
updateSavingStatus,
|
||||
updateCourseData,
|
||||
updateRedirectUrlObj,
|
||||
updateClipboardData,
|
||||
} = slice.actions;
|
||||
|
||||
export const {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { logError } from '@edx/frontend-platform/logging';
|
||||
|
||||
import { CLIPBOARD_STATUS, NOTIFICATION_MESSAGES } from '../../constants';
|
||||
import {
|
||||
hideProcessingNotification,
|
||||
showProcessingNotification,
|
||||
} from '../processing-notification/data/slice';
|
||||
import { RequestStatus } from '../../data/constants';
|
||||
import { createOrRerunCourse, getOrganizations, getCourseRerun } from './api';
|
||||
import {
|
||||
fetchOrganizations,
|
||||
updatePostErrors,
|
||||
@@ -7,7 +13,15 @@ import {
|
||||
updateRedirectUrlObj,
|
||||
updateCourseRerunData,
|
||||
updateSavingStatus,
|
||||
updateClipboardData,
|
||||
} from './slice';
|
||||
import {
|
||||
createOrRerunCourse,
|
||||
getOrganizations,
|
||||
getCourseRerun,
|
||||
updateClipboard,
|
||||
getClipboard,
|
||||
} from './api';
|
||||
|
||||
export function fetchOrganizationsQuery() {
|
||||
return async (dispatch) => {
|
||||
@@ -49,3 +63,33 @@ export function updateCreateOrRerunCourseQuery(courseData) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function copyToClipboard(usageKey) {
|
||||
const POLL_INTERVAL_MS = 1000; // Timeout duration for polling in milliseconds
|
||||
|
||||
return async (dispatch) => {
|
||||
dispatch(showProcessingNotification(NOTIFICATION_MESSAGES.copying));
|
||||
dispatch(updateSavingStatus({ status: RequestStatus.PENDING }));
|
||||
|
||||
try {
|
||||
let clipboardData = await updateClipboard(usageKey);
|
||||
|
||||
while (clipboardData.content?.status === CLIPBOARD_STATUS.loading) {
|
||||
// eslint-disable-next-line no-await-in-loop,no-promise-executor-return
|
||||
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
||||
clipboardData = await getClipboard(); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
|
||||
if (clipboardData.content?.status === CLIPBOARD_STATUS.ready) {
|
||||
dispatch(updateClipboardData(clipboardData));
|
||||
dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL }));
|
||||
} else {
|
||||
throw new Error(`Unexpected clipboard status "${clipboardData.content?.status}" in successful API response.`);
|
||||
}
|
||||
} catch (error) {
|
||||
logError('Error copying to clipboard:', error);
|
||||
} finally {
|
||||
dispatch(hideProcessingNotification());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user