Merge pull request #23697 from overleaf/dp-layout-switcher

Add layout switcher button to toolbar

GitOrigin-RevId: 33bf664c2e2cb5c2f992b70fd9ca90b5dfe2ee44
This commit is contained in:
David
2025-02-20 10:53:51 +00:00
committed by Copybot
parent 2d936f4bf8
commit 8a9297a67e
11 changed files with 282 additions and 46 deletions

View File

@@ -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": "",

View File

@@ -18,6 +18,7 @@ export default /** @type {const} */ ([
'rate_review',
'report',
'settings',
'space_dashboard',
'table_chart',
'upload_file',
'web_asset',

View File

@@ -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 (
<div className="ide-redesign-main">
@@ -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,
})}
>
<RailLayout />
<Panel id="ide-redesign-editor-panel" order={2}>
<Panel
id="ide-redesign-editor-panel"
order={1}
className={classNames({
'ide-panel-group-resizing': resizing,
hidden: !editorIsOpen,
})}
minSize={5}
defaultSize={50}
>
<div className="ide-redesign-editor-container">
<Editor />
</div>
</Panel>
<HorizontalResizeHandle
resizable
resizable={pdfLayout === 'sideBySide'}
onDragging={setResizing}
onDoubleClick={togglePdfPane}
hitAreaMargins={{ coarse: 0, fine: 0 }}
className={classNames({
hidden: !editorIsOpen,
})}
>
<HorizontalToggler
id="ide-redesign-pdf-panel"
@@ -55,6 +77,8 @@ export default function MainLayout() {
className="ide-redesign-pdf-container"
id="ide-redesign-pdf-panel"
order={2}
defaultSize={50}
minSize={5}
ref={pdfPanelRef}
onExpand={handlePdfPaneExpand}
onCollapse={handlePdfPaneCollapse}

View File

@@ -0,0 +1,46 @@
import OLButton from '@/features/ui/components/ol/ol-button'
import MaterialIcon from '@/shared/components/material-icon'
import {
Dropdown,
DropdownMenu,
DropdownToggle,
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
import React, { forwardRef } from 'react'
import ChangeLayoutOptions from './change-layout-options'
const LayoutDropdownToggleButton = forwardRef<
HTMLButtonElement,
{
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void
}
>(({ onClick }, ref) => {
return (
<OLButton
size="sm"
variant="ghost"
className="ide-redesign-toolbar-button-subdued"
ref={ref}
onClick={onClick}
leadingIcon={<MaterialIcon unfilled type="space_dashboard" />}
/>
)
})
LayoutDropdownToggleButton.displayName = 'LayoutDropdownToggleButton'
export default function ChangeLayoutButton() {
return (
<div className="ide-redesign-toolbar-button-container">
<Dropdown className="toolbar-item layout-dropdown" align="end">
<DropdownToggle
id="layout-dropdown-btn"
className="btn-full-height"
as={LayoutDropdownToggleButton}
/>
<DropdownMenu>
<ChangeLayoutOptions />
</DropdownMenu>
</Dropdown>
</div>
)
}

View File

@@ -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 = (
<Spinner animation="border" aria-hidden="true" size="sm" role="status" />
)
} else if (active) {
trailingIcon = 'check'
}
return (
<DropdownItem
active={active}
aria-current={active}
trailingIcon={trailingIcon}
disabled={disabled}
onClick={onClick}
leadingIcon={leadingIcon}
>
{children}
</DropdownItem>
)
}
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 (
<>
<DropdownHeader>{t('layout_options')}</DropdownHeader>
<LayoutDropdownItem
onClick={() => handleChangeLayout('sideBySide')}
active={activeLayoutOption === 'sideBySide'}
leadingIcon="splitscreen_right"
>
{t('split_view')}
</LayoutDropdownItem>
<LayoutDropdownItem
onClick={() => handleChangeLayout('flat', 'editor')}
active={activeLayoutOption === 'editorOnly'}
leadingIcon="edit"
>
{t('editor_only')}
</LayoutDropdownItem>
<LayoutDropdownItem
onClick={() => handleChangeLayout('flat', 'pdf')}
active={activeLayoutOption === 'pdfOnly'}
leadingIcon="picture_as_pdf"
>
{t('pdf_only')}
</LayoutDropdownItem>
<LayoutDropdownItem
onClick={() => handleDetach()}
active={activeLayoutOption === 'detachedPdf' && detachIsLinked}
disabled={!detachable}
leadingIcon="open_in_new"
processing={waitingForDetachedLink}
>
{t('open_pdf_in_separate_tab')}
</LayoutDropdownItem>
</>
)
}

View File

@@ -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"
>
<MenuBarOption title="PDF only" />
<ChangeLayoutOptions />
</MenuBarDropdown>
<MenuBarDropdown
title={t('insert')}

View File

@@ -8,6 +8,7 @@ import ShareProjectButton from './share-project-button'
import importOverleafModules from '../../../../../macros/import-overleaf-module.macro'
import { useEditorContext } from '@/shared/context/editor-context'
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
import ChangeLayoutButton from './change-layout-button'
const [publishModalModules] = importOverleafModules('publishModal')
const SubmitProjectButton = publishModalModules?.import.NewPublishToolbarButton
@@ -66,6 +67,7 @@ const ToolbarButtons = () => {
/>
</OLTooltip>
</div>
<ChangeLayoutButton />
{shouldDisplaySubmitButton && <SubmitProjectButton />}
<ShareProjectButton />
</div>

View File

@@ -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<ImperativePanelHandle>(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,
}
}

View File

@@ -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);

View File

@@ -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 couldnt 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",