chore: iframe rendering optimization (#1544)
Iframe reload optimizations for various xblock related actions. Added some improvements related to scrolling to the current xblock. Fixed behavior of the xblock action dropdown list.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
.DS_Store
|
||||
.eslintcache
|
||||
.idea
|
||||
.run
|
||||
node_modules
|
||||
npm-debug.log
|
||||
coverage
|
||||
|
||||
@@ -247,7 +247,7 @@ describe('<CourseUnit />', () => {
|
||||
);
|
||||
|
||||
simulatePostMessageEvent(messageTypes.deleteXBlock, {
|
||||
id: courseVerticalChildrenMock.children[0].block_id,
|
||||
usageId: courseVerticalChildrenMock.children[0].block_id,
|
||||
});
|
||||
|
||||
expect(getByText(/Delete this component?/i)).toBeInTheDocument();
|
||||
@@ -261,10 +261,10 @@ describe('<CourseUnit />', () => {
|
||||
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();
|
||||
@@ -300,8 +300,12 @@ describe('<CourseUnit />', () => {
|
||||
|
||||
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,
|
||||
@@ -1632,6 +1636,8 @@ describe('<CourseUnit />', () => {
|
||||
callbackFn: requestData.callbackFn,
|
||||
}), store.dispatch);
|
||||
|
||||
simulatePostMessageEvent(messageTypes.rollbackMovedXBlock, { locator: requestData.sourceLocator });
|
||||
|
||||
const dismissButton = queryByRole('button', {
|
||||
name: /dismiss/i, hidden: true,
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -52,15 +52,21 @@ 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',
|
||||
handleViewXBlockContent: 'handleViewXBlockContent',
|
||||
};
|
||||
|
||||
@@ -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, { locator: xblockId });
|
||||
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 }));
|
||||
try {
|
||||
const courseUnit = await getCourseUnitData(currentParentLocator);
|
||||
dispatch(fetchCourseItemSuccess(courseUnit));
|
||||
callbackFn();
|
||||
} catch (error) {
|
||||
handleResponseErrors(error, dispatch, updateSavingStatus);
|
||||
}
|
||||
callbackFn(sourceLocator);
|
||||
} catch (error) {
|
||||
handleResponseErrors(error, dispatch, updateSavingStatus);
|
||||
} finally {
|
||||
|
||||
@@ -12,8 +12,6 @@ import ConfigureModal from '../../generic/configure-modal/ConfigureModal';
|
||||
import { COURSE_BLOCK_NAMES } from '../../constants';
|
||||
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 = ({
|
||||
@@ -29,15 +27,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 = () => {
|
||||
|
||||
@@ -60,42 +60,35 @@ describe('<HeaderTitle />', () => {
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
it('render HeaderTitle with open edit form', () => {
|
||||
const { getByRole } = renderComponent({
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
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`);
|
||||
@@ -107,7 +100,6 @@ describe('<HeaderTitle />', () => {
|
||||
expect(titleField).toHaveValue(`${unitTitle} 1 2`);
|
||||
expect(handleTitleEditSubmit).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('displays a visibility message with the selected groups for the unit', async () => {
|
||||
axiosMock
|
||||
@@ -125,7 +117,7 @@ describe('<HeaderTitle />', () => {
|
||||
const visibilityMessage = messages.definedVisibilityMessage.defaultMessage
|
||||
.replace('{selectedGroupsLabel}', 'Visibility group 1');
|
||||
|
||||
waitFor(() => {
|
||||
await waitFor(() => {
|
||||
expect(getByText(visibilityMessage)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -140,8 +132,8 @@ describe('<HeaderTitle />', () => {
|
||||
await executeThunk(fetchCourseUnitQuery(blockId), store.dispatch);
|
||||
const { getByText } = renderComponent();
|
||||
|
||||
waitFor(() => {
|
||||
expect(getByText(messages.someVisibilityMessage.defaultMessage)).toBeInTheDocument();
|
||||
await waitFor(() => {
|
||||
expect(getByText(messages.commonVisibilityMessage.defaultMessage)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -88,6 +88,7 @@ export const useCourseUnit = ({ courseId, blockId }) => {
|
||||
isVisible,
|
||||
groupAccess,
|
||||
isDiscussionEnabled,
|
||||
() => sendMessageToIframe(messageTypes.completeManageXBlockAccess, { locator: id }),
|
||||
blockId,
|
||||
));
|
||||
if (typeof closeModalFn === 'function') {
|
||||
@@ -119,15 +120,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 }),
|
||||
));
|
||||
},
|
||||
};
|
||||
|
||||
@@ -142,7 +147,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' });
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -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' });
|
||||
},
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -4,7 +4,9 @@ import { useKeyedState } from '@edx/react-unit-test-utils';
|
||||
import { logError } from '@edx/frontend-platform/logging';
|
||||
|
||||
import { stateKeys, messageTypes } from '../../../constants';
|
||||
import { useLoadBearingHook, useIFrameBehavior } from '..';
|
||||
import { useLoadBearingHook, useIFrameBehavior, useMessageHandlers } from '..';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
jest.mock('@edx/react-unit-test-utils', () => ({
|
||||
useKeyedState: jest.fn(),
|
||||
@@ -171,3 +173,36 @@ describe('useLoadBearingHook', () => {
|
||||
expect(setValue.mock.calls);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useMessageHandlers', () => {
|
||||
it('calls handleScrollToXBlock after debounce delay', () => {
|
||||
const mockHandleScrollToXBlock = jest.fn();
|
||||
const courseId = 'course-v1:Test+101+2025';
|
||||
const navigate = jest.fn();
|
||||
const dispatch = jest.fn();
|
||||
const setIframeOffset = jest.fn();
|
||||
const handleDeleteXBlock = jest.fn();
|
||||
const handleDuplicateXBlock = jest.fn();
|
||||
const handleManageXBlockAccess = jest.fn();
|
||||
|
||||
const { result } = renderHook(() => useMessageHandlers({
|
||||
courseId,
|
||||
navigate,
|
||||
dispatch,
|
||||
setIframeOffset,
|
||||
handleDeleteXBlock,
|
||||
handleDuplicateXBlock,
|
||||
handleScrollToXBlock: mockHandleScrollToXBlock,
|
||||
handleManageXBlockAccess,
|
||||
}));
|
||||
|
||||
act(() => {
|
||||
result.current[messageTypes.scrollToXBlock]({ scrollOffset: 200 });
|
||||
});
|
||||
|
||||
jest.advanceTimersByTime(3000);
|
||||
|
||||
expect(mockHandleScrollToXBlock).toHaveBeenCalledTimes(1);
|
||||
expect(mockHandleScrollToXBlock).toHaveBeenCalledWith(200);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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<HTMLIFrameElement>} iframeRef - A React ref for the iframe element.
|
||||
* @param {(ref: React.RefObject<HTMLIFrameElement>) => 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<HTMLIFrameElement>,
|
||||
setIframeRef: (ref: RefObject<HTMLIFrameElement>) => 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 };
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
]);
|
||||
|
||||
@@ -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<XBlockContainerIframeProps> = ({
|
||||
|
||||
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<XBlockContainerIframeProps> = ({
|
||||
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<XBlockContainerIframeProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
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<XBlockContainerIframeProps> = ({
|
||||
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);
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -369,7 +369,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,
|
||||
|
||||
@@ -157,12 +157,15 @@ UnitTab.propTypes = {
|
||||
isLibraryContent: 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.arrayOf(PropTypes.string),
|
||||
selectedGroups: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.string),
|
||||
PropTypes.array,
|
||||
]),
|
||||
}).isRequired,
|
||||
setFieldValue: PropTypes.func.isRequired,
|
||||
showWarning: PropTypes.bool.isRequired,
|
||||
|
||||
Reference in New Issue
Block a user