Skip to content

Commit 08a7c1e

Browse files
committed
feat: add fullscreen button to editor
1 parent f900ace commit 08a7c1e

File tree

6 files changed

+106
-28
lines changed

6 files changed

+106
-28
lines changed

src/editors/AdvancedEditor.tsx

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
import React, { useEffect } from 'react';
22
import { getConfig } from '@edx/frontend-platform';
33
import { useIntl } from '@edx/frontend-platform/i18n';
4-
import { useToggle } from '@openedx/paragon';
4+
import {
5+
ActionRow,
6+
Icon,
7+
IconButton,
8+
ModalDialog,
9+
ModalCloseButton,
10+
Stack,
11+
useToggle,
12+
} from '@openedx/paragon';
13+
import { Close, Fullscreen, FullscreenExit } from '@openedx/paragon/icons';
514

615
import { LibraryBlock } from '../library-authoring/LibraryBlock';
716
import { EditorModalWrapper } from './containers/EditorContainer';
@@ -11,6 +20,8 @@ import messages from './messages';
1120
import CancelConfirmModal from './containers/EditorContainer/components/CancelConfirmModal';
1221
import { IframeProvider } from '../generic/hooks/context/iFrameContext';
1322

23+
import editorModalWrapperMessages from './containers/EditorContainer/messages';
24+
1425
interface AdvancedEditorProps {
1526
usageKey: string,
1627
onClose: (() => void) | null,
@@ -20,6 +31,7 @@ const AdvancedEditor = ({ usageKey, onClose }: AdvancedEditorProps) => {
2031
const intl = useIntl();
2132
const { showToast } = React.useContext(ToastContext);
2233
const [isCancelConfirmOpen, openCancelConfirmModal, closeCancelConfirmModal] = useToggle(false);
34+
const [isFullscreen, , , toggleFullscreen] = useToggle(false);
2335

2436
useEffect(() => {
2537
const handleIframeMessage = (event) => {
@@ -49,7 +61,28 @@ const AdvancedEditor = ({ usageKey, onClose }: AdvancedEditorProps) => {
4961

5062
return (
5163
<>
52-
<EditorModalWrapper onClose={openCancelConfirmModal}>
64+
<EditorModalWrapper onClose={openCancelConfirmModal} fullscreen={isFullscreen}>
65+
<ModalDialog.Header>
66+
<ActionRow>
67+
<ModalDialog.Title>
68+
{intl.formatMessage(editorModalWrapperMessages.modalTitle)}
69+
</ModalDialog.Title>
70+
<ActionRow.Spacer />
71+
<Stack direction="horizontal" reversed>
72+
<ModalCloseButton
73+
as={IconButton}
74+
src={Close}
75+
iconAs={Icon}
76+
/>
77+
<IconButton
78+
src={isFullscreen ? FullscreenExit : Fullscreen}
79+
iconAs={Icon}
80+
alt={intl.formatMessage(messages.advancedEditorFullscreenButtonAlt)}
81+
onClick={toggleFullscreen}
82+
/>
83+
</Stack>
84+
</ActionRow>
85+
</ModalDialog.Header>
5386
<IframeProvider>
5487
<LibraryBlock
5588
usageKey={usageKey}

src/editors/containers/EditorContainer/index.tsx

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,40 +7,60 @@ import {
77
Icon,
88
IconButton,
99
ModalDialog,
10+
ModalCloseButton,
1011
Spinner,
12+
Stack,
1113
Toast,
14+
useToggle,
1215
} from '@openedx/paragon';
13-
import { Close } from '@openedx/paragon/icons';
16+
import { Close, Fullscreen, FullscreenExit } from '@openedx/paragon/icons';
1417
import { useIntl, FormattedMessage } from '@edx/frontend-platform/i18n';
1518

19+
import { parseErrorMsg } from '@src/library-authoring/add-content/AddContent';
20+
import libraryMessages from '@src/library-authoring/add-content/messages';
21+
import usePromptIfDirty from '@src/generic/promptIfDirty/usePromptIfDirty';
22+
1623
import { EditorComponent } from '../../EditorComponent';
1724
import TitleHeader from './components/TitleHeader';
1825
import * as hooks from './hooks';
1926
import messages from './messages';
20-
import { parseErrorMsg } from '../../../library-authoring/add-content/AddContent';
21-
import libraryMessages from '../../../library-authoring/add-content/messages';
2227

2328
import './index.scss';
24-
import usePromptIfDirty from '../../../generic/promptIfDirty/usePromptIfDirty';
2529
import CancelConfirmModal from './components/CancelConfirmModal';
2630

2731
interface WrapperProps {
2832
children: React.ReactNode;
33+
fullscreen?: boolean;
2934
}
3035

31-
export const EditorModalWrapper: React.FC<WrapperProps & { onClose: () => void }> = ({ children, onClose }) => {
36+
export const EditorModalWrapper: React.FC<WrapperProps & { onClose: () => void }> = (
37+
{
38+
children,
39+
onClose,
40+
fullscreen = false,
41+
},
42+
) => {
3243
const intl = useIntl();
3344

3445
const title = intl.formatMessage(messages.modalTitle);
3546
return (
36-
<ModalDialog isOpen size="xl" isOverflowVisible={false} onClose={onClose} title={title}>{children}</ModalDialog>
47+
<ModalDialog
48+
isOpen
49+
onClose={onClose}
50+
title={title}
51+
size={fullscreen ? 'fullscreen' : 'xl'}
52+
isOverflowVisible={false}
53+
hasCloseButton={false}
54+
>
55+
{children}
56+
</ModalDialog>
3757
);
3858
};
3959

40-
export const EditorModalBody: React.FC<WrapperProps> = ({ children }) => <ModalDialog.Body className="pb-0">{ children }</ModalDialog.Body>;
60+
export const EditorModalBody: React.FC<WrapperProps> = ({ children }) => <ModalDialog.Body className="pb-0">{children}</ModalDialog.Body>;
4161

4262
// eslint-disable-next-line react/jsx-no-useless-fragment
43-
export const FooterWrapper: React.FC<WrapperProps> = ({ children }) => <>{ children }</>;
63+
export const FooterWrapper: React.FC<WrapperProps> = ({ children }) => <>{children}</>;
4464

4565
interface Props extends EditorComponent {
4666
children: React.ReactNode;
@@ -63,6 +83,7 @@ const EditorContainer: React.FC<Props> = ({
6383
const [saved, setSaved] = React.useState(false);
6484
const isInitialized = hooks.isInitialized();
6585
const { isCancelConfirmOpen, openCancelConfirmModal, closeCancelConfirmModal } = hooks.cancelConfirmModalToggle();
86+
const [isFullscreen, , , toggleFullscreen] = useToggle(false);
6687
const handleCancel = hooks.handleCancel({ onClose, returnFunction });
6788
const { createFailed, createFailedError } = hooks.createFailed();
6889
const disableSave = !isInitialized;
@@ -97,8 +118,9 @@ const EditorContainer: React.FC<Props> = ({
97118
handleCancel();
98119
}
99120
};
121+
100122
return (
101-
<EditorModalWrapper onClose={confirmCancelIfDirty}>
123+
<EditorModalWrapper onClose={confirmCancelIfDirty} fullscreen={isFullscreen}>
102124
{createFailed && (
103125
<Toast show onClose={clearCreateFailed}>
104126
{parseErrorMsg(
@@ -127,15 +149,27 @@ const EditorContainer: React.FC<Props> = ({
127149
/>
128150
<ModalDialog.Header className="shadow-sm zindex-10">
129151
<div className="d-flex flex-row justify-content-between">
130-
<h2 className="h3 col pl-0">
131-
<TitleHeader isInitialized={isInitialized} />
132-
</h2>
133-
<IconButton
134-
src={Close}
135-
iconAs={Icon}
136-
onClick={confirmCancelIfDirty}
137-
alt={intl.formatMessage(messages.exitButtonAlt)}
138-
/>
152+
<ActionRow>
153+
<h2 className="h3 col pl-0">
154+
<TitleHeader isInitialized={isInitialized} />
155+
</h2>
156+
<ActionRow.Spacer />
157+
<Stack direction="horizontal" reversed>
158+
<IconButton
159+
src={Close}
160+
iconAs={Icon}
161+
onClick={confirmCancelIfDirty}
162+
alt={intl.formatMessage(messages.exitButtonAlt)}
163+
autoFocus
164+
/>
165+
<IconButton
166+
src={isFullscreen ? FullscreenExit : Fullscreen}
167+
iconAs={Icon}
168+
alt={intl.formatMessage(messages.toggleFullscreenButtonLabel)}
169+
onClick={toggleFullscreen}
170+
/>
171+
</Stack>
172+
</ActionRow>
139173
</div>
140174
</ModalDialog.Header>
141175
<EditorModalBody>

src/editors/containers/EditorContainer/messages.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { defineMessages } from '@edx/frontend-platform/i18n';
22

33
const messages = defineMessages({
4-
54
cancelConfirmTitle: {
65
id: 'authoring.editorContainer.cancelConfirm.title',
76
defaultMessage: 'Exit the editor?',
@@ -57,6 +56,11 @@ const messages = defineMessages({
5756
defaultMessage: 'Save',
5857
description: 'Label for Save button',
5958
},
59+
toggleFullscreenButtonLabel: {
60+
id: 'authoring.editorfooter.toggleFullscreen.label',
61+
defaultMessage: 'Toggle Fullscreen',
62+
description: 'Label for toggle fullscreen button',
63+
},
6064
});
6165

6266
export default messages;

src/editors/messages.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ const messages = defineMessages({
3636
defaultMessage: 'An unexpected error occurred in the editor',
3737
description: 'Generic error message shown when an error occurs in the Advanced Editor.',
3838
},
39+
advancedEditorFullscreenButtonAlt: {
40+
id: 'authoring.advancedEditor.fullscreenButton.alt',
41+
defaultMessage: 'Toggle Fullscreen',
42+
description: 'Alt text for the Fullscreen button',
43+
},
3944
});
4045

4146
export default messages;

src/generic/modal-iframe/index.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@ export const SANDBOX_OPTIONS = [
2020
].join(' ');
2121

2222
const ModalIframe = forwardRef<HTMLIFrameElement, ModalIframeProps>(
23-
({ title, className, ...props }, ref: ForwardedRef<HTMLIFrameElement>) => (
23+
({
24+
title,
25+
className = '',
26+
...props
27+
}, ref: ForwardedRef<HTMLIFrameElement>) => (
2428
<iframe
2529
title={title}
2630
className={classNames('modal-iframe', className)}
@@ -36,8 +40,4 @@ const ModalIframe = forwardRef<HTMLIFrameElement, ModalIframeProps>(
3640
),
3741
);
3842

39-
ModalIframe.defaultProps = {
40-
className: '',
41-
};
42-
4343
export default ModalIframe;

src/library-authoring/components/ComponentEditorModal.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import { getConfig } from '@edx/frontend-platform';
22
import React from 'react';
33

44
import { useQueryClient } from '@tanstack/react-query';
5-
import EditorPage from '../../editors/EditorPage';
6-
import { getBlockType } from '../../generic/key-utils';
5+
6+
import EditorPage from '@src/editors/EditorPage';
7+
import { getBlockType } from '@src/generic/key-utils';
8+
79
import { useLibraryContext } from '../common/context/LibraryContext';
810
import { invalidateComponentData } from '../data/apiHooks';
911

0 commit comments

Comments
 (0)