[ide-react] Tidy IDE page layout components (#15953)

* Defer script loading
* Refactor loading
* Wait for project:joined
* Only mount IdePage once everything has connected
* Add useConnectionState hook and comments
* Remove placeholder components
* Move props into EditorAndSidebar
* Move props into MainLayout
* Tidy editor and sidebar components
* Lazy-load the symbol palette pane and separate the loading pane

GitOrigin-RevId: 4b721a06d6aba0ae0ec91768e6a6e29cf15e2083
This commit is contained in:
Alf Eaton 2023-12-05 09:34:21 +00:00 committed by Copybot
parent a4ca117640
commit 23593f8650
24 changed files with 206 additions and 575 deletions

View file

@ -14,14 +14,10 @@ import { DefaultSynctexControl } from '@/features/pdf-preview/components/detach-
import classnames from 'classnames' import classnames from 'classnames'
export type EditorProps = { export type EditorProps = {
shouldPersistLayout?: boolean
editorContent: ReactNode editorContent: ReactNode
} }
export default function EditorAndPdf({ export default function EditorAndPdf({ editorContent }: EditorProps) {
shouldPersistLayout = false,
editorContent,
}: EditorProps) {
const { t } = useTranslation() const { t } = useTranslation()
const { view, pdfLayout, changeLayout, detachRole, reattach } = const { view, pdfLayout, changeLayout, detachRole, reattach } =
useLayoutContext() useLayoutContext()
@ -49,9 +45,7 @@ export default function EditorAndPdf({
return ( return (
<PanelGroup <PanelGroup
autoSaveId={ autoSaveId="ide-react-editor-and-pdf-layout"
shouldPersistLayout ? 'ide-react-editor-and-pdf-layout' : undefined
}
direction="horizontal" direction="horizontal"
className={classnames('ide-react-editor-and-pdf', { className={classnames('ide-react-editor-and-pdf', {
hide: historyIsOpen, hide: historyIsOpen,

View file

@ -26,12 +26,6 @@ import { BinaryFile } from '@/features/file-view/types/binary-file'
import useScopeValue from '@/shared/hooks/use-scope-value' import useScopeValue from '@/shared/hooks/use-scope-value'
import { useSelectFileTreeEntity } from '@/features/ide-react/hooks/use-select-file-tree-entity' import { useSelectFileTreeEntity } from '@/features/ide-react/hooks/use-select-file-tree-entity'
type EditorAndSidebarProps = {
shouldPersistLayout: boolean
leftColumnDefaultSize: number
setLeftColumnDefaultSize: React.Dispatch<React.SetStateAction<number>>
}
function convertFileRefToBinaryFile(fileRef: FileRef): BinaryFile { function convertFileRefToBinaryFile(fileRef: FileRef): BinaryFile {
return { return {
_id: fileRef._id, _id: fileRef._id,
@ -58,11 +52,8 @@ function fileViewFile(fileRef: FileRef) {
} }
} }
export function EditorAndSidebar({ export function EditorAndSidebar() {
shouldPersistLayout, const [leftColumnDefaultSize, setLeftColumnDefaultSize] = useState(20)
leftColumnDefaultSize,
setLeftColumnDefaultSize,
}: EditorAndSidebarProps) {
const [leftColumnIsOpen, setLeftColumnIsOpen] = useState(true) const [leftColumnIsOpen, setLeftColumnIsOpen] = useState(true)
const { rootDocId } = useProjectContext() const { rootDocId } = useProjectContext()
const { eventEmitter } = useIdeReactContext() const { eventEmitter } = useIdeReactContext()
@ -75,9 +66,6 @@ export function EditorAndSidebar({
const { projectJoined } = useIdeReactContext() const { projectJoined } = useIdeReactContext()
const { selectEntity } = useSelectFileTreeEntity() const { selectEntity } = useSelectFileTreeEntity()
const [, setOpenFile] = useScopeValue<BinaryFile | null>('openFile') const [, setOpenFile] = useScopeValue<BinaryFile | null>('openFile')
const historyIsOpen = view === 'history'
const [openEntity, setOpenEntity] = useState< const [openEntity, setOpenEntity] = useState<
FileTreeDocumentFindResult | FileTreeFileRefFindResult | null FileTreeDocumentFindResult | FileTreeFileRefFindResult | null
>(null) >(null)
@ -157,40 +145,49 @@ export function EditorAndSidebar({
} }
}, [fileTreeReady, openInitialDoc, projectJoined, rootDocId]) }, [fileTreeReady, openInitialDoc, projectJoined, rootDocId])
// Keep the editor file tree around so that it is available and up to date // Keep the editor file tree around so that it is available and up to date when restoring a file
// when restoring a file const editorSidebar = (
const leftColumnContent = (
<>
<EditorSidebar <EditorSidebar
shouldShow={!historyIsOpen} shouldShow={view !== 'history'}
shouldPersistLayout={shouldPersistLayout}
onFileTreeInit={handleFileTreeInit} onFileTreeInit={handleFileTreeInit}
onFileTreeSelect={handleFileTreeSelect} onFileTreeSelect={handleFileTreeSelect}
onFileTreeDelete={handleFileTreeDelete} onFileTreeDelete={handleFileTreeDelete}
/> />
{historyIsOpen ? <HistorySidebar /> : null}
</>
) )
let rightColumnContent if (view === 'history') {
return (
if (historyIsOpen) { <TwoColumnMainContent
rightColumnContent = ( leftColumnId="editor-left-column"
leftColumnContent={
<>
{editorSidebar}
<HistorySidebar />
</>
}
leftColumnDefaultSize={leftColumnDefaultSize}
setLeftColumnDefaultSize={setLeftColumnDefaultSize}
rightColumnContent={
<HistoryProvider> <HistoryProvider>
<History /> <History />
</HistoryProvider> </HistoryProvider>
}
leftColumnIsOpen={leftColumnIsOpen}
setLeftColumnIsOpen={setLeftColumnIsOpen}
/>
) )
} else { }
let editorContent = null
// Always have the editor mounted when not in history view, and hide and // Always have the editor mounted when not in history view, and hide and
// show it as necessary // show it as necessary
const editorPane = ( const editorPane = (
<EditorPane <EditorPane
shouldPersistLayout={shouldPersistLayout}
show={openEntity?.type === 'doc' && selectedEntityCount === 1} show={openEntity?.type === 'doc' && selectedEntityCount === 1}
/> />
) )
let rightColumnContent
if (openDocId === undefined) { if (openDocId === undefined) {
rightColumnContent = <NoOpenDocPane /> rightColumnContent = <NoOpenDocPane />
} else if (selectedEntityCount === 0) { } else if (selectedEntityCount === 0) {
@ -201,44 +198,40 @@ export function EditorAndSidebar({
</> </>
) )
} else if (selectedEntityCount > 1) { } else if (selectedEntityCount > 1) {
editorContent = ( rightColumnContent = (
<EditorAndPdf
editorContent={
<> <>
{editorPane} {editorPane}
<MultipleSelectionPane selectedEntityCount={selectedEntityCount} /> <MultipleSelectionPane selectedEntityCount={selectedEntityCount} />
</> </>
)
} else if (openEntity) {
editorContent =
openEntity.type === 'doc' ? (
editorPane
) : (
<>
{editorPane}
<FileView file={fileViewFile(openEntity.entity)} />
</>
)
} }
if (editorContent) {
rightColumnContent = (
<EditorAndPdf
editorContent={editorContent}
shouldPersistLayout={shouldPersistLayout}
/> />
) )
} else if (openEntity) {
rightColumnContent = (
<EditorAndPdf
editorContent={
<>
{editorPane}
{openEntity.type !== 'doc' && (
<FileView file={fileViewFile(openEntity.entity)} />
)}
</>
} }
/>
)
} }
return ( return (
<TwoColumnMainContent <TwoColumnMainContent
leftColumnId="editor-left-column" leftColumnId="editor-left-column"
leftColumnContent={leftColumnContent} leftColumnContent={editorSidebar}
leftColumnDefaultSize={leftColumnDefaultSize} leftColumnDefaultSize={leftColumnDefaultSize}
setLeftColumnDefaultSize={setLeftColumnDefaultSize} setLeftColumnDefaultSize={setLeftColumnDefaultSize}
rightColumnContent={rightColumnContent} rightColumnContent={rightColumnContent}
leftColumnIsOpen={leftColumnIsOpen} leftColumnIsOpen={leftColumnIsOpen}
setLeftColumnIsOpen={setLeftColumnIsOpen} setLeftColumnIsOpen={setLeftColumnIsOpen}
shouldPersistLayout={shouldPersistLayout}
/> />
) )
} }

View file

@ -9,31 +9,26 @@ import {
import classNames from 'classnames' import classNames from 'classnames'
type EditorSidebarProps = { type EditorSidebarProps = {
shouldShow: boolean shouldShow?: boolean
shouldPersistLayout: boolean
onFileTreeInit: () => void onFileTreeInit: () => void
onFileTreeSelect: FileTreeSelectHandler onFileTreeSelect: FileTreeSelectHandler
onFileTreeDelete: FileTreeDeleteHandler onFileTreeDelete: FileTreeDeleteHandler
} }
export default function EditorSidebar({ export default function EditorSidebar({
shouldShow, shouldShow = false,
shouldPersistLayout,
onFileTreeInit, onFileTreeInit,
onFileTreeSelect, onFileTreeSelect,
onFileTreeDelete, onFileTreeDelete,
}: EditorSidebarProps) { }: EditorSidebarProps) {
return ( return (
<>
<aside <aside
className={classNames('ide-react-editor-sidebar', { className={classNames('ide-react-editor-sidebar', {
hidden: !shouldShow, hidden: !shouldShow,
})} })}
> >
<PanelGroup <PanelGroup
autoSaveId={ autoSaveId="ide-react-editor-sidebar-layout"
shouldPersistLayout ? 'ide-react-editor-sidebar-layout' : undefined
}
direction="vertical" direction="vertical"
> >
<Panel defaultSize={75} className="ide-react-file-tree-panel"> <Panel defaultSize={75} className="ide-react-file-tree-panel">
@ -49,6 +44,5 @@ export default function EditorSidebar({
</Panel> </Panel>
</PanelGroup> </PanelGroup>
</aside> </aside>
</>
) )
} }

View file

@ -1,58 +1,39 @@
import { Panel, PanelGroup } from 'react-resizable-panels' import { Panel, PanelGroup } from 'react-resizable-panels'
import { VerticalResizeHandle } from '@/features/ide-react/components/resize/vertical-resize-handle' import React, { FC, lazy, Suspense } from 'react'
import React, { ElementType } from 'react'
import useScopeValue from '@/shared/hooks/use-scope-value' import useScopeValue from '@/shared/hooks/use-scope-value'
import SourceEditor from '@/features/source-editor/components/source-editor' import SourceEditor from '@/features/source-editor/components/source-editor'
import { EditorScopeValue } from '@/features/ide-react/context/editor-manager-context' import { EditorScopeValue } from '@/features/ide-react/context/editor-manager-context'
import importOverleafModules from '../../../../../macros/import-overleaf-module.macro'
import classNames from 'classnames' import classNames from 'classnames'
import { useTranslation } from 'react-i18next' import { LoadingPane } from '@/features/ide-react/components/editor/loading-pane'
import { FullSizeLoadingSpinner } from '@/shared/components/loading-spinner'
import { VerticalResizeHandle } from '@/features/ide-react/components/resize/vertical-resize-handle'
const symbolPaletteComponents = importOverleafModules( const SymbolPalettePane = lazy(
'sourceEditorSymbolPalette' () => import('@/features/ide-react/components/editor/symbol-palette-pane')
) as { import: { default: ElementType }; path: string }[] )
export type EditorPaneProps = { export const EditorPane: FC<{ show: boolean }> = ({ show }) => {
shouldPersistLayout?: boolean
show: boolean
}
export function EditorPane({ shouldPersistLayout, show }: EditorPaneProps) {
const { t } = useTranslation()
const [editor] = useScopeValue<EditorScopeValue>('editor') const [editor] = useScopeValue<EditorScopeValue>('editor')
const isLoading = Boolean(
(!editor.sharejs_doc || editor.opening) &&
!editor.error_state &&
editor.open_doc_id
)
return ( return (
<PanelGroup <PanelGroup
autoSaveId={ autoSaveId="ide-react-editor-and-symbol-palette-layout"
shouldPersistLayout
? 'ide-react-editor-and-symbol-palette-layout'
: undefined
}
direction="vertical" direction="vertical"
units="pixels" units="pixels"
className={classNames({ hidden: !show })} className={classNames({ hidden: !show })}
> >
<Panel <Panel id="sourceEditor" order={1} className="ide-react-editor-panel">
id="sourceEditor"
order={1}
style={{
display: 'flex',
flexDirection: 'column',
}}
>
<SourceEditor /> <SourceEditor />
{(!editor.sharejs_doc || editor.opening) && {isLoading && <LoadingPane />}
!editor.error_state &&
!!editor.open_doc_id ? (
<div className="loading-panel">
<span>
<i className="fa fa-spin fa-refresh" />
&nbsp;&nbsp;{t('loading')}
</span>
</div>
) : null}
</Panel> </Panel>
{editor.showSymbolPalette ? (
{editor.showSymbolPalette && (
<> <>
<VerticalResizeHandle id="editor-symbol-palette" /> <VerticalResizeHandle id="editor-symbol-palette" />
<Panel <Panel
@ -62,16 +43,12 @@ export function EditorPane({ shouldPersistLayout, show }: EditorPaneProps) {
minSize={250} minSize={250}
maxSize={336} maxSize={336}
> >
<div className="ide-react-symbol-palette"> <Suspense fallback={<FullSizeLoadingSpinner delay={500} />}>
{symbolPaletteComponents.map( <SymbolPalettePane />
({ import: { default: Component }, path }) => ( </Suspense>
<Component key={path} />
)
)}
</div>
</Panel> </Panel>
</> </>
) : null} )}
</PanelGroup> </PanelGroup>
) )
} }

View file

@ -0,0 +1,15 @@
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
export const LoadingPane: FC = () => {
const { t } = useTranslation()
return (
<div className="loading-panel">
<span>
<i className="fa fa-spin fa-refresh" />
&nbsp;&nbsp;{t('loading')}
</span>
</div>
)
}

View file

@ -0,0 +1,20 @@
import React, { ElementType, FC } from 'react'
import importOverleafModules from '../../../../../macros/import-overleaf-module.macro'
const symbolPaletteComponents = importOverleafModules(
'sourceEditorSymbolPalette'
) as { import: { default: ElementType }; path: string }[]
const SymbolPalettePane: FC = () => {
return (
<div className="ide-react-symbol-palette">
{symbolPaletteComponents.map(
({ import: { default: Component }, path }) => (
<Component key={path} />
)
)}
</div>
)
}
export default SymbolPalettePane

View file

@ -1,71 +1,28 @@
import { useConnectionContext } from '@/features/ide-react/context/connection-context'
import { useEffect, useState } from 'react'
import { Alerts } from '@/features/ide-react/components/alerts/alerts' import { Alerts } from '@/features/ide-react/components/alerts/alerts'
import { useLayoutContext } from '@/shared/context/layout-context'
import MainLayout from '@/features/ide-react/components/layout/main-layout' import MainLayout from '@/features/ide-react/components/layout/main-layout'
import { EditorAndSidebar } from '@/features/ide-react/components/editor-and-sidebar'
import EditorLeftMenu from '@/features/editor-left-menu/components/editor-left-menu' import EditorLeftMenu from '@/features/editor-left-menu/components/editor-left-menu'
import EditorNavigationToolbar from '@/features/ide-react/components/editor-navigation-toolbar'
import ChatPane from '@/features/chat/components/chat-pane'
import { useLayoutEventTracking } from '@/features/ide-react/hooks/use-layout-event-tracking' import { useLayoutEventTracking } from '@/features/ide-react/hooks/use-layout-event-tracking'
import useSocketListeners from '@/features/ide-react/hooks/use-socket-listeners' import useSocketListeners from '@/features/ide-react/hooks/use-socket-listeners'
import { useModalsContext } from '@/features/ide-react/context/modals-context'
import { useOpenFile } from '@/features/ide-react/hooks/use-open-file' import { useOpenFile } from '@/features/ide-react/hooks/use-open-file'
import { useEditingSessionHeartbeat } from '@/features/ide-react/hooks/use-editing-session-heartbeat' import { useEditingSessionHeartbeat } from '@/features/ide-react/hooks/use-editing-session-heartbeat'
import { useRegisterUserActivity } from '@/features/ide-react/hooks/use-register-user-activity' import { useRegisterUserActivity } from '@/features/ide-react/hooks/use-register-user-activity'
import { useHasLintingError } from '@/features/ide-react/hooks/use-has-linting-error' import { useHasLintingError } from '@/features/ide-react/hooks/use-has-linting-error'
import { useConnectionState } from '@/features/ide-react/hooks/use-connection-state'
// This is filled with placeholder content while the real content is migrated
// away from Angular
export default function IdePage() { export default function IdePage() {
useLayoutEventTracking() useLayoutEventTracking() // send event when the layout changes
useSocketListeners() useSocketListeners() // listen for project-related websocket messages
useEditingSessionHeartbeat() useEditingSessionHeartbeat() // send a batched event when user is active
useRegisterUserActivity() useRegisterUserActivity() // record activity and ensure connection when user is active
useHasLintingError() useHasLintingError() // pass editor:lint hasLintingError to the compiler
useOpenFile() // create ide.binaryFilesManager (TODO: move to the history file restore component)
// This returns a function to open a binary file but for now we just use the useConnectionState() // show modal when editor is forcefully disconnected
// fact that it also patches in ide.binaryFilesManager. Once Angular is gone,
// we can remove this hook from here and use it in the history file restore
// component instead.
useOpenFile()
const [leftColumnDefaultSize, setLeftColumnDefaultSize] = useState(20)
const { connectionState } = useConnectionContext()
const { showLockEditorMessageModal } = useModalsContext()
// Show modal when editor is forcefully disconnected
useEffect(() => {
if (connectionState.forceDisconnected) {
showLockEditorMessageModal(connectionState.forcedDisconnectDelay)
}
}, [
connectionState.forceDisconnected,
connectionState.forcedDisconnectDelay,
showLockEditorMessageModal,
])
const { chatIsOpen } = useLayoutContext()
const mainContent = (
<EditorAndSidebar
leftColumnDefaultSize={leftColumnDefaultSize}
setLeftColumnDefaultSize={setLeftColumnDefaultSize}
shouldPersistLayout
/>
)
return ( return (
<> <>
<Alerts /> <Alerts />
<EditorLeftMenu /> <EditorLeftMenu />
<MainLayout <MainLayout />
headerContent={<EditorNavigationToolbar />}
chatContent={<ChatPane />}
mainContent={mainContent}
chatIsOpen={chatIsOpen}
shouldPersistLayout
/>
</> </>
) )
} }

View file

@ -1,49 +0,0 @@
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}
/>
)
}

View file

@ -1,31 +1,23 @@
import { Panel, PanelGroup } from 'react-resizable-panels' import { Panel, PanelGroup } from 'react-resizable-panels'
import { ReactNode, useState } from 'react' import { useState } from 'react'
import { HorizontalResizeHandle } from '../resize/horizontal-resize-handle' import { HorizontalResizeHandle } from '../resize/horizontal-resize-handle'
import useFixedSizeColumn from '@/features/ide-react/hooks/use-fixed-size-column' import useFixedSizeColumn from '@/features/ide-react/hooks/use-fixed-size-column'
import useCollapsiblePanel from '@/features/ide-react/hooks/use-collapsible-panel' import useCollapsiblePanel from '@/features/ide-react/hooks/use-collapsible-panel'
import classNames from 'classnames' import classNames from 'classnames'
import { useLayoutContext } from '@/shared/context/layout-context'
import EditorNavigationToolbar from '@/features/ide-react/components/editor-navigation-toolbar'
import ChatPane from '@/features/chat/components/chat-pane'
import { EditorAndSidebar } from '@/features/ide-react/components/editor-and-sidebar'
const CHAT_DEFAULT_SIZE = 20 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 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 // 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 // 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. // convenient to replace the whole of the main content when in history view.
export default function MainLayout({ export default function MainLayout() {
headerContent, const { chatIsOpen } = useLayoutContext()
chatContent,
mainContent,
chatIsOpen,
shouldPersistLayout,
}: PageProps) {
const { fixedPanelRef: chatPanelRef, handleLayout } = useFixedSizeColumn( const { fixedPanelRef: chatPanelRef, handleLayout } = useFixedSizeColumn(
CHAT_DEFAULT_SIZE, CHAT_DEFAULT_SIZE,
chatIsOpen chatIsOpen
@ -37,10 +29,10 @@ export default function MainLayout({
return ( return (
<div className="ide-react-main"> <div className="ide-react-main">
{headerContent} <EditorNavigationToolbar />
<div className="ide-react-body"> <div className="ide-react-body">
<PanelGroup <PanelGroup
autoSaveId={shouldPersistLayout ? 'ide-react-chat-layout' : undefined} autoSaveId="ide-react-chat-layout"
direction="horizontal" direction="horizontal"
onLayout={handleLayout} onLayout={handleLayout}
className={classNames({ className={classNames({
@ -48,7 +40,7 @@ export default function MainLayout({
})} })}
> >
<Panel id="main" order={1}> <Panel id="main" order={1}>
{mainContent} <EditorAndSidebar />
</Panel> </Panel>
{chatIsOpen ? ( {chatIsOpen ? (
<> <>
@ -61,7 +53,7 @@ export default function MainLayout({
minSize={5} minSize={5}
collapsible collapsible
> >
{chatContent} <ChatPane />
</Panel> </Panel>
</> </>
) : null} ) : null}

View file

@ -1,7 +0,0 @@
import React from 'react'
export default function PlaceholderChat() {
return (
<aside className="chat ide-react-placeholder-chat">Chat placeholder</aside>
)
}

View file

@ -1,90 +0,0 @@
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-symbol-palette ">
Symbol palette placeholder
</div>
</Panel>
</>
) : null}
</PanelGroup>
</Panel>
<HorizontalResizeHandle onDoubleClick={() => setPdfIsOpen(!pdfIsOpen)}>
<HorizontalToggler
id="editor-pdf"
togglerType="east"
isOpen={pdfIsOpen}
setIsOpen={setPdfIsOpen}
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>
)
}

View file

@ -1,38 +0,0 @@
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}
/>
)
}

View file

@ -1,26 +0,0 @@
import React from 'react'
import { Panel, PanelGroup } from 'react-resizable-panels'
import { VerticalResizeHandle } from '@/features/ide-react/components/resize/vertical-resize-handle'
type PlaceholderEditorSidebarProps = {
shouldPersistLayout: boolean
}
export default function PlaceholderEditorSidebar({
shouldPersistLayout,
}: PlaceholderEditorSidebarProps) {
return (
<aside className="ide-react-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>
)
}

View file

@ -1,5 +0,0 @@
import React from 'react'
export default function PlaceholderFile() {
return <div className="file-view full-size">File placeholder</div>
}

View file

@ -1,42 +0,0 @@
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: (historyIsOpen: 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>
)
}

View file

@ -1,38 +0,0 @@
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-editor-sidebar history-file-tree">
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}
/>
)
}

View file

@ -1,5 +0,0 @@
import React from 'react'
export default function PlaceholderPdf() {
return <div>PDF</div>
}

View file

@ -17,7 +17,6 @@ type TwoColumnMainContentProps = {
setLeftColumnIsOpen: ( setLeftColumnIsOpen: (
leftColumnIsOpen: TwoColumnMainContentProps['leftColumnIsOpen'] leftColumnIsOpen: TwoColumnMainContentProps['leftColumnIsOpen']
) => void ) => void
shouldPersistLayout?: boolean
} }
export default function TwoColumnMainContent({ export default function TwoColumnMainContent({
@ -28,7 +27,6 @@ export default function TwoColumnMainContent({
rightColumnContent, rightColumnContent,
leftColumnIsOpen, leftColumnIsOpen,
setLeftColumnIsOpen, setLeftColumnIsOpen,
shouldPersistLayout = false,
}: TwoColumnMainContentProps) { }: TwoColumnMainContentProps) {
const { t } = useTranslation() const { t } = useTranslation()
@ -52,9 +50,7 @@ export default function TwoColumnMainContent({
return ( return (
<PanelGroup <PanelGroup
autoSaveId={ autoSaveId="ide-react-main-content-layout"
shouldPersistLayout ? 'ide-react-main-content-layout' : undefined
}
direction="horizontal" direction="horizontal"
onLayout={handleLayout} onLayout={handleLayout}
className={classNames({ className={classNames({

View file

@ -6,14 +6,12 @@ export default function useCollapsiblePanel(
panelRef: RefObject<ImperativePanelHandle> panelRef: RefObject<ImperativePanelHandle>
) { ) {
useEffect(() => { useEffect(() => {
const panel = panelRef.current if (panelRef.current) {
if (!panel) {
return
}
if (panelIsOpen) { if (panelIsOpen) {
panel.expand() panelRef.current.expand()
} else { } else {
panel.collapse() panelRef.current.collapse()
}
} }
}, [panelIsOpen, panelRef]) }, [panelIsOpen, panelRef])
} }

View file

@ -0,0 +1,19 @@
import { useEffect } from 'react'
import { useConnectionContext } from '@/features/ide-react/context/connection-context'
import { useModalsContext } from '@/features/ide-react/context/modals-context'
export const useConnectionState = () => {
const { connectionState } = useConnectionContext()
const { showLockEditorMessageModal } = useModalsContext()
// Show modal when editor is forcefully disconnected
useEffect(() => {
if (connectionState.forceDisconnected) {
showLockEditorMessageModal(connectionState.forcedDisconnectDelay)
}
}, [
connectionState.forceDisconnected,
connectionState.forcedDisconnectDelay,
showLockEditorMessageModal,
])
}

View file

@ -15,7 +15,7 @@ export default function useFixedSizeColumn(
return fixedPanelRef.current?.getSize('pixels') || 0 return fixedPanelRef.current?.getSize('pixels') || 0
}, [fixedPanelRef]) }, [fixedPanelRef])
const handleLayout = useCallback( const handleLayout: PanelGroupOnLayout = useCallback(
sizes => { sizes => {
// Measure the pixel width here because it's not always up to date in the // Measure the pixel width here because it's not always up to date in the
// panel's onResize // panel's onResize
@ -26,7 +26,7 @@ export default function useFixedSizeColumn(
setInitialLayoutDone(true) setInitialLayoutDone(true)
}, },
[measureFixedPanelSizePixels] [measureFixedPanelSizePixels]
) as PanelGroupOnLayout )
useEffect(() => { useEffect(() => {
if (!isOpen) { if (!isOpen) {

View file

@ -21,6 +21,4 @@ export function useOpenFile() {
// Expose BinaryFilesManager via ide object solely for the benefit of the file // Expose BinaryFilesManager via ide object solely for the benefit of the file
// restore feature in history. This can be removed once Angular is gone. // restore feature in history. This can be removed once Angular is gone.
ide.binaryFilesManager = { openFileWithId } ide.binaryFilesManager = { openFileWithId }
return openFileWithId
} }

View file

@ -1,27 +0,0 @@
import LayoutWithPlaceholders from '@/features/ide-react/components/layout/layout-with-placeholders'
export default {
title: 'Editor / Page Layout',
component: LayoutWithPlaceholders,
decorators: [
(Story: any) => (
<div
style={{ position: 'absolute', inset: '1em', border: 'solid #ccc 1px' }}
>
<Story />
</div>
),
],
}
export const Persisted = {
args: {
shouldPersistLayout: true,
},
}
export const Unpersisted = {
args: {
shouldPersistLayout: false,
},
}

View file

@ -128,6 +128,11 @@
} }
} }
.ide-react-editor-panel {
display: flex;
flex-direction: column;
}
// Ensure an element with class "full-size", such as the binary file view, stays within the bounds of the panel // Ensure an element with class "full-size", such as the binary file view, stays within the bounds of the panel
.ide-react-panel { .ide-react-panel {
position: relative; position: relative;