feat: add fullscreen button to XBlock editor (#2892)
This commit is contained in:
@@ -1,7 +1,16 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useToggle } from '@openedx/paragon';
|
||||
import {
|
||||
ActionRow,
|
||||
Icon,
|
||||
IconButton,
|
||||
ModalDialog,
|
||||
ModalCloseButton,
|
||||
Stack,
|
||||
useToggle,
|
||||
} from '@openedx/paragon';
|
||||
import { Close, CloseFullscreen, OpenInFull } from '@openedx/paragon/icons';
|
||||
|
||||
import { LibraryBlock } from '../library-authoring/LibraryBlock';
|
||||
import { EditorModalWrapper } from './containers/EditorContainer';
|
||||
@@ -11,6 +20,8 @@ import messages from './messages';
|
||||
import CancelConfirmModal from './containers/EditorContainer/components/CancelConfirmModal';
|
||||
import { IframeProvider } from '../generic/hooks/context/iFrameContext';
|
||||
|
||||
import editorModalWrapperMessages from './containers/EditorContainer/messages';
|
||||
|
||||
interface AdvancedEditorProps {
|
||||
usageKey: string,
|
||||
onClose: (() => void) | null,
|
||||
@@ -20,6 +31,7 @@ const AdvancedEditor = ({ usageKey, onClose }: AdvancedEditorProps) => {
|
||||
const intl = useIntl();
|
||||
const { showToast } = React.useContext(ToastContext);
|
||||
const [isCancelConfirmOpen, openCancelConfirmModal, closeCancelConfirmModal] = useToggle(false);
|
||||
const [isFullscreen, , , toggleFullscreen] = useToggle(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleIframeMessage = (event) => {
|
||||
@@ -49,7 +61,28 @@ const AdvancedEditor = ({ usageKey, onClose }: AdvancedEditorProps) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<EditorModalWrapper onClose={openCancelConfirmModal}>
|
||||
<EditorModalWrapper onClose={openCancelConfirmModal} fullscreen={isFullscreen}>
|
||||
<ModalDialog.Header>
|
||||
<ActionRow>
|
||||
<ModalDialog.Title>
|
||||
{intl.formatMessage(editorModalWrapperMessages.modalTitle)}
|
||||
</ModalDialog.Title>
|
||||
<ActionRow.Spacer />
|
||||
<Stack direction="horizontal" reversed gap={1}>
|
||||
<ModalCloseButton
|
||||
as={IconButton}
|
||||
src={Close}
|
||||
iconAs={Icon}
|
||||
/>
|
||||
<IconButton
|
||||
src={isFullscreen ? CloseFullscreen : OpenInFull}
|
||||
iconAs={Icon}
|
||||
alt={intl.formatMessage(messages.advancedEditorFullscreenButtonAlt)}
|
||||
onClick={toggleFullscreen}
|
||||
/>
|
||||
</Stack>
|
||||
</ActionRow>
|
||||
</ModalDialog.Header>
|
||||
<IframeProvider>
|
||||
<LibraryBlock
|
||||
usageKey={usageKey}
|
||||
|
||||
@@ -8,39 +8,57 @@ import {
|
||||
IconButton,
|
||||
ModalDialog,
|
||||
Spinner,
|
||||
Stack,
|
||||
Toast,
|
||||
useToggle,
|
||||
} from '@openedx/paragon';
|
||||
import { Close } from '@openedx/paragon/icons';
|
||||
import { Close, CloseFullscreen, OpenInFull } from '@openedx/paragon/icons';
|
||||
import { useIntl, FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { parseErrorMsg } from '@src/library-authoring/add-content/AddContent';
|
||||
import libraryMessages from '@src/library-authoring/add-content/messages';
|
||||
import usePromptIfDirty from '@src/generic/promptIfDirty/usePromptIfDirty';
|
||||
|
||||
import { EditorComponent } from '../../EditorComponent';
|
||||
import TitleHeader from './components/TitleHeader';
|
||||
import * as hooks from './hooks';
|
||||
import messages from './messages';
|
||||
import { parseErrorMsg } from '../../../library-authoring/add-content/AddContent';
|
||||
import libraryMessages from '../../../library-authoring/add-content/messages';
|
||||
|
||||
import './index.scss';
|
||||
import usePromptIfDirty from '../../../generic/promptIfDirty/usePromptIfDirty';
|
||||
import CancelConfirmModal from './components/CancelConfirmModal';
|
||||
|
||||
interface WrapperProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const EditorModalWrapper: React.FC<WrapperProps & { onClose: () => void }> = ({ children, onClose }) => {
|
||||
export const EditorModalWrapper: React.FC<WrapperProps & { onClose: () => void, fullscreen?: boolean }> = (
|
||||
{
|
||||
children,
|
||||
onClose,
|
||||
fullscreen = false,
|
||||
},
|
||||
) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const title = intl.formatMessage(messages.modalTitle);
|
||||
return (
|
||||
<ModalDialog isOpen size="xl" isOverflowVisible={false} onClose={onClose} title={title}>{children}</ModalDialog>
|
||||
<ModalDialog
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
title={title}
|
||||
size={fullscreen ? 'fullscreen' : 'xl'}
|
||||
isOverflowVisible={false}
|
||||
hasCloseButton={false}
|
||||
>
|
||||
{children}
|
||||
</ModalDialog>
|
||||
);
|
||||
};
|
||||
|
||||
export const EditorModalBody: React.FC<WrapperProps> = ({ children }) => <ModalDialog.Body className="pb-0">{ children }</ModalDialog.Body>;
|
||||
export const EditorModalBody: React.FC<WrapperProps> = ({ children }) => <ModalDialog.Body className="pb-0">{children}</ModalDialog.Body>;
|
||||
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
export const FooterWrapper: React.FC<WrapperProps> = ({ children }) => <>{ children }</>;
|
||||
export const FooterWrapper: React.FC<WrapperProps> = ({ children }) => <>{children}</>;
|
||||
|
||||
interface Props extends EditorComponent {
|
||||
children: React.ReactNode;
|
||||
@@ -63,6 +81,7 @@ const EditorContainer: React.FC<Props> = ({
|
||||
const [saved, setSaved] = React.useState(false);
|
||||
const isInitialized = hooks.isInitialized();
|
||||
const { isCancelConfirmOpen, openCancelConfirmModal, closeCancelConfirmModal } = hooks.cancelConfirmModalToggle();
|
||||
const [isFullscreen, , , toggleFullscreen] = useToggle(false);
|
||||
const handleCancel = hooks.handleCancel({ onClose, returnFunction });
|
||||
const { createFailed, createFailedError } = hooks.createFailed();
|
||||
const disableSave = !isInitialized;
|
||||
@@ -97,8 +116,9 @@ const EditorContainer: React.FC<Props> = ({
|
||||
handleCancel();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<EditorModalWrapper onClose={confirmCancelIfDirty}>
|
||||
<EditorModalWrapper onClose={confirmCancelIfDirty} fullscreen={isFullscreen}>
|
||||
{createFailed && (
|
||||
<Toast show onClose={clearCreateFailed}>
|
||||
{parseErrorMsg(
|
||||
@@ -127,15 +147,27 @@ const EditorContainer: React.FC<Props> = ({
|
||||
/>
|
||||
<ModalDialog.Header className="shadow-sm zindex-10">
|
||||
<div className="d-flex flex-row justify-content-between">
|
||||
<h2 className="h3 col pl-0">
|
||||
<TitleHeader isInitialized={isInitialized} />
|
||||
</h2>
|
||||
<IconButton
|
||||
src={Close}
|
||||
iconAs={Icon}
|
||||
onClick={confirmCancelIfDirty}
|
||||
alt={intl.formatMessage(messages.exitButtonAlt)}
|
||||
/>
|
||||
<ActionRow>
|
||||
<h2 className="h3 col pl-0">
|
||||
<TitleHeader isInitialized={isInitialized} />
|
||||
</h2>
|
||||
<ActionRow.Spacer />
|
||||
<Stack direction="horizontal" reversed gap={1}>
|
||||
<IconButton
|
||||
src={Close}
|
||||
iconAs={Icon}
|
||||
onClick={confirmCancelIfDirty}
|
||||
alt={intl.formatMessage(messages.exitButtonAlt)}
|
||||
autoFocus
|
||||
/>
|
||||
<IconButton
|
||||
src={isFullscreen ? CloseFullscreen : OpenInFull}
|
||||
iconAs={Icon}
|
||||
alt={intl.formatMessage(messages.toggleFullscreenButtonLabel)}
|
||||
onClick={toggleFullscreen}
|
||||
/>
|
||||
</Stack>
|
||||
</ActionRow>
|
||||
</div>
|
||||
</ModalDialog.Header>
|
||||
<EditorModalBody>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
||||
cancelConfirmTitle: {
|
||||
id: 'authoring.editorContainer.cancelConfirm.title',
|
||||
defaultMessage: 'Exit the editor?',
|
||||
@@ -57,6 +56,11 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Save',
|
||||
description: 'Label for Save button',
|
||||
},
|
||||
toggleFullscreenButtonLabel: {
|
||||
id: 'authoring.editorHeader.toggleFullscreen.label',
|
||||
defaultMessage: 'Toggle Fullscreen',
|
||||
description: 'Label for toggle fullscreen button',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -36,6 +36,11 @@ const messages = defineMessages({
|
||||
defaultMessage: 'An unexpected error occurred in the editor',
|
||||
description: 'Generic error message shown when an error occurs in the Advanced Editor.',
|
||||
},
|
||||
advancedEditorFullscreenButtonAlt: {
|
||||
id: 'authoring.advancedEditor.fullscreenButton.alt',
|
||||
defaultMessage: 'Toggle Fullscreen',
|
||||
description: 'Alt text for the Fullscreen button',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -20,7 +20,11 @@ export const SANDBOX_OPTIONS = [
|
||||
].join(' ');
|
||||
|
||||
const ModalIframe = forwardRef<HTMLIFrameElement, ModalIframeProps>(
|
||||
({ title, className, ...props }, ref: ForwardedRef<HTMLIFrameElement>) => (
|
||||
({
|
||||
title,
|
||||
className = '',
|
||||
...props
|
||||
}, ref: ForwardedRef<HTMLIFrameElement>) => (
|
||||
<iframe
|
||||
title={title}
|
||||
className={classNames('modal-iframe', className)}
|
||||
@@ -36,8 +40,4 @@ const ModalIframe = forwardRef<HTMLIFrameElement, ModalIframeProps>(
|
||||
),
|
||||
);
|
||||
|
||||
ModalIframe.defaultProps = {
|
||||
className: '',
|
||||
};
|
||||
|
||||
export default ModalIframe;
|
||||
|
||||
@@ -2,8 +2,10 @@ import { getConfig } from '@edx/frontend-platform';
|
||||
import React from 'react';
|
||||
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import EditorPage from '../../editors/EditorPage';
|
||||
import { getBlockType } from '../../generic/key-utils';
|
||||
|
||||
import EditorPage from '@src/editors/EditorPage';
|
||||
import { getBlockType } from '@src/generic/key-utils';
|
||||
|
||||
import { useLibraryContext } from '../common/context/LibraryContext';
|
||||
import { invalidateComponentData } from '../data/apiHooks';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user