React IDE page shell (#14988)
* React IDE page shell * Set the maximum height of the symbol palette to 336px * Tidy export * Remove unnecessary destructuring * Update comment * Optimize toggle Co-authored-by: Alf Eaton <alf.eaton@overleaf.com> * Change snap-to-collapse threshold to 5% * Synchronize left column width between history and editor views and remove duplication in ide-page * Replace resizer dots with SVG * Rermove unnecessary import and comment the remaining ones * Use block prepend to avoid duplication * Improve vertical content divider styling * Implement fixed width during container resize on left column * Change IDE page file extension * Refactor fixed-size panel into a hook and use for chat panel --------- Co-authored-by: Alf Eaton <alf.eaton@overleaf.com> GitOrigin-RevId: aa881e48a2838a192b6f8f9e16e561f5cd706bd3
This commit is contained in:
committed by
Copybot
co-authored by
Alf Eaton
parent
afcac7af86
commit
ea1fc5f74e
@@ -0,0 +1,11 @@
|
||||
import LayoutWithPlaceholders from '@/features/ide-react/components/layout/layout-with-placeholders'
|
||||
|
||||
// This is filled with placeholder content while the real content is migrated
|
||||
// away from Angular
|
||||
export default function IdePage() {
|
||||
return (
|
||||
<div id="ide-react-page">
|
||||
<LayoutWithPlaceholders shouldPersistLayout />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
import { useState } from 'react'
|
||||
import PlaceholderHeader from '@/features/ide-react/components/layout/placeholder/placeholder-header'
|
||||
import PlaceholderChat from '@/features/ide-react/components/layout/placeholder/placeholder-chat'
|
||||
import PlaceholderHistory from '@/features/ide-react/components/layout/placeholder/placeholder-history'
|
||||
import PlaceholderEditorMainContent from '@/features/ide-react/components/layout/placeholder/placeholder-editor-main-content'
|
||||
import MainLayout from '@/features/ide-react/components/layout/main-layout'
|
||||
|
||||
export default function LayoutWithPlaceholders({
|
||||
shouldPersistLayout,
|
||||
}: {
|
||||
shouldPersistLayout: boolean
|
||||
}) {
|
||||
const [chatIsOpen, setChatIsOpen] = useState(false)
|
||||
const [historyIsOpen, setHistoryIsOpen] = useState(false)
|
||||
const [leftColumnDefaultSize, setLeftColumnDefaultSize] = useState(20)
|
||||
|
||||
const headerContent = (
|
||||
<PlaceholderHeader
|
||||
chatIsOpen={chatIsOpen}
|
||||
setChatIsOpen={setChatIsOpen}
|
||||
historyIsOpen={historyIsOpen}
|
||||
setHistoryIsOpen={setHistoryIsOpen}
|
||||
/>
|
||||
)
|
||||
const chatContent = <PlaceholderChat />
|
||||
const mainContent = historyIsOpen ? (
|
||||
<PlaceholderHistory
|
||||
shouldPersistLayout
|
||||
leftColumnDefaultSize={leftColumnDefaultSize}
|
||||
setLeftColumnDefaultSize={setLeftColumnDefaultSize}
|
||||
/>
|
||||
) : (
|
||||
<PlaceholderEditorMainContent
|
||||
shouldPersistLayout
|
||||
leftColumnDefaultSize={leftColumnDefaultSize}
|
||||
setLeftColumnDefaultSize={setLeftColumnDefaultSize}
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
<MainLayout
|
||||
headerContent={headerContent}
|
||||
chatContent={chatContent}
|
||||
mainContent={mainContent}
|
||||
chatIsOpen={chatIsOpen}
|
||||
shouldPersistLayout={shouldPersistLayout}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Panel, PanelGroup } from 'react-resizable-panels'
|
||||
import { ReactNode } from 'react'
|
||||
import { HorizontalResizeHandle } from '../resize/horizontal-resize-handle'
|
||||
import useFixedSizeColumn from '@/features/ide-react/hooks/use-fixed-size-column'
|
||||
import useCollapsiblePanel from '@/features/ide-react/hooks/use-collapsible-panel'
|
||||
|
||||
const CHAT_DEFAULT_SIZE = 20
|
||||
|
||||
type PageProps = {
|
||||
headerContent: ReactNode
|
||||
chatContent: ReactNode
|
||||
mainContent: ReactNode
|
||||
chatIsOpen: boolean
|
||||
shouldPersistLayout: boolean
|
||||
}
|
||||
|
||||
// The main area below the header is split into two: the main content and chat.
|
||||
// The reason for not splitting the left column containing the file tree and
|
||||
// outline here is that the history view has its own file tree, so it is more
|
||||
// convenient to replace the whole of the main content when in history view.
|
||||
export default function MainLayout({
|
||||
headerContent,
|
||||
chatContent,
|
||||
mainContent,
|
||||
chatIsOpen,
|
||||
shouldPersistLayout,
|
||||
}: PageProps) {
|
||||
const { fixedPanelRef: chatPanelRef, handleLayout } = useFixedSizeColumn(
|
||||
CHAT_DEFAULT_SIZE,
|
||||
chatIsOpen
|
||||
)
|
||||
|
||||
useCollapsiblePanel(chatIsOpen, chatPanelRef)
|
||||
|
||||
return (
|
||||
<div className="ide-react-main">
|
||||
{headerContent}
|
||||
<div className="ide-react-body">
|
||||
<PanelGroup
|
||||
autoSaveId={shouldPersistLayout ? 'ide-react-chat-layout' : undefined}
|
||||
direction="horizontal"
|
||||
onLayout={handleLayout}
|
||||
>
|
||||
<Panel id="main" order={1}>
|
||||
{mainContent}
|
||||
</Panel>
|
||||
{chatIsOpen ? (
|
||||
<>
|
||||
<HorizontalResizeHandle />
|
||||
<Panel
|
||||
ref={chatPanelRef}
|
||||
id="chat"
|
||||
order={2}
|
||||
defaultSize={CHAT_DEFAULT_SIZE}
|
||||
minSize={5}
|
||||
collapsible
|
||||
>
|
||||
{chatContent}
|
||||
</Panel>
|
||||
</>
|
||||
) : null}
|
||||
</PanelGroup>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function PlaceholderChat() {
|
||||
return (
|
||||
<aside className="chat ide-react-placeholder-chat">Chat placeholder</aside>
|
||||
)
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
import React, { useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
ImperativePanelHandle,
|
||||
Panel,
|
||||
PanelGroup,
|
||||
} from 'react-resizable-panels'
|
||||
import { HorizontalResizeHandle } from '@/features/ide-react/components/resize/horizontal-resize-handle'
|
||||
import { HorizontalToggler } from '@/features/ide-react/components/resize/horizontal-toggler'
|
||||
import { VerticalResizeHandle } from '@/features/ide-react/components/resize/vertical-resize-handle'
|
||||
import useCollapsiblePanel from '@/features/ide-react/hooks/use-collapsible-panel'
|
||||
|
||||
type PlaceholderEditorAndPdfProps = {
|
||||
shouldPersistLayout?: boolean
|
||||
}
|
||||
|
||||
export default function PlaceholderEditorAndPdf({
|
||||
shouldPersistLayout = false,
|
||||
}: PlaceholderEditorAndPdfProps) {
|
||||
const { t } = useTranslation()
|
||||
const [pdfIsOpen, setPdfIsOpen] = useState(false)
|
||||
const [symbolPaletteIsOpen, setSymbolPaletteIsOpen] = useState(false)
|
||||
|
||||
const pdfPanelRef = useRef<ImperativePanelHandle>(null)
|
||||
useCollapsiblePanel(pdfIsOpen, pdfPanelRef)
|
||||
|
||||
return (
|
||||
<PanelGroup
|
||||
autoSaveId={
|
||||
shouldPersistLayout ? 'ide-react-editor-and-pdf-layout' : undefined
|
||||
}
|
||||
direction="horizontal"
|
||||
>
|
||||
<Panel defaultSize={50}>
|
||||
<PanelGroup
|
||||
autoSaveId={
|
||||
shouldPersistLayout
|
||||
? 'ide-react-editor-and-symbol-palette-layout'
|
||||
: undefined
|
||||
}
|
||||
direction="vertical"
|
||||
units="pixels"
|
||||
>
|
||||
<Panel id="editor" order={1}>
|
||||
Editor placeholder
|
||||
<br />
|
||||
<button onClick={() => setSymbolPaletteIsOpen(value => !value)}>
|
||||
Toggle symbol palette
|
||||
</button>
|
||||
</Panel>
|
||||
{symbolPaletteIsOpen ? (
|
||||
<>
|
||||
<VerticalResizeHandle id="editor-symbol-palette" />
|
||||
<Panel
|
||||
id="symbol-palette"
|
||||
order={2}
|
||||
defaultSize={250}
|
||||
minSize={250}
|
||||
maxSize={336}
|
||||
>
|
||||
<div className="ide-react-placeholder-symbol-palette ">
|
||||
Symbol palette placeholder
|
||||
</div>
|
||||
</Panel>
|
||||
</>
|
||||
) : null}
|
||||
</PanelGroup>
|
||||
</Panel>
|
||||
<HorizontalResizeHandle>
|
||||
<HorizontalToggler
|
||||
id="editor-pdf"
|
||||
togglerType="east"
|
||||
isOpen={pdfIsOpen}
|
||||
setIsOpen={pdfIsOpen => setPdfIsOpen(pdfIsOpen)}
|
||||
tooltipWhenOpen={t('tooltip_hide_pdf')}
|
||||
tooltipWhenClosed={t('tooltip_show_pdf')}
|
||||
/>
|
||||
</HorizontalResizeHandle>
|
||||
<Panel
|
||||
ref={pdfPanelRef}
|
||||
defaultSize={50}
|
||||
minSize={5}
|
||||
collapsible
|
||||
onCollapse={collapsed => setPdfIsOpen(!collapsed)}
|
||||
>
|
||||
PDF placeholder
|
||||
</Panel>
|
||||
</PanelGroup>
|
||||
)
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import React, { useState } from 'react'
|
||||
import TwoColumnMainContent from '@/features/ide-react/components/layout/two-column-main-content'
|
||||
import PlaceholderEditorAndPdf from '@/features/ide-react/components/layout/placeholder/placeholder-editor-and-pdf'
|
||||
import PlaceholderEditorSidebar from '@/features/ide-react/components/layout/placeholder/placeholder-editor-sidebar'
|
||||
|
||||
type PlaceholderEditorMainContentProps = {
|
||||
shouldPersistLayout: boolean
|
||||
leftColumnDefaultSize: number
|
||||
setLeftColumnDefaultSize: React.Dispatch<React.SetStateAction<number>>
|
||||
}
|
||||
|
||||
export default function PlaceholderEditorMainContent({
|
||||
shouldPersistLayout,
|
||||
leftColumnDefaultSize,
|
||||
setLeftColumnDefaultSize,
|
||||
}: PlaceholderEditorMainContentProps) {
|
||||
const [leftColumnIsOpen, setLeftColumnIsOpen] = useState(true)
|
||||
|
||||
const leftColumnContent = (
|
||||
<PlaceholderEditorSidebar shouldPersistLayout={shouldPersistLayout} />
|
||||
)
|
||||
const rightColumnContent = (
|
||||
<PlaceholderEditorAndPdf shouldPersistLayout={shouldPersistLayout} />
|
||||
)
|
||||
|
||||
return (
|
||||
<TwoColumnMainContent
|
||||
leftColumnId="editor-left-column"
|
||||
leftColumnContent={leftColumnContent}
|
||||
leftColumnDefaultSize={leftColumnDefaultSize}
|
||||
setLeftColumnDefaultSize={setLeftColumnDefaultSize}
|
||||
rightColumnContent={rightColumnContent}
|
||||
leftColumnIsOpen={leftColumnIsOpen}
|
||||
setLeftColumnIsOpen={setLeftColumnIsOpen}
|
||||
shouldPersistLayout={shouldPersistLayout}
|
||||
/>
|
||||
)
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import React from 'react'
|
||||
import { Panel, PanelGroup } from 'react-resizable-panels'
|
||||
import { VerticalResizeHandle } from '@/features/ide-react/components/resize/vertical-resize-handle'
|
||||
|
||||
type PlaceholderHeaderProps = {
|
||||
shouldPersistLayout: boolean
|
||||
}
|
||||
|
||||
export default function PlaceholderEditorSidebar({
|
||||
shouldPersistLayout,
|
||||
}: PlaceholderHeaderProps) {
|
||||
return (
|
||||
<aside className="ide-react-placeholder-editor-sidebar">
|
||||
<PanelGroup
|
||||
autoSaveId={
|
||||
shouldPersistLayout ? 'ide-react-editor-sidebar-layout' : undefined
|
||||
}
|
||||
direction="vertical"
|
||||
>
|
||||
<Panel defaultSize={75}>File tree placeholder</Panel>
|
||||
<VerticalResizeHandle />
|
||||
<Panel defaultSize={25}>File outline placeholder</Panel>
|
||||
</PanelGroup>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import React from 'react'
|
||||
import ChatToggleButton from '@/features/editor-navigation-toolbar/components/chat-toggle-button'
|
||||
import HistoryToggleButton from '@/features/editor-navigation-toolbar/components/history-toggle-button'
|
||||
|
||||
type PlaceholderHeaderProps = {
|
||||
chatIsOpen: boolean
|
||||
setChatIsOpen: (chatIsOpen: boolean) => void
|
||||
historyIsOpen: boolean
|
||||
setHistoryIsOpen: (chatIsOpen: boolean) => void
|
||||
}
|
||||
|
||||
export default function PlaceholderHeader({
|
||||
chatIsOpen,
|
||||
setChatIsOpen,
|
||||
historyIsOpen,
|
||||
setHistoryIsOpen,
|
||||
}: PlaceholderHeaderProps) {
|
||||
function toggleChatOpen() {
|
||||
setChatIsOpen(!chatIsOpen)
|
||||
}
|
||||
|
||||
function toggleHistoryOpen() {
|
||||
setHistoryIsOpen(!historyIsOpen)
|
||||
}
|
||||
|
||||
return (
|
||||
<header className="toolbar toolbar-header">
|
||||
<div className="toolbar-left">Header placeholder</div>
|
||||
<div className="toolbar-right">
|
||||
<HistoryToggleButton
|
||||
historyIsOpen={historyIsOpen}
|
||||
onClick={toggleHistoryOpen}
|
||||
/>
|
||||
<ChatToggleButton
|
||||
chatIsOpen={chatIsOpen}
|
||||
onClick={toggleChatOpen}
|
||||
unreadMessageCount={0}
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import React, { useState } from 'react'
|
||||
import TwoColumnMainContent from '@/features/ide-react/components/layout/two-column-main-content'
|
||||
|
||||
type PlaceholderHistoryProps = {
|
||||
shouldPersistLayout: boolean
|
||||
leftColumnDefaultSize: number
|
||||
setLeftColumnDefaultSize: React.Dispatch<React.SetStateAction<number>>
|
||||
}
|
||||
|
||||
export default function PlaceholderHistory({
|
||||
shouldPersistLayout,
|
||||
leftColumnDefaultSize,
|
||||
setLeftColumnDefaultSize,
|
||||
}: PlaceholderHistoryProps) {
|
||||
const [leftColumnIsOpen, setLeftColumnIsOpen] = useState(true)
|
||||
|
||||
const leftColumnContent = (
|
||||
<aside className="ide-react-placeholder-editor-sidebar">
|
||||
History file tree placeholder
|
||||
</aside>
|
||||
)
|
||||
const rightColumnContent = (
|
||||
<div>History document diff viewer and versions list placeholder</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<TwoColumnMainContent
|
||||
leftColumnId="editor-left-column"
|
||||
leftColumnContent={leftColumnContent}
|
||||
leftColumnDefaultSize={leftColumnDefaultSize}
|
||||
setLeftColumnDefaultSize={setLeftColumnDefaultSize}
|
||||
rightColumnContent={rightColumnContent}
|
||||
leftColumnIsOpen={leftColumnIsOpen}
|
||||
setLeftColumnIsOpen={setLeftColumnIsOpen}
|
||||
shouldPersistLayout={shouldPersistLayout}
|
||||
/>
|
||||
)
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
import React, { ReactNode, useEffect } from 'react'
|
||||
import { Panel, PanelGroup } from 'react-resizable-panels'
|
||||
import { HorizontalResizeHandle } from '@/features/ide-react/components/resize/horizontal-resize-handle'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { HorizontalToggler } from '@/features/ide-react/components/resize/horizontal-toggler'
|
||||
import useFixedSizeColumn from '@/features/ide-react/hooks/use-fixed-size-column'
|
||||
import useCollapsiblePanel from '@/features/ide-react/hooks/use-collapsible-panel'
|
||||
|
||||
type TwoColumnMainContentProps = {
|
||||
leftColumnId: string
|
||||
leftColumnContent: ReactNode
|
||||
leftColumnDefaultSize: number
|
||||
setLeftColumnDefaultSize: React.Dispatch<React.SetStateAction<number>>
|
||||
rightColumnContent: ReactNode
|
||||
leftColumnIsOpen: boolean
|
||||
setLeftColumnIsOpen: (
|
||||
leftColumnIsOpen: TwoColumnMainContentProps['leftColumnIsOpen']
|
||||
) => void
|
||||
shouldPersistLayout?: boolean
|
||||
}
|
||||
|
||||
export default function TwoColumnMainContent({
|
||||
leftColumnId,
|
||||
leftColumnContent,
|
||||
leftColumnDefaultSize,
|
||||
setLeftColumnDefaultSize,
|
||||
rightColumnContent,
|
||||
leftColumnIsOpen,
|
||||
setLeftColumnIsOpen,
|
||||
shouldPersistLayout = false,
|
||||
}: TwoColumnMainContentProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const {
|
||||
fixedPanelRef: leftColumnPanelRef,
|
||||
fixedPanelWidthRef: leftColumnWidthRef,
|
||||
handleLayout,
|
||||
} = useFixedSizeColumn(leftColumnDefaultSize, leftColumnIsOpen)
|
||||
|
||||
useCollapsiblePanel(leftColumnIsOpen, leftColumnPanelRef)
|
||||
|
||||
// Update the left column default size on unmount rather than doing it on
|
||||
// every resize, which causes ResizeObserver errors
|
||||
useEffect(() => {
|
||||
if (leftColumnWidthRef.current) {
|
||||
setLeftColumnDefaultSize(leftColumnWidthRef.current.size)
|
||||
}
|
||||
}, [leftColumnWidthRef, setLeftColumnDefaultSize])
|
||||
|
||||
return (
|
||||
<PanelGroup
|
||||
autoSaveId={
|
||||
shouldPersistLayout ? 'ide-react-main-content-layout' : undefined
|
||||
}
|
||||
direction="horizontal"
|
||||
onLayout={handleLayout}
|
||||
>
|
||||
<Panel
|
||||
ref={leftColumnPanelRef}
|
||||
defaultSize={leftColumnDefaultSize}
|
||||
minSize={5}
|
||||
collapsible
|
||||
onCollapse={collapsed => setLeftColumnIsOpen(!collapsed)}
|
||||
>
|
||||
{leftColumnIsOpen ? leftColumnContent : null}
|
||||
</Panel>
|
||||
<HorizontalResizeHandle>
|
||||
<HorizontalToggler
|
||||
id={leftColumnId}
|
||||
togglerType="west"
|
||||
isOpen={leftColumnIsOpen}
|
||||
setIsOpen={isOpen => setLeftColumnIsOpen(isOpen)}
|
||||
tooltipWhenOpen={t('tooltip_hide_filetree')}
|
||||
tooltipWhenClosed={t('tooltip_show_filetree')}
|
||||
/>
|
||||
</HorizontalResizeHandle>
|
||||
<Panel>{rightColumnContent}</Panel>
|
||||
</PanelGroup>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user