diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json
index 34d8431612..8db302aedb 100644
--- a/services/web/frontend/extracted-translations.json
+++ b/services/web/frontend/extracted-translations.json
@@ -463,6 +463,7 @@
"editor_and_pdf": "",
"editor_disconected_click_to_reconnect": "",
"editor_limit_exceeded_in_this_project": "",
+ "editor_only": "",
"editor_only_hide_pdf": "",
"editor_settings": "",
"editor_theme": "",
@@ -858,6 +859,7 @@
"latex_places_figures_according_to_a_special_algorithm": "",
"latex_places_tables_according_to_a_special_algorithm": "",
"layout": "",
+ "layout_options": "",
"layout_processing": "",
"learn_more": "",
"learn_more_about_account": "",
@@ -1091,6 +1093,7 @@
"open_file": "",
"open_link": "",
"open_path": "",
+ "open_pdf_in_separate_tab": "",
"open_project": "",
"open_survey": "",
"open_target": "",
@@ -1150,6 +1153,7 @@
"pdf_compile_try_again": "",
"pdf_couldnt_compile": "",
"pdf_in_separate_tab": "",
+ "pdf_only": "",
"pdf_only_hide_editor": "",
"pdf_preview_error": "",
"pdf_rendering_error": "",
@@ -1531,6 +1535,7 @@
"sort_projects": "",
"source": "",
"spell_check": "",
+ "split_view": "",
"sso": "",
"sso_active": "",
"sso_already_setup_good_to_go": "",
diff --git a/services/web/frontend/fonts/material-symbols/MaterialSymbolsRoundedUnfilledPartialSlice.woff2 b/services/web/frontend/fonts/material-symbols/MaterialSymbolsRoundedUnfilledPartialSlice.woff2
index bf4ba34c6b..a7c79e29a9 100644
Binary files a/services/web/frontend/fonts/material-symbols/MaterialSymbolsRoundedUnfilledPartialSlice.woff2 and b/services/web/frontend/fonts/material-symbols/MaterialSymbolsRoundedUnfilledPartialSlice.woff2 differ
diff --git a/services/web/frontend/fonts/material-symbols/unfilled-symbols.mjs b/services/web/frontend/fonts/material-symbols/unfilled-symbols.mjs
index c727e77cf0..9c4a941859 100644
--- a/services/web/frontend/fonts/material-symbols/unfilled-symbols.mjs
+++ b/services/web/frontend/fonts/material-symbols/unfilled-symbols.mjs
@@ -18,6 +18,7 @@ export default /** @type {const} */ ([
'rate_review',
'report',
'settings',
+ 'space_dashboard',
'table_chart',
'upload_file',
'web_asset',
diff --git a/services/web/frontend/js/features/ide-redesign/components/main-layout.tsx b/services/web/frontend/js/features/ide-redesign/components/main-layout.tsx
index 71bbbe33c3..9d847ee95e 100644
--- a/services/web/frontend/js/features/ide-redesign/components/main-layout.tsx
+++ b/services/web/frontend/js/features/ide-redesign/components/main-layout.tsx
@@ -7,17 +7,26 @@ import { RailLayout } from './rail'
import { Toolbar } from './toolbar/toolbar'
import { HorizontalToggler } from '@/features/ide-react/components/resize/horizontal-toggler'
import { useTranslation } from 'react-i18next'
-import { usePdfPane } from '../hooks/use-pdf-pane'
+import { usePdfPane } from '@/features/ide-react/hooks/use-pdf-pane'
+import { useLayoutContext } from '@/shared/context/layout-context'
+import { useState } from 'react'
export default function MainLayout() {
+ const [resizing, setResizing] = useState(false)
const {
- isOpen: isPdfOpen,
- setIsOpen: setIsPdfOpen,
- panelRef: pdfPanelRef,
- handlePaneCollapse: handlePdfPaneCollapse,
- handlePaneExpand: handlePdfPaneExpand,
- togglePane: togglePdfPane,
+ togglePdfPane,
+ handlePdfPaneExpand,
+ handlePdfPaneCollapse,
+ setPdfIsOpen: setIsPdfOpen,
+ pdfIsOpen: isPdfOpen,
+ pdfPanelRef,
} = usePdfPane()
+
+ const { view, pdfLayout } = useLayoutContext()
+
+ const editorIsOpen =
+ view === 'editor' || view === 'file' || pdfLayout === 'sideBySide'
+
const { t } = useTranslation()
return (
@@ -27,19 +36,32 @@ export default function MainLayout() {
autoSaveId="ide-redesign-outer-layout"
direction="horizontal"
className={classNames('ide-redesign-inner', {
- 'ide-panel-group-resizing': false,
+ 'ide-panel-group-resizing': resizing,
})}
>
-
+
) => void
+ }
+>(({ onClick }, ref) => {
+ return (
+ }
+ />
+ )
+})
+
+LayoutDropdownToggleButton.displayName = 'LayoutDropdownToggleButton'
+
+export default function ChangeLayoutButton() {
+ return (
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/services/web/frontend/js/features/ide-redesign/components/toolbar/change-layout-options.tsx b/services/web/frontend/js/features/ide-redesign/components/toolbar/change-layout-options.tsx
new file mode 100644
index 0000000000..3c3126e8da
--- /dev/null
+++ b/services/web/frontend/js/features/ide-redesign/components/toolbar/change-layout-options.tsx
@@ -0,0 +1,175 @@
+import {
+ DropdownItem,
+ DropdownHeader,
+} from '@/features/ui/components/bootstrap-5/dropdown-menu'
+import {
+ IdeLayout,
+ IdeView,
+ useLayoutContext,
+} from '@/shared/context/layout-context'
+import React, { useCallback } from 'react'
+import { useTranslation } from 'react-i18next'
+import * as eventTracking from '../../../../infrastructure/event-tracking'
+import useEventListener from '@/shared/hooks/use-event-listener'
+import { DetachRole } from '@/shared/context/detach-context'
+import { Spinner } from 'react-bootstrap-5'
+
+type LayoutOption = 'sideBySide' | 'editorOnly' | 'pdfOnly' | 'detachedPdf'
+
+const getActiveLayoutOption = ({
+ pdfLayout,
+ view,
+ detachRole,
+}: {
+ pdfLayout: IdeLayout
+ view: IdeView | null
+ detachRole?: DetachRole
+}): LayoutOption | null => {
+ if (view === 'history') {
+ return null
+ }
+
+ if (detachRole === 'detacher') {
+ return 'detachedPdf'
+ }
+
+ if (pdfLayout === 'flat' && (view === 'editor' || view === 'file')) {
+ return 'editorOnly'
+ }
+
+ if (pdfLayout === 'flat' && view === 'pdf') {
+ return 'pdfOnly'
+ }
+
+ if (pdfLayout === 'sideBySide') {
+ return 'sideBySide'
+ }
+
+ return null
+}
+
+const LayoutDropdownItem = ({
+ active,
+ disabled = false,
+ processing = false,
+ leadingIcon,
+ onClick,
+ children,
+}: {
+ active: boolean
+ leadingIcon: string
+ onClick: () => void
+ children: React.ReactNode
+ processing?: boolean
+ disabled?: boolean
+}) => {
+ let trailingIcon: string | React.ReactNode | null = null
+ if (processing) {
+ trailingIcon = (
+
+ )
+ } else if (active) {
+ trailingIcon = 'check'
+ }
+
+ return (
+
+ {children}
+
+ )
+}
+
+export default function ChangeLayoutOptions() {
+ const {
+ reattach,
+ detach,
+ detachIsLinked,
+ detachRole,
+ changeLayout,
+ view,
+ pdfLayout,
+ } = useLayoutContext()
+
+ const handleDetach = useCallback(() => {
+ detach()
+ eventTracking.sendMB('project-layout-detach')
+ }, [detach])
+
+ const handleReattach = useCallback(() => {
+ if (detachRole !== 'detacher') {
+ return
+ }
+ reattach()
+ eventTracking.sendMB('project-layout-reattach')
+ }, [detachRole, reattach])
+
+ // reattach when the PDF pane opens
+ useEventListener('ui:pdf-open', handleReattach)
+
+ const handleChangeLayout = useCallback(
+ (newLayout: IdeLayout, newView?: IdeView) => {
+ handleReattach()
+ changeLayout(newLayout, newView)
+ eventTracking.sendMB('project-layout-change', {
+ layout: newLayout,
+ view: newView,
+ })
+ },
+ [changeLayout, handleReattach]
+ )
+
+ const { t } = useTranslation()
+
+ const detachable = 'BroadcastChannel' in window
+
+ const activeLayoutOption = getActiveLayoutOption({
+ pdfLayout,
+ view,
+ detachRole,
+ })
+
+ const waitingForDetachedLink = !detachIsLinked && detachRole === 'detacher'
+
+ return (
+ <>
+ {t('layout_options')}
+ handleChangeLayout('sideBySide')}
+ active={activeLayoutOption === 'sideBySide'}
+ leadingIcon="splitscreen_right"
+ >
+ {t('split_view')}
+
+ handleChangeLayout('flat', 'editor')}
+ active={activeLayoutOption === 'editorOnly'}
+ leadingIcon="edit"
+ >
+ {t('editor_only')}
+
+ handleChangeLayout('flat', 'pdf')}
+ active={activeLayoutOption === 'pdfOnly'}
+ leadingIcon="picture_as_pdf"
+ >
+ {t('pdf_only')}
+
+ handleDetach()}
+ active={activeLayoutOption === 'detachedPdf' && detachIsLinked}
+ disabled={!detachable}
+ leadingIcon="open_in_new"
+ processing={waitingForDetachedLink}
+ >
+ {t('open_pdf_in_separate_tab')}
+
+ >
+ )
+}
diff --git a/services/web/frontend/js/features/ide-redesign/components/toolbar/menu-bar.tsx b/services/web/frontend/js/features/ide-redesign/components/toolbar/menu-bar.tsx
index 502cf44323..ce6626ee86 100644
--- a/services/web/frontend/js/features/ide-redesign/components/toolbar/menu-bar.tsx
+++ b/services/web/frontend/js/features/ide-redesign/components/toolbar/menu-bar.tsx
@@ -3,6 +3,7 @@ import { MenuBar } from '@/shared/components/menu-bar/menu-bar'
import { MenuBarDropdown } from '@/shared/components/menu-bar/menu-bar-dropdown'
import { MenuBarOption } from '@/shared/components/menu-bar/menu-bar-option'
import { useTranslation } from 'react-i18next'
+import ChangeLayoutOptions from './change-layout-options'
export const ToolbarMenuBar = () => {
const { t } = useTranslation()
@@ -36,7 +37,7 @@ export const ToolbarMenuBar = () => {
id="view"
className="ide-redesign-toolbar-dropdown-toggle-subdued"
>
-
+
{
/>
+
{shouldDisplaySubmitButton && }
diff --git a/services/web/frontend/js/features/ide-redesign/hooks/use-pdf-pane.tsx b/services/web/frontend/js/features/ide-redesign/hooks/use-pdf-pane.tsx
deleted file mode 100644
index 8215af3fdd..0000000000
--- a/services/web/frontend/js/features/ide-redesign/hooks/use-pdf-pane.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { useCallback, useRef, useState } from 'react'
-import useCollapsiblePanel from '@/features/ide-react/hooks/use-collapsible-panel'
-import { ImperativePanelHandle } from 'react-resizable-panels'
-
-// FIXME: This is temporary, to avoid clashing with the existing usePdfPane
-// which uses the layout context. That's the correct approach.
-export const usePdfPane = () => {
- const [isOpen, setIsOpen] = useState(true)
- const [resizing, setResizing] = useState(false)
- const panelRef = useRef(null)
- useCollapsiblePanel(isOpen, panelRef)
-
- const togglePane = useCallback(() => {
- setIsOpen(value => !value)
- }, [])
-
- const handlePaneExpand = useCallback(() => {
- setIsOpen(true)
- }, [])
-
- const handlePaneCollapse = useCallback(() => {
- setIsOpen(false)
- }, [])
-
- return {
- isOpen,
- setIsOpen,
- panelRef,
- togglePane,
- handlePaneExpand,
- handlePaneCollapse,
- resizing,
- setResizing,
- }
-}
diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/ide.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/ide.scss
index a4649f102f..8513e2fca1 100644
--- a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/ide.scss
+++ b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/ide.scss
@@ -45,6 +45,18 @@ $editor-toggler-bg-dark-color: color.adjust(
}
}
+.ide-redesign-main {
+ .ide-panel-group-resizing {
+ background-color: var(--white);
+
+ // Hide panel contents while resizing
+ .ide-redesign-editor-content,
+ .pdf {
+ display: none !important;
+ }
+ }
+}
+
.global-alerts {
height: 0;
margin-top: var(--spacing-01);
diff --git a/services/web/locales/en.json b/services/web/locales/en.json
index 5b730599d9..38af911789 100644
--- a/services/web/locales/en.json
+++ b/services/web/locales/en.json
@@ -598,6 +598,7 @@
"editor_and_pdf": "Editor & PDF",
"editor_disconected_click_to_reconnect": "Editor disconnected, click anywhere to reconnect.",
"editor_limit_exceeded_in_this_project": "Too many editors in this project",
+ "editor_only": "Editor only",
"editor_only_hide_pdf": "Editor only <0>(hide PDF)0>",
"editor_settings": "Editor settings",
"editor_theme": "Editor theme",
@@ -1131,6 +1132,7 @@
"latex_templates_for_journal_articles": "LaTeX templates for journal articles, academic papers, CVs and résumés, presentations, and more.",
"latex_templates_sentence_case": "LaTeX templates",
"layout": "Layout",
+ "layout_options": "Layout options",
"layout_processing": "Layout processing",
"ldap": "LDAP",
"ldap_create_admin_instructions": "Choose an email address for the first __appName__ admin account. This should correspond to an account in the LDAP system. You will then be asked to log in with this account.",
@@ -1448,6 +1450,7 @@
"open_file": "Edit file",
"open_link": "Go to page",
"open_path": "Open __path__",
+ "open_pdf_in_separate_tab": "Open PDF in separate tab",
"open_project": "Open Project",
"open_survey": "Open survey",
"open_target": "Go to target",
@@ -1537,6 +1540,7 @@
"pdf_compile_try_again": "Please wait for your other compile to finish before trying again.",
"pdf_couldnt_compile": "PDF couldn’t compile",
"pdf_in_separate_tab": "PDF in separate tab",
+ "pdf_only": "PDF only",
"pdf_only_hide_editor": "PDF only <0>(hide editor)0>",
"pdf_preview_error": "There was a problem displaying the compilation results for this project.",
"pdf_rendering_error": "PDF Rendering Error",
@@ -2007,6 +2011,7 @@
"sort_projects": "Sort projects",
"source": "Source",
"spell_check": "Spell check",
+ "split_view": "Split view",
"sso": "SSO",
"sso_account_already_linked": "Account already linked to another __appName__ user",
"sso_active": "SSO active",