diff --git a/.gitignore b/.gitignore
index 925d1768a4..810bfdb938 100755
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
.DS_Store
.eslintcache
.idea
+.run
node_modules
npm-debug.log
coverage
diff --git a/src/course-unit/CourseUnit.test.jsx b/src/course-unit/CourseUnit.test.jsx
index 6d64615de0..1fae368da7 100644
--- a/src/course-unit/CourseUnit.test.jsx
+++ b/src/course-unit/CourseUnit.test.jsx
@@ -243,7 +243,7 @@ describe('', () => {
);
simulatePostMessageEvent(messageTypes.deleteXBlock, {
- id: courseVerticalChildrenMock.children[0].block_id,
+ usageId: courseVerticalChildrenMock.children[0].block_id,
});
expect(getByText(/Delete this component?/i)).toBeInTheDocument();
@@ -257,10 +257,10 @@ describe('', () => {
const deleteButton = getAllByRole('button', { name: /Delete/i })
.find(({ classList }) => classList.contains('btn-primary'));
- userEvent.click(cancelButton);
+ expect(cancelButton).toBeInTheDocument();
simulatePostMessageEvent(messageTypes.deleteXBlock, {
- id: courseVerticalChildrenMock.children[0].block_id,
+ usageId: courseVerticalChildrenMock.children[0].block_id,
});
expect(getByRole('dialog')).toBeInTheDocument();
@@ -296,8 +296,12 @@ describe('', () => {
axiosMock
.onDelete(getXBlockBaseApiUrl(courseVerticalChildrenMock.children[0].block_id))
- .replyOnce(200, { dummy: 'value' });
- await executeThunk(deleteUnitItemQuery(courseId, blockId), store.dispatch);
+ .reply(200, { dummy: 'value' });
+ await executeThunk(deleteUnitItemQuery(
+ courseId,
+ courseVerticalChildrenMock.children[0].block_id,
+ simulatePostMessageEvent,
+ ), store.dispatch);
const updatedCourseVerticalChildren = courseVerticalChildrenMock.children.filter(
child => child.block_id !== courseVerticalChildrenMock.children[0].block_id,
@@ -1617,6 +1621,8 @@ describe('', () => {
callbackFn: requestData.callbackFn,
}), store.dispatch);
+ simulatePostMessageEvent(messageTypes.rollbackMovedXBlock, { locator: requestData.sourceLocator });
+
const dismissButton = queryByRole('button', {
name: /dismiss/i, hidden: true,
});
diff --git a/src/course-unit/add-component/AddComponent.jsx b/src/course-unit/add-component/AddComponent.jsx
index 70962a7ac6..ac1a815dd4 100644
--- a/src/course-unit/add-component/AddComponent.jsx
+++ b/src/course-unit/add-component/AddComponent.jsx
@@ -61,6 +61,7 @@ const AddComponent = ({ blockId, handleCreateNewCourseXBlock }) => {
case COMPONENT_TYPES.problem:
case COMPONENT_TYPES.video:
handleCreateNewCourseXBlock({ type, parentLocator: blockId }, ({ courseKey, locator }) => {
+ localStorage.setItem('modalEditLastYPosition', window.scrollY);
navigate(`/course/${courseKey}/editor/${type}/${locator}`);
});
break;
diff --git a/src/course-unit/constants.js b/src/course-unit/constants.js
index 37c3609005..487dba6589 100644
--- a/src/course-unit/constants.js
+++ b/src/course-unit/constants.js
@@ -52,14 +52,20 @@ export const messageTypes = {
videoFullScreen: 'plugin.videoFullScreen',
refreshXBlock: 'refreshXBlock',
showMoveXBlockModal: 'showMoveXBlockModal',
+ completeXBlockMoving: 'completeXBlockMoving',
+ rollbackMovedXBlock: 'rollbackMovedXBlock',
showMultipleComponentPicker: 'showMultipleComponentPicker',
addSelectedComponentsToBank: 'addSelectedComponentsToBank',
showXBlockLibraryChangesPreview: 'showXBlockLibraryChangesPreview',
copyXBlock: 'copyXBlock',
manageXBlockAccess: 'manageXBlockAccess',
+ completeManageXBlockAccess: 'completeManageXBlockAccess',
deleteXBlock: 'deleteXBlock',
+ completeXBlockDeleting: 'completeXBlockDeleting',
duplicateXBlock: 'duplicateXBlock',
- refreshXBlockPositions: 'refreshPositions',
+ completeXBlockDuplicating: 'completeXBlockDuplicating',
newXBlockEditor: 'newXBlockEditor',
toggleCourseXBlockDropdown: 'toggleCourseXBlockDropdown',
+ addXBlock: 'addXBlock',
+ scrollToXBlock: 'scrollToXBlock',
};
diff --git a/src/course-unit/data/thunk.js b/src/course-unit/data/thunk.js
index 8f560e75a5..464111ed27 100644
--- a/src/course-unit/data/thunk.js
+++ b/src/course-unit/data/thunk.js
@@ -9,6 +9,7 @@ import { RequestStatus } from '../../data/constants';
import { NOTIFICATION_MESSAGES } from '../../constants';
import { updateModel, updateModels } from '../../generic/model-store';
import { updateClipboardData } from '../../generic/data/slice';
+import { messageTypes } from '../constants';
import {
getCourseUnitData,
editUnitDisplayName,
@@ -126,6 +127,7 @@ export function editCourseUnitVisibilityAndData(
isVisible,
groupAccess,
isDiscussionEnabled,
+ callback,
blockId = itemId,
) {
return async (dispatch) => {
@@ -143,6 +145,9 @@ export function editCourseUnitVisibilityAndData(
isDiscussionEnabled,
).then(async (result) => {
if (result) {
+ if (callback) {
+ callback();
+ }
const courseUnit = await getCourseUnitData(blockId);
dispatch(fetchCourseItemSuccess(courseUnit));
const courseVerticalChildrenData = await getCourseVerticalChildren(blockId);
@@ -158,11 +163,8 @@ export function editCourseUnitVisibilityAndData(
};
}
-export function createNewCourseXBlock(body, callback, blockId) {
+export function createNewCourseXBlock(body, callback, blockId, sendMessageToIframe) {
return async (dispatch) => {
- dispatch(updateLoadingCourseXblockStatus({ status: RequestStatus.IN_PROGRESS }));
- dispatch(updateSavingStatus({ status: RequestStatus.PENDING }));
-
if (body.stagedContent) {
dispatch(showProcessingNotification(NOTIFICATION_MESSAGES.pasting));
} else {
@@ -188,10 +190,10 @@ export function createNewCourseXBlock(body, callback, blockId) {
const courseVerticalChildrenData = await getCourseVerticalChildren(blockId);
dispatch(updateCourseVerticalChildren(courseVerticalChildrenData));
dispatch(hideProcessingNotification());
- dispatch(updateLoadingCourseXblockStatus({ status: RequestStatus.SUCCESSFUL }));
- dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL }));
if (callback) {
callback(result);
+ } else {
+ sendMessageToIframe(messageTypes.addXBlock, { data: result });
}
const currentBlockId = body.category === 'vertical' ? formattedResult.locator : blockId;
const courseUnit = await getCourseUnitData(currentBlockId);
@@ -220,13 +222,14 @@ export function fetchCourseVerticalChildrenData(itemId) {
};
}
-export function deleteUnitItemQuery(itemId, xblockId) {
+export function deleteUnitItemQuery(itemId, xblockId, sendMessageToIframe) {
return async (dispatch) => {
dispatch(updateSavingStatus({ status: RequestStatus.PENDING }));
dispatch(showProcessingNotification(NOTIFICATION_MESSAGES.deleting));
try {
await deleteUnitItem(xblockId);
+ sendMessageToIframe(messageTypes.completeXBlockDeleting, null);
const { userClipboard } = await getCourseSectionVerticalData(itemId);
dispatch(updateClipboardData(userClipboard));
const courseUnit = await getCourseUnitData(itemId);
@@ -240,13 +243,14 @@ export function deleteUnitItemQuery(itemId, xblockId) {
};
}
-export function duplicateUnitItemQuery(itemId, xblockId) {
+export function duplicateUnitItemQuery(itemId, xblockId, callback) {
return async (dispatch) => {
dispatch(updateSavingStatus({ status: RequestStatus.PENDING }));
dispatch(showProcessingNotification(NOTIFICATION_MESSAGES.duplicating));
try {
- await duplicateUnitItem(itemId, xblockId);
+ const { courseKey, locator } = await duplicateUnitItem(itemId, xblockId);
+ callback(courseKey, locator);
const courseUnit = await getCourseUnitData(itemId);
dispatch(fetchCourseItemSuccess(courseUnit));
dispatch(hideProcessingNotification());
@@ -300,9 +304,13 @@ export function patchUnitItemQuery({
dispatch(updateMovedXBlockParams(xBlockParams));
dispatch(updateCourseOutlineInfo({}));
dispatch(updateCourseOutlineInfoLoadingStatus({ status: RequestStatus.IN_PROGRESS }));
- const courseUnit = await getCourseUnitData(currentParentLocator);
- dispatch(fetchCourseItemSuccess(courseUnit));
- callbackFn();
+ try {
+ const courseUnit = await getCourseUnitData(currentParentLocator);
+ dispatch(fetchCourseItemSuccess(courseUnit));
+ } catch (error) {
+ handleResponseErrors(error, dispatch, updateSavingStatus);
+ }
+ callbackFn(sourceLocator);
} catch (error) {
handleResponseErrors(error, dispatch, updateSavingStatus);
} finally {
diff --git a/src/course-unit/header-title/HeaderTitle.jsx b/src/course-unit/header-title/HeaderTitle.jsx
index bb3cd3a72c..336d986fab 100644
--- a/src/course-unit/header-title/HeaderTitle.jsx
+++ b/src/course-unit/header-title/HeaderTitle.jsx
@@ -11,8 +11,6 @@ import {
import ConfigureModal from '../../generic/configure-modal/ConfigureModal';
import { getCourseUnitData } from '../data/selectors';
import { updateQueryPendingStatus } from '../data/slice';
-import { messageTypes } from '../constants';
-import { useIframe } from '../context/hooks';
import messages from './messages';
const HeaderTitle = ({
@@ -28,15 +26,9 @@ const HeaderTitle = ({
const currentItemData = useSelector(getCourseUnitData);
const [isConfigureModalOpen, openConfigureModal, closeConfigureModal] = useToggle(false);
const { selectedPartitionIndex, selectedGroupsLabel } = currentItemData.userPartitionInfo;
- const { sendMessageToIframe } = useIframe();
const onConfigureSubmit = (...arg) => {
handleConfigureSubmit(currentItemData.id, ...arg, closeConfigureModal);
- // TODO: this artificial delay is a temporary solution
- // to ensure the iframe content is properly refreshed.
- setTimeout(() => {
- sendMessageToIframe(messageTypes.refreshXBlock, null);
- }, 1000);
};
const getVisibilityMessage = () => {
diff --git a/src/course-unit/header-title/HeaderTitle.test.jsx b/src/course-unit/header-title/HeaderTitle.test.jsx
index 4383fcf6ca..314b0c25a8 100644
--- a/src/course-unit/header-title/HeaderTitle.test.jsx
+++ b/src/course-unit/header-title/HeaderTitle.test.jsx
@@ -60,11 +60,9 @@ describe('', () => {
it('render HeaderTitle component correctly', () => {
const { getByText, getByRole } = renderComponent();
- waitFor(() => {
- expect(getByText(unitTitle)).toBeInTheDocument();
- expect(getByRole('button', { name: messages.altButtonEdit.defaultMessage })).toBeInTheDocument();
- expect(getByRole('button', { name: messages.altButtonSettings.defaultMessage })).toBeInTheDocument();
- });
+ expect(getByText(unitTitle)).toBeInTheDocument();
+ expect(getByRole('button', { name: messages.altButtonEdit.defaultMessage })).toBeInTheDocument();
+ expect(getByRole('button', { name: messages.altButtonSettings.defaultMessage })).toBeInTheDocument();
});
it('render HeaderTitle with open edit form', () => {
@@ -72,41 +70,35 @@ describe('', () => {
isTitleEditFormOpen: true,
});
- waitFor(() => {
- expect(getByRole('textbox', { name: messages.ariaLabelButtonEdit.defaultMessage })).toBeInTheDocument();
- expect(getByRole('textbox', { name: messages.ariaLabelButtonEdit.defaultMessage })).toHaveValue(unitTitle);
- expect(getByRole('button', { name: messages.altButtonEdit.defaultMessage })).toBeInTheDocument();
- expect(getByRole('button', { name: messages.altButtonSettings.defaultMessage })).toBeInTheDocument();
- });
+ expect(getByRole('textbox', { name: messages.ariaLabelButtonEdit.defaultMessage })).toBeInTheDocument();
+ expect(getByRole('textbox', { name: messages.ariaLabelButtonEdit.defaultMessage })).toHaveValue(unitTitle);
+ expect(getByRole('button', { name: messages.altButtonEdit.defaultMessage })).toBeInTheDocument();
+ expect(getByRole('button', { name: messages.altButtonSettings.defaultMessage })).toBeInTheDocument();
});
it('calls toggle edit title form by clicking on Edit button', () => {
const { getByRole } = renderComponent();
- waitFor(() => {
- const editTitleButton = getByRole('button', { name: messages.altButtonEdit.defaultMessage });
- userEvent.click(editTitleButton);
- expect(handleTitleEdit).toHaveBeenCalledTimes(1);
- });
+ const editTitleButton = getByRole('button', { name: messages.altButtonEdit.defaultMessage });
+ userEvent.click(editTitleButton);
+ expect(handleTitleEdit).toHaveBeenCalledTimes(1);
});
- it('calls saving title by clicking outside or press Enter key', async () => {
+ it('calls saving title by clicking outside or press Enter key', () => {
const { getByRole } = renderComponent({
isTitleEditFormOpen: true,
});
- waitFor(() => {
- const titleField = getByRole('textbox', { name: messages.ariaLabelButtonEdit.defaultMessage });
- userEvent.type(titleField, ' 1');
- expect(titleField).toHaveValue(`${unitTitle} 1`);
- userEvent.click(document.body);
- expect(handleTitleEditSubmit).toHaveBeenCalledTimes(1);
-
- userEvent.click(titleField);
- userEvent.type(titleField, ' 2[Enter]');
- expect(titleField).toHaveValue(`${unitTitle} 1 2`);
- expect(handleTitleEditSubmit).toHaveBeenCalledTimes(2);
- });
+ const titleField = getByRole('textbox', { name: messages.ariaLabelButtonEdit.defaultMessage });
+ userEvent.type(titleField, ' 1');
+ expect(titleField).toHaveValue(`${unitTitle} 1`);
+ userEvent.click(document.body);
+ expect(handleTitleEditSubmit).toHaveBeenCalledTimes(1);
+
+ userEvent.click(titleField);
+ userEvent.type(titleField, ' 2[Enter]');
+ expect(titleField).toHaveValue(`${unitTitle} 1 2`);
+ expect(handleTitleEditSubmit).toHaveBeenCalledTimes(2);
});
it('displays a visibility message with the selected groups for the unit', async () => {
@@ -125,7 +117,7 @@ describe('', () => {
const visibilityMessage = messages.definedVisibilityMessage.defaultMessage
.replace('{selectedGroupsLabel}', 'Visibility group 1');
- waitFor(() => {
+ await waitFor(() => {
expect(getByText(visibilityMessage)).toBeInTheDocument();
});
});
@@ -140,8 +132,8 @@ describe('', () => {
await executeThunk(fetchCourseUnitQuery(blockId), store.dispatch);
const { getByText } = renderComponent();
- waitFor(() => {
- expect(getByText(messages.someVisibilityMessage.defaultMessage)).toBeInTheDocument();
+ await waitFor(() => {
+ expect(getByText(messages.commonVisibilityMessage.defaultMessage)).toBeInTheDocument();
});
});
});
diff --git a/src/course-unit/hooks.jsx b/src/course-unit/hooks.jsx
index 11731cc2ad..d28aaa600f 100644
--- a/src/course-unit/hooks.jsx
+++ b/src/course-unit/hooks.jsx
@@ -84,6 +84,7 @@ export const useCourseUnit = ({ courseId, blockId }) => {
isVisible,
groupAccess,
isDiscussionEnabled,
+ () => sendMessageToIframe(messageTypes.completeManageXBlockAccess, null),
blockId,
));
closeModalFn();
@@ -113,15 +114,19 @@ export const useCourseUnit = ({ courseId, blockId }) => {
};
const handleCreateNewCourseXBlock = (body, callback) => (
- dispatch(createNewCourseXBlock(body, callback, blockId))
+ dispatch(createNewCourseXBlock(body, callback, blockId, sendMessageToIframe))
);
const unitXBlockActions = {
handleDelete: (XBlockId) => {
- dispatch(deleteUnitItemQuery(blockId, XBlockId));
+ dispatch(deleteUnitItemQuery(blockId, XBlockId, sendMessageToIframe));
},
handleDuplicate: (XBlockId) => {
- dispatch(duplicateUnitItemQuery(blockId, XBlockId));
+ dispatch(duplicateUnitItemQuery(
+ blockId,
+ XBlockId,
+ (courseKey, locator) => sendMessageToIframe(messageTypes.completeXBlockDuplicating, { courseKey, locator }),
+ ));
},
};
@@ -136,7 +141,7 @@ export const useCourseUnit = ({ courseId, blockId }) => {
currentParentLocator,
isMoving: false,
callbackFn: () => {
- sendMessageToIframe(messageTypes.refreshXBlock, null);
+ sendMessageToIframe(messageTypes.rollbackMovedXBlock, { locator: sourceLocator });
window.scrollTo({ top: 0, behavior: 'smooth' });
},
}));
diff --git a/src/course-unit/move-modal/hooks.tsx b/src/course-unit/move-modal/hooks.tsx
index 69ad13470c..d21014e3bb 100644
--- a/src/course-unit/move-modal/hooks.tsx
+++ b/src/course-unit/move-modal/hooks.tsx
@@ -184,8 +184,8 @@ export const useMoveModal = ({
title: state.sourceXBlockInfo.current.displayName,
currentParentLocator: blockId,
isMoving: true,
- callbackFn: () => {
- sendMessageToIframe(messageTypes.refreshXBlock, null);
+ callbackFn: (sourceLocator: string) => {
+ sendMessageToIframe(messageTypes.completeXBlockMoving, { locator: sourceLocator });
closeModal();
window.scrollTo({ top: 0, behavior: 'smooth' });
},
diff --git a/src/course-unit/sidebar/PublishControls.jsx b/src/course-unit/sidebar/PublishControls.jsx
index 0ef08baf28..d5076aa838 100644
--- a/src/course-unit/sidebar/PublishControls.jsx
+++ b/src/course-unit/sidebar/PublishControls.jsx
@@ -35,12 +35,14 @@ const PublishControls = ({ blockId }) => {
const handleCourseUnitDiscardChanges = () => {
closeDiscardModal();
- dispatch(editCourseUnitVisibilityAndData(blockId, PUBLISH_TYPES.discardChanges));
- // TODO: this artificial delay is a temporary solution
- // to ensure the iframe content is properly refreshed.
- setTimeout(() => {
- sendMessageToIframe(messageTypes.refreshXBlock, null);
- }, 1000);
+ dispatch(editCourseUnitVisibilityAndData(
+ blockId,
+ PUBLISH_TYPES.discardChanges,
+ null,
+ null,
+ null,
+ () => sendMessageToIframe(messageTypes.refreshXBlock, null),
+ ));
};
const handleCourseUnitPublish = () => {
diff --git a/src/course-unit/xblock-container-iframe/hooks/types.ts b/src/course-unit/xblock-container-iframe/hooks/types.ts
index 3974656c49..c759f403f9 100644
--- a/src/course-unit/xblock-container-iframe/hooks/types.ts
+++ b/src/course-unit/xblock-container-iframe/hooks/types.ts
@@ -4,7 +4,7 @@ export type UseMessageHandlersTypes = {
dispatch: (action: any) => void;
setIframeOffset: (height: number) => void;
handleDeleteXBlock: (usageId: string) => void;
- handleRefetchXBlocks: () => void;
+ handleScrollToXBlock: (scrollOffset: number) => void;
handleDuplicateXBlock: (blockType: string, usageId: string) => void;
handleManageXBlockAccess: (usageId: string) => void;
};
diff --git a/src/course-unit/xblock-container-iframe/hooks/useIframeContent.tsx b/src/course-unit/xblock-container-iframe/hooks/useIframeContent.tsx
index abbc98b212..17507a8241 100644
--- a/src/course-unit/xblock-container-iframe/hooks/useIframeContent.tsx
+++ b/src/course-unit/xblock-container-iframe/hooks/useIframeContent.tsx
@@ -1,6 +1,4 @@
-import { useEffect, useCallback, RefObject } from 'react';
-
-import { messageTypes } from '../../constants';
+import { useEffect, RefObject } from 'react';
/**
* Hook for managing iframe content and providing utilities to interact with the iframe.
@@ -8,26 +6,15 @@ import { messageTypes } from '../../constants';
* @param {React.RefObject} iframeRef - A React ref for the iframe element.
* @param {(ref: React.RefObject) => void} setIframeRef -
* A function to associate the iframeRef with the parent context.
- * @param {(type: string, payload: any) => void} sendMessageToIframe - A function to send messages to the iframe.
*
* @returns {Object} - An object containing utility functions.
- * @returns {() => void} return.refreshIframeContent -
- * A function to refresh the iframe content by sending a specific message.
+ * @returns {() => void}
*/
export const useIframeContent = (
iframeRef: RefObject,
setIframeRef: (ref: RefObject) => void,
- sendMessageToIframe: (type: string, payload: any) => void,
-): { refreshIframeContent: () => void } => {
+): void => {
useEffect(() => {
setIframeRef(iframeRef);
}, [setIframeRef, iframeRef]);
-
- // TODO: this artificial delay is a temporary solution
- // to ensure the iframe content is properly refreshed.
- const refreshIframeContent = useCallback(() => {
- setTimeout(() => sendMessageToIframe(messageTypes.refreshXBlock, null), 1000);
- }, [sendMessageToIframe]);
-
- return { refreshIframeContent };
};
diff --git a/src/course-unit/xblock-container-iframe/hooks/useMessageHandlers.tsx b/src/course-unit/xblock-container-iframe/hooks/useMessageHandlers.tsx
index 974f7bf0c6..8633e63e96 100644
--- a/src/course-unit/xblock-container-iframe/hooks/useMessageHandlers.tsx
+++ b/src/course-unit/xblock-container-iframe/hooks/useMessageHandlers.tsx
@@ -1,4 +1,5 @@
import { useMemo } from 'react';
+import { debounce } from 'lodash';
import { copyToClipboard } from '../../../generic/data/thunks';
import { messageTypes } from '../../constants';
@@ -16,8 +17,8 @@ export const useMessageHandlers = ({
dispatch,
setIframeOffset,
handleDeleteXBlock,
- handleRefetchXBlocks,
handleDuplicateXBlock,
+ handleScrollToXBlock,
handleManageXBlockAccess,
}: UseMessageHandlersTypes): MessageHandlersTypes => useMemo(() => ({
[messageTypes.copyXBlock]: ({ usageId }) => dispatch(copyToClipboard(usageId)),
@@ -25,14 +26,14 @@ export const useMessageHandlers = ({
[messageTypes.newXBlockEditor]: ({ blockType, usageId }) => navigate(`/course/${courseId}/editor/${blockType}/${usageId}`),
[messageTypes.duplicateXBlock]: ({ blockType, usageId }) => handleDuplicateXBlock(blockType, usageId),
[messageTypes.manageXBlockAccess]: ({ usageId }) => handleManageXBlockAccess(usageId),
- [messageTypes.refreshXBlockPositions]: handleRefetchXBlocks,
+ [messageTypes.scrollToXBlock]: debounce(({ scrollOffset }) => handleScrollToXBlock(scrollOffset), 3000),
[messageTypes.toggleCourseXBlockDropdown]: ({
courseXBlockDropdownHeight,
}: { courseXBlockDropdownHeight: number }) => setIframeOffset(courseXBlockDropdownHeight),
}), [
courseId,
handleDeleteXBlock,
- handleRefetchXBlocks,
handleDuplicateXBlock,
handleManageXBlockAccess,
+ handleScrollToXBlock,
]);
diff --git a/src/course-unit/xblock-container-iframe/index.tsx b/src/course-unit/xblock-container-iframe/index.tsx
index 9b47a32d07..d0a8528378 100644
--- a/src/course-unit/xblock-container-iframe/index.tsx
+++ b/src/course-unit/xblock-container-iframe/index.tsx
@@ -10,7 +10,6 @@ import DeleteModal from '../../generic/delete-modal/DeleteModal';
import ConfigureModal from '../../generic/configure-modal/ConfigureModal';
import { IFRAME_FEATURE_POLICY } from '../../constants';
import supportedEditors from '../../editors/supportedEditors';
-import { fetchCourseUnitQuery } from '../data/thunk';
import { useIframe } from '../context/hooks';
import {
useMessageHandlers,
@@ -43,9 +42,10 @@ const XBlockContainerIframe: FC = ({
const iframeUrl = useMemo(() => getIframeUrl(blockId), [blockId]);
- const { setIframeRef, sendMessageToIframe } = useIframe();
+ const { setIframeRef } = useIframe();
const { iframeHeight } = useIFrameBehavior({ id: blockId, iframeUrl });
- const { refreshIframeContent } = useIframeContent(iframeRef, setIframeRef, sendMessageToIframe);
+
+ useIframeContent(iframeRef, setIframeRef);
useEffect(() => {
setIframeRef(iframeRef);
@@ -57,9 +57,8 @@ const XBlockContainerIframe: FC = ({
if (supportedEditors[blockType]) {
navigate(`/course/${courseId}/editor/${blockType}/${usageId}`);
}
- refreshIframeContent();
},
- [unitXBlockActions, courseId, navigate, refreshIframeContent],
+ [unitXBlockActions, courseId, navigate],
);
const handleDeleteXBlock = (usageId: string) => {
@@ -76,15 +75,10 @@ const XBlockContainerIframe: FC = ({
}
};
- const handleRefetchXBlocks = useCallback(() => {
- setTimeout(() => dispatch(fetchCourseUnitQuery(blockId)), 1000);
- }, [dispatch, blockId]);
-
const onDeleteSubmit = () => {
if (deleteXBlockId) {
unitXBlockActions.handleDelete(deleteXBlockId);
closeDeleteModal();
- refreshIframeContent();
}
};
@@ -92,19 +86,25 @@ const XBlockContainerIframe: FC = ({
if (configureXBlockId) {
handleConfigureSubmit(configureXBlockId, ...args, closeConfigureModal);
setAccessManagedXBlockData({});
- refreshIframeContent();
}
};
+ const handleScrollToXBlock = (scrollOffset: number) => {
+ window.scrollBy({
+ top: scrollOffset,
+ behavior: 'smooth',
+ });
+ };
+
const messageHandlers = useMessageHandlers({
courseId,
navigate,
dispatch,
setIframeOffset,
handleDeleteXBlock,
- handleRefetchXBlocks,
handleDuplicateXBlock,
handleManageXBlockAccess,
+ handleScrollToXBlock,
});
useIframeMessages(messageHandlers);
diff --git a/src/generic/clipboard/paste-component/components/PasteButton.jsx b/src/generic/clipboard/paste-component/components/PasteButton.jsx
index a13dc28c6b..173e57ce33 100644
--- a/src/generic/clipboard/paste-component/components/PasteButton.jsx
+++ b/src/generic/clipboard/paste-component/components/PasteButton.jsx
@@ -7,7 +7,7 @@ const PasteButton = ({ onClick, text, className }) => {
const { blockId } = useParams();
const handlePasteXBlockComponent = () => {
- onClick({ stagedContent: 'clipboard', parentLocator: blockId }, null, blockId);
+ onClick({ stagedContent: 'clipboard', parentLocator: blockId });
};
return (
diff --git a/src/generic/configure-modal/ConfigureModal.jsx b/src/generic/configure-modal/ConfigureModal.jsx
index 04c82200df..4aa03435f1 100644
--- a/src/generic/configure-modal/ConfigureModal.jsx
+++ b/src/generic/configure-modal/ConfigureModal.jsx
@@ -365,7 +365,7 @@ ConfigureModal.propTypes = {
supportsOnboarding: PropTypes.bool,
showReviewRules: PropTypes.bool,
onlineProctoringRules: PropTypes.string,
- discussionEnabled: PropTypes.bool.isRequired,
+ discussionEnabled: PropTypes.bool,
}).isRequired,
isXBlockComponent: PropTypes.bool,
isSelfPaced: PropTypes.bool.isRequired,
diff --git a/src/generic/configure-modal/UnitTab.jsx b/src/generic/configure-modal/UnitTab.jsx
index 01e18be075..28d7ab45ac 100644
--- a/src/generic/configure-modal/UnitTab.jsx
+++ b/src/generic/configure-modal/UnitTab.jsx
@@ -152,13 +152,14 @@ UnitTab.propTypes = {
isXBlockComponent: PropTypes.bool,
values: PropTypes.shape({
isVisibleToStaffOnly: PropTypes.bool.isRequired,
- discussionEnabled: PropTypes.bool.isRequired,
+ discussionEnabled: PropTypes.bool,
selectedPartitionIndex: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]).isRequired,
selectedGroups: PropTypes.oneOfType([
- PropTypes.string,
+ PropTypes.arrayOf(PropTypes.string),
+ PropTypes.array,
]),
}).isRequired,
setFieldValue: PropTypes.func.isRequired,