feat: display editors as modals (#1838)

This commit is contained in:
Daniel Valenzuela
2025-04-25 15:48:28 -04:00
committed by GitHub
parent b30a1c8c5e
commit dbb1a996e1
19 changed files with 146 additions and 152 deletions

View File

@@ -671,8 +671,7 @@ describe('<CourseUnit />', () => {
});
});
it('handle creating Problem xblock and navigate to editor page', async () => {
const { courseKey, locator } = courseCreateXblockMock;
it('handle creating Problem xblock and showing editor modal', async () => {
axiosMock
.onPost(postXBlockBaseApiUrl({ type: 'problem', category: 'problem', parentLocator: blockId }))
.reply(200, courseCreateXblockMock);
@@ -701,11 +700,16 @@ describe('<CourseUnit />', () => {
await waitFor(() => {
const problemButton = getByRole('button', {
name: new RegExp(`problem ${addComponentMessages.buttonText.defaultMessage} Problem`, 'i'),
hidden: true,
});
userEvent.click(problemButton);
expect(mockedUsedNavigate).toHaveBeenCalled();
expect(mockedUsedNavigate).toHaveBeenCalledWith(`/course/${courseKey}/editor/problem/${locator}`);
});
await waitFor(() => {
expect(getByRole('heading', {
name: new RegExp(`${addComponentMessages.blockEditorModalTitle.defaultMessage}`, 'i'),
})).toBeInTheDocument();
});
axiosMock
@@ -735,44 +739,6 @@ describe('<CourseUnit />', () => {
)).toBeInTheDocument();
});
it('handle creating Text xblock and saves scroll position in localStorage', async () => {
const { getByText, getByRole } = render(<RootWrapper />);
const xblockType = 'text';
axiosMock
.onPost(postXBlockBaseApiUrl({ type: xblockType, category: 'html', parentLocator: blockId }))
.reply(200, courseCreateXblockMock);
window.scrollTo(0, 250);
Object.defineProperty(window, 'scrollY', { value: 250, configurable: true });
await waitFor(() => {
const textButton = screen.getByRole('button', { name: /Text/i });
expect(getByText(addComponentMessages.title.defaultMessage)).toBeInTheDocument();
userEvent.click(textButton);
const addXBlockDialog = getByRole('dialog');
expect(addXBlockDialog).toBeInTheDocument();
expect(getByText(
addComponentMessages.modalContainerTitle.defaultMessage.replace('{componentTitle}', xblockType),
)).toBeInTheDocument();
const textRadio = screen.getByRole('radio', { name: /Text/i });
userEvent.click(textRadio);
expect(textRadio).toBeChecked();
const selectBtn = getByRole('button', { name: addComponentMessages.modalBtnText.defaultMessage });
expect(selectBtn).toBeInTheDocument();
userEvent.click(selectBtn);
});
expect(localStorage.getItem('createXBlockLastYPosition')).toBe('250');
});
it('correct addition of a new course unit after click on the "Add new unit" button', async () => {
const { getByRole, getAllByTestId } = render(<RootWrapper />);
let units = null;
@@ -863,8 +829,7 @@ describe('<CourseUnit />', () => {
});
});
it('handles creating Video xblock and navigates to editor page', async () => {
const { courseKey, locator } = courseCreateXblockMock;
it('handles creating Video xblock and showing editor modal', async () => {
axiosMock
.onPost(postXBlockBaseApiUrl({ type: 'video', category: 'video', parentLocator: blockId }))
.reply(200, courseCreateXblockMock);
@@ -902,13 +867,18 @@ describe('<CourseUnit />', () => {
const videoButton = getByRole('button', {
name: new RegExp(`${addComponentMessages.buttonText.defaultMessage} Video`, 'i'),
hidden: true,
});
userEvent.click(videoButton);
expect(mockedUsedNavigate).toHaveBeenCalled();
expect(mockedUsedNavigate).toHaveBeenCalledWith(`/course/${courseKey}/editor/video/${locator}`);
});
/** TODO -- fix this test.
await waitFor(() => {
expect(getByRole('textbox', { name: /paste your video id or url/i })).toBeInTheDocument();
});
*/
axiosMock
.onGet(getCourseUnitApiUrl(blockId))
.reply(200, courseUnitIndexMock);

View File

@@ -1,7 +1,7 @@
import { useCallback, useState } from 'react';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { getConfig } from '@edx/frontend-platform';
import { useIntl, FormattedMessage } from '@edx/frontend-platform/i18n';
import {
ActionRow, Button, StandardModal, useToggle,
@@ -16,6 +16,7 @@ import { ComponentPicker } from '../../library-authoring/component-picker';
import { messageTypes } from '../constants';
import { useIframe } from '../../generic/hooks/context/hooks';
import { useEventListener } from '../../generic/hooks';
import EditorPage from '../../editors/EditorPage';
const AddComponent = ({
parentLocator,
@@ -24,7 +25,6 @@ const AddComponent = ({
addComponentTemplateData,
handleCreateNewCourseXBlock,
}) => {
const navigate = useNavigate();
const intl = useIntl();
const [isOpenAdvanced, openAdvanced, closeAdvanced] = useToggle(false);
const [isOpenHtml, openHtml, closeHtml] = useToggle(false);
@@ -32,6 +32,11 @@ const AddComponent = ({
const { componentTemplates = {} } = useSelector(getCourseSectionVertical);
const blockId = addComponentTemplateData.parentLocator || parentLocator;
const [isAddLibraryContentModalOpen, showAddLibraryContentModal, closeAddLibraryContentModal] = useToggle();
const [isXBlockEditorModalOpen, showXBlockEditorModal, closeXBlockEditorModal] = useToggle();
const [blockType, setBlockType] = useState(null);
const [courseId, setCourseId] = useState(null);
const [newBlockId, setNewBlockId] = useState(null);
const [isSelectLibraryContentModalOpen, showSelectLibraryContentModal, closeSelectLibraryContentModal] = useToggle();
const [selectedComponents, setSelectedComponents] = useState([]);
const [usageId, setUsageId] = useState(null);
@@ -54,6 +59,11 @@ const AddComponent = ({
closeSelectLibraryContentModal();
}, [selectedComponents]);
const onXBlockSave = useCallback(/* istanbul ignore next */ () => {
closeXBlockEditorModal();
sendMessageToIframe(messageTypes.refreshXBlock, null);
}, [closeXBlockEditorModal, sendMessageToIframe]);
const handleLibraryV2Selection = useCallback((selection) => {
handleCreateNewCourseXBlock({
type: COMPONENT_TYPES.libraryV2,
@@ -70,11 +80,13 @@ const AddComponent = ({
case COMPONENT_TYPES.dragAndDrop:
handleCreateNewCourseXBlock({ type, parentLocator: blockId });
break;
case COMPONENT_TYPES.problem:
case COMPONENT_TYPES.video:
case COMPONENT_TYPES.problem:
handleCreateNewCourseXBlock({ type, parentLocator: blockId }, ({ courseKey, locator }) => {
localStorage.setItem('createXBlockLastYPosition', window.scrollY);
navigate(`/course/${courseKey}/editor/${type}/${locator}`);
setCourseId(courseKey);
setBlockType(type);
setNewBlockId(locator);
showXBlockEditorModal();
});
break;
// TODO: The library functional will be a bit different of current legacy (CMS)
@@ -99,9 +111,11 @@ const AddComponent = ({
type,
boilerplate: moduleName,
parentLocator: blockId,
}, ({ courseKey, locator }) => {
localStorage.setItem('createXBlockLastYPosition', window.scrollY);
navigate(`/course/${courseKey}/editor/html/${locator}`);
}, /* istanbul ignore next */ ({ courseKey, locator }) => {
setCourseId(courseKey);
setBlockType(type);
setNewBlockId(locator);
showXBlockEditorModal();
});
break;
default:
@@ -201,6 +215,25 @@ const AddComponent = ({
onChangeComponentSelection={setSelectedComponents}
/>
</StandardModal>
<StandardModal
title={intl.formatMessage(messages.blockEditorModalTitle)}
isOpen={isXBlockEditorModalOpen}
onClose={closeXBlockEditorModal}
isOverflowVisible={false}
size="xl"
>
<div className="editor-page">
<EditorPage
courseId={courseId}
blockType={blockType}
blockId={newBlockId}
studioEndpointUrl={getConfig().STUDIO_BASE_URL}
lmsEndpointUrl={getConfig().LMS_BASE_URL}
onClose={closeXBlockEditorModal}
returnFunction={/* istanbul ignore next */ () => onXBlockSave}
/>
</div>
</StandardModal>
</div>
);
}

View File

@@ -31,6 +31,16 @@ const messages = defineMessages({
defaultMessage: 'Add selected components',
description: 'Problem bank component add button text.',
},
videoPickerModalTitle: {
id: 'course-authoring.course-unit.modal.video-title.text',
defaultMessage: 'Select video',
description: 'Video picker modal title.',
},
blockEditorModalTitle: {
id: 'course-authoring.course-unit.modal.block-editor-title.text',
defaultMessage: 'Edit component',
description: 'Block editor modal title.',
},
modalContainerTitle: {
id: 'course-authoring.course-unit.modal.container.title',
defaultMessage: 'Add {componentTitle} component',

View File

@@ -1,11 +1,11 @@
export type UseMessageHandlersTypes = {
courseId: string;
navigate: (path: string) => void;
dispatch: (action: any) => void;
setIframeOffset: (height: number) => void;
handleDeleteXBlock: (usageId: string) => void;
handleScrollToXBlock: (scrollOffset: number) => void;
handleDuplicateXBlock: (blockType: string, usageId: string) => void;
handleEditXBlock: (blockType: string, usageId: string) => void;
handleManageXBlockAccess: (usageId: string) => void;
handleShowLegacyEditXBlockModal: (id: string) => void;
handleCloseLegacyEditorXBlockModal: () => void;
@@ -14,7 +14,6 @@ export type UseMessageHandlersTypes = {
handleOpenManageTagsModal: (id: string) => void;
handleShowProcessingNotification: (variant: string) => void;
handleHideProcessingNotification: () => void;
handleRedirectToXBlockEditPage: (payload: { type: string, locator: string }) => void;
};
export type MessageHandlersTypes = Record<string, (payload: any) => void>;

View File

@@ -16,7 +16,6 @@ import { MessageHandlersTypes, UseMessageHandlersTypes } from './types';
*/
export const useMessageHandlers = ({
courseId,
navigate,
dispatch,
setIframeOffset,
handleDeleteXBlock,
@@ -30,14 +29,14 @@ export const useMessageHandlers = ({
handleOpenManageTagsModal,
handleShowProcessingNotification,
handleHideProcessingNotification,
handleRedirectToXBlockEditPage,
handleEditXBlock,
}: UseMessageHandlersTypes): MessageHandlersTypes => {
const { copyToClipboard } = useClipboard();
return useMemo(() => ({
[messageTypes.copyXBlock]: ({ usageId }) => copyToClipboard(usageId),
[messageTypes.deleteXBlock]: ({ usageId }) => handleDeleteXBlock(usageId),
[messageTypes.newXBlockEditor]: ({ blockType, usageId }) => navigate(`/course/${courseId}/editor/${blockType}/${usageId}`),
[messageTypes.newXBlockEditor]: ({ blockType, usageId }) => handleEditXBlock(blockType, usageId),
[messageTypes.duplicateXBlock]: ({ blockType, usageId }) => handleDuplicateXBlock(blockType, usageId),
[messageTypes.manageXBlockAccess]: ({ usageId }) => handleManageXBlockAccess(usageId),
[messageTypes.scrollToXBlock]: debounce(({ scrollOffset }) => handleScrollToXBlock(scrollOffset), 1000),
@@ -52,9 +51,14 @@ export const useMessageHandlers = ({
[messageTypes.openManageTags]: (payload) => handleOpenManageTagsModal(payload.contentId),
[messageTypes.addNewComponent]: () => handleShowProcessingNotification(NOTIFICATION_MESSAGES.adding),
[messageTypes.pasteNewComponent]: () => handleShowProcessingNotification(NOTIFICATION_MESSAGES.pasting),
[messageTypes.copyXBlockLegacy]: () => handleShowProcessingNotification(NOTIFICATION_MESSAGES.copying),
[messageTypes.copyXBlockLegacy]: /* istanbul ignore next */ () => handleShowProcessingNotification(
NOTIFICATION_MESSAGES.copying,
),
[messageTypes.hideProcessingNotification]: handleHideProcessingNotification,
[messageTypes.handleRedirectToXBlockEditPage]: (payload) => handleRedirectToXBlockEditPage(payload),
[messageTypes.handleRedirectToXBlockEditPage]: /* istanbul ignore next */ (payload) => handleEditXBlock(
payload.type,
payload.locator,
),
}), [
courseId,
handleDeleteXBlock,

View File

@@ -1,8 +1,9 @@
import { getConfig } from '@edx/frontend-platform';
import {
FC, useEffect, useState, useMemo, useCallback,
} from 'react';
import { useIntl } from '@edx/frontend-platform/i18n';
import { useToggle, Sheet } from '@openedx/paragon';
import { useToggle, Sheet, StandardModal } from '@openedx/paragon';
import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom';
@@ -35,6 +36,7 @@ import messages from './messages';
import { useIframeBehavior } from '../../generic/hooks/useIframeBehavior';
import { useIframeContent } from '../../generic/hooks/useIframeContent';
import { useIframeMessages } from '../../generic/hooks/useIframeMessages';
import EditorPage from '../../editors/EditorPage';
const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
courseId, blockId, unitXBlockActions, courseVerticalChildren, handleConfigureSubmit, isUnitVerticalType,
@@ -45,6 +47,9 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useToggle(false);
const [isConfigureModalOpen, openConfigureModal, closeConfigureModal] = useToggle(false);
const [isXBlockEditorModalOpen, showXBlockEditorModal, closeXBlockEditorModal] = useToggle();
const [blockType, setBlockType] = useState<string>('');
const [newBlockId, setNewBlockId] = useState<string>('');
const [accessManagedXBlockData, setAccessManagedXBlockData] = useState<AccessManagedXBlockDataTypes | {}>({});
const [iframeOffset, setIframeOffset] = useState(0);
const [deleteXBlockId, setDeleteXBlockId] = useState<string | null>(null);
@@ -64,11 +69,23 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
setIframeRef(iframeRef);
}, [setIframeRef]);
const onXBlockSave = useCallback(/* istanbul ignore next */ () => {
closeXBlockEditorModal();
sendMessageToIframe(messageTypes.refreshXBlock, null);
}, [closeXBlockEditorModal, sendMessageToIframe]);
const handleEditXBlock = useCallback((type: string, id: string) => {
setBlockType(type);
setNewBlockId(id);
showXBlockEditorModal();
}, [showXBlockEditorModal]);
const handleDuplicateXBlock = useCallback(
(blockType: string, usageId: string) => {
(type: string, usageId: string) => {
unitXBlockActions.handleDuplicate(usageId);
if (supportedEditors[blockType]) {
navigate(`/course/${courseId}/editor/${blockType}/${usageId}`);
if (supportedEditors[type]) {
// istanbul ignore next
handleEditXBlock(type, usageId);
}
},
[unitXBlockActions, courseId, navigate],
@@ -147,13 +164,8 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
dispatch(hideProcessingNotification());
};
const handleRedirectToXBlockEditPage = (payload: { type: string, locator: string }) => {
navigate(`/course/${courseId}/editor/${payload.type}/${payload.locator}`);
};
const messageHandlers = useMessageHandlers({
courseId,
navigate,
dispatch,
setIframeOffset,
handleDeleteXBlock,
@@ -167,7 +179,7 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
handleOpenManageTagsModal,
handleShowProcessingNotification,
handleHideProcessingNotification,
handleRedirectToXBlockEditPage,
handleEditXBlock,
});
useIframeMessages(messageHandlers);
@@ -186,6 +198,25 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
close={closeDeleteModal}
onDeleteSubmit={onDeleteSubmit}
/>
<StandardModal
title={intl.formatMessage(messages.blockEditorModalTitle)}
isOpen={isXBlockEditorModalOpen}
onClose={closeXBlockEditorModal}
isOverflowVisible={false}
size="xl"
>
<div className="editor-page">
<EditorPage
courseId={courseId}
blockType={blockType}
blockId={newBlockId}
studioEndpointUrl={getConfig().STUDIO_BASE_URL}
lmsEndpointUrl={getConfig().LMS_BASE_URL}
onClose={closeXBlockEditorModal}
returnFunction={/* istanbul ignore next */ () => onXBlockSave}
/>
</div>
</StandardModal>
{Object.keys(accessManagedXBlockData).length ? (
<ConfigureModal
isXBlockComponent

View File

@@ -15,6 +15,15 @@ const messages = defineMessages({
id: 'course-authoring.course-unit.xblock.iframe.label',
defaultMessage: '{xblockCount} xBlocks inside the frame',
},
videoPickerModalTitle: {
id: 'course-authoring.course-unit.xblock.video-editor.title',
defaultMessage: 'Select video',
},
blockEditorModalTitle: {
id: 'course-authoring.course-unit.xblock.editor.title',
defaultMessage: 'Edit component',
description: 'Block editor modal title.',
},
});
export default messages;

View File

@@ -7,7 +7,6 @@ import * as hooks from './hooks';
import supportedEditors from './supportedEditors';
import type { EditorComponent } from './EditorComponent';
import { useEditorContext } from './EditorContext';
import AdvancedEditor from './AdvancedEditor';
export interface Props extends EditorComponent {
@@ -17,7 +16,6 @@ export interface Props extends EditorComponent {
learningContextId: string | null;
lmsEndpointUrl: string | null;
studioEndpointUrl: string | null;
fullScreen?: boolean; // eslint-disable-line react/no-unused-prop-types
}
const Editor: React.FC<Props> = ({
@@ -42,7 +40,6 @@ const Editor: React.FC<Props> = ({
studioEndpointUrl,
},
});
const { fullScreen } = useEditorContext();
const EditorComponent = supportedEditors[blockType];
@@ -60,24 +57,7 @@ const Editor: React.FC<Props> = ({
);
}
const innerEditor = <EditorComponent {...{ onClose, returnFunction }} />;
if (fullScreen) {
return (
<div
className="d-flex flex-column"
>
<div
className="pgn__modal-fullscreen h-100"
role="dialog"
aria-label={blockType}
>
{innerEditor}
</div>
</div>
);
}
return innerEditor;
return <EditorComponent {...{ onClose, returnFunction }} />;
};
export default Editor;

View File

@@ -7,14 +7,6 @@ import React from 'react';
*/
export interface EditorContext {
learningContextId: string;
/**
* When editing components in the libraries part of the Authoring MFE, we show
* the editors in a modal (fullScreen = false). This is the preferred approach
* so that authors can see context behind the modal.
* However, when making edits from the legacy course view, we display the
* editors in a fullscreen view. This approach is deprecated.
*/
fullScreen: boolean;
}
const context = React.createContext<EditorContext | undefined>(undefined);
@@ -32,7 +24,6 @@ export function useEditorContext() {
export const EditorContextProvider: React.FC<{
children: React.ReactNode,
learningContextId: string;
fullScreen: boolean;
}> = ({ children, ...contextData }) => {
const ctx: EditorContext = React.useMemo(() => ({ ...contextData }), []);
return <context.Provider value={ctx}>{children}</context.Provider>;

View File

@@ -37,7 +37,6 @@ const defaultPropsHtml = {
lmsEndpointUrl: 'http://lms.test.none/',
studioEndpointUrl: 'http://cms.test.none/',
onClose: jest.fn(),
fullScreen: false,
};
const fieldsHtml = {
displayName: 'Introduction to Testing',
@@ -66,22 +65,6 @@ describe('EditorPage', () => {
expect(modalElement.classList).not.toContain('pgn__modal-fullscreen');
});
test('it can display the Text (html) editor as a full page (when coming from the legacy UI)', async () => {
jest.spyOn(editorCmsApi, 'fetchBlockById').mockImplementationOnce(async () => (
{ status: 200, data: snakeCaseObject(fieldsHtml) }
));
render(<EditorPage {...defaultPropsHtml} fullScreen />);
// Then the editor should open
expect(await screen.findByRole('heading', { name: /Introduction to Testing/ })).toBeInTheDocument();
const modalElement = screen.getByRole('dialog');
expect(modalElement.classList).toContain('pgn__modal-fullscreen');
expect(modalElement.classList).not.toContain('pgn__modal');
expect(modalElement.classList).not.toContain('pgn__modal-xl');
});
test('it shows the Advanced Editor if there is no corresponding editor', async () => {
jest.spyOn(editorCmsApi, 'fetchBlockById').mockImplementationOnce(async () => ( // eslint-disable-next-line
{ status: 200, data: { display_name: 'Fake Un-editable Block', category: 'fake', metadata: {}, data: '' } }

View File

@@ -14,7 +14,6 @@ interface Props extends EditorComponent {
isMarkdownEditorEnabledForCourse?: boolean;
lmsEndpointUrl?: string;
studioEndpointUrl?: string;
fullScreen?: boolean;
children?: never;
}
@@ -31,7 +30,6 @@ const EditorPage: React.FC<Props> = ({
studioEndpointUrl = null,
onClose = null,
returnFunction = null,
fullScreen = true,
}) => (
<Provider store={store}>
<ErrorBoundary
@@ -40,7 +38,7 @@ const EditorPage: React.FC<Props> = ({
studioEndpointUrl,
}}
>
<EditorContextProvider fullScreen={fullScreen} learningContextId={courseId}>
<EditorContextProvider learningContextId={courseId}>
<Editor
{...{
onClose,

View File

@@ -32,7 +32,6 @@ const defaultPropsHtml = {
lmsEndpointUrl: 'http://lms.test.none/',
studioEndpointUrl: 'http://cms.test.none/',
onClose: jest.fn(),
fullScreen: false,
};
const fieldsHtml = {
displayName: 'Introduction to Testing',

View File

@@ -14,7 +14,6 @@ import { Close } from '@openedx/paragon/icons';
import { useIntl, FormattedMessage } from '@edx/frontend-platform/i18n';
import { EditorComponent } from '../../EditorComponent';
import { useEditorContext } from '../../EditorContext';
import TitleHeader from './components/TitleHeader';
import * as hooks from './hooks';
import messages from './messages';
@@ -30,37 +29,18 @@ interface WrapperProps {
}
export const EditorModalWrapper: React.FC<WrapperProps & { onClose: () => void }> = ({ children, onClose }) => {
const { fullScreen } = useEditorContext();
const intl = useIntl();
if (fullScreen) {
return (
<div
className="editor-container d-flex flex-column position-relative zindex-0"
style={{ minHeight: '100%' }}
>
{children}
</div>
);
}
const title = intl.formatMessage(messages.modalTitle);
return (
<ModalDialog isOpen size="xl" isOverflowVisible={false} onClose={onClose} title={title}>{children}</ModalDialog>
);
};
export const EditorModalBody: React.FC<WrapperProps> = ({ children }) => {
const { fullScreen } = useEditorContext();
return <ModalDialog.Body className={fullScreen ? 'pb-6' : 'pb-0'}>{ children }</ModalDialog.Body>;
};
export const EditorModalBody: React.FC<WrapperProps> = ({ children }) => <ModalDialog.Body className="pb-0">{ children }</ModalDialog.Body>;
export const FooterWrapper: React.FC<WrapperProps> = ({ children }) => {
const { fullScreen } = useEditorContext();
if (fullScreen) {
return <div className="editor-footer fixed-bottom">{children}</div>;
}
// eslint-disable-next-line react/jsx-no-useless-fragment
return <>{ children }</>;
};
// eslint-disable-next-line react/jsx-no-useless-fragment
export const FooterWrapper: React.FC<WrapperProps> = ({ children }) => <>{ children }</>;
interface Props extends EditorComponent {
children: React.ReactNode;

View File

@@ -20,7 +20,7 @@ describe('SelectTypeModal', () => {
jest.spyOn(hooks, 'onSelect').mockImplementation(mockSelect);
// This is a new-style test, unlike most of the old snapshot-based editor tests.
render(
<EditorContextProvider fullScreen={false} learningContextId="course-v1:Org+COURSE+RUN">
<EditorContextProvider learningContextId="course-v1:Org+COURSE+RUN">
<Provider store={editorStore}>
<SelectTypeModal onClose={mockClose} />
</Provider>

View File

@@ -18,6 +18,7 @@ exports[`VideoEditor snapshots renders as expected with default behavior 1`] = `
"useSelector": [MockFunction],
}
}
onClose={[MockFunction props.onClose]}
/>
</div>
</EditorContainer>

View File

@@ -7,7 +7,9 @@ import VideoSettingsModal from './VideoSettingsModal';
import { RequestKeys } from '../../../data/constants/requests';
interface Props {
onReturn?: (() => void);
isLibrary: boolean;
onClose?: (() => void) | null;
}
export const {
@@ -27,13 +29,15 @@ export const hooks = {
const VideoEditorModal: React.FC<Props> = ({
isLibrary,
onClose,
onReturn,
}) => {
const dispatch = useDispatch();
const location = useLocation();
const searchParams = new URLSearchParams(location.search);
const selectedVideoId = searchParams.get('selectedVideoId');
const selectedVideoUrl = searchParams.get('selectedVideoUrl');
const onReturn = hooks.useReturnToGallery();
const onSettingsReturn = onReturn || hooks.useReturnToGallery();
const isLoaded = useSelector(
(state) => selectors.requests.isFinished(state, { requestKey: RequestKeys.fetchVideos }),
);
@@ -44,8 +48,9 @@ const VideoEditorModal: React.FC<Props> = ({
return (
<VideoSettingsModal {...{
onReturn,
onReturn: onSettingsReturn,
isLibrary,
onClose,
}}
/>
);

View File

@@ -20,11 +20,13 @@ import messages from '../../messages';
interface Props {
onReturn: () => void;
isLibrary: boolean;
onClose?: (() => void) | null;
}
const VideoSettingsModal: React.FC<Props> = ({
onReturn,
isLibrary,
onClose,
}) => (
<>
{!isLibrary && (
@@ -32,7 +34,7 @@ const VideoSettingsModal: React.FC<Props> = ({
variant="link"
className="text-primary-500"
size="sm"
onClick={onReturn}
onClick={onClose || onReturn}
style={{
textDecoration: 'none',
marginLeft: '3px',

View File

@@ -39,7 +39,7 @@ const VideoEditor: React.FC<EditorComponent> = ({
>
{(isCreateWorkflow || studioViewFinished) ? (
<div className="video-editor">
<VideoEditorModal {...{ isLibrary }} />
<VideoEditorModal {...{ isLibrary, onClose, returnFunction }} />
</div>
) : (
<div style={{

View File

@@ -41,7 +41,6 @@ export const ComponentEditorModal: React.FC<Record<never, never>> = () => {
lmsEndpointUrl={getConfig().LMS_BASE_URL}
onClose={onClose}
returnFunction={() => onClose}
fullScreen={false}
/>
);
};