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:
Peter Kulko
2024-04-24 21:27:29 +03:00
committed by GitHub
parent bef6796da4
commit 5686dee43b
69 changed files with 1621 additions and 404 deletions

View File

@@ -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

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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());
}
};
}