[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'
export type EditorProps = {
shouldPersistLayout?: boolean
editorContent: ReactNode
}
export default function EditorAndPdf({
shouldPersistLayout = false,
editorContent,
}: EditorProps) {
export default function EditorAndPdf({ editorContent }: EditorProps) {
const { t } = useTranslation()
const { view, pdfLayout, changeLayout, detachRole, reattach } =
useLayoutContext()
@ -49,9 +45,7 @@ export default function EditorAndPdf({
return (
<PanelGroup
autoSaveId={
shouldPersistLayout ? 'ide-react-editor-and-pdf-layout' : undefined
}
autoSaveId="ide-react-editor-and-pdf-layout"
direction="horizontal"
className={classnames('ide-react-editor-and-pdf', {
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 { 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 {
return {
_id: fileRef._id,
@ -58,11 +52,8 @@ function fileViewFile(fileRef: FileRef) {
}
}
export function EditorAndSidebar({
shouldPersistLayout,
leftColumnDefaultSize,
setLeftColumnDefaultSize,
}: EditorAndSidebarProps) {
export function EditorAndSidebar() {
const [leftColumnDefaultSize, setLeftColumnDefaultSize] = useState(20)
const [leftColumnIsOpen, setLeftColumnIsOpen] = useState(true)
const { rootDocId } = useProjectContext()
const { eventEmitter } = useIdeReactContext()
@ -75,9 +66,6 @@ export function EditorAndSidebar({
const { projectJoined } = useIdeReactContext()
const { selectEntity } = useSelectFileTreeEntity()
const [, setOpenFile] = useScopeValue<BinaryFile | null>('openFile')
const historyIsOpen = view === 'history'
const [openEntity, setOpenEntity] = useState<
FileTreeDocumentFindResult | FileTreeFileRefFindResult | null
>(null)
@ -157,88 +145,93 @@ export function EditorAndSidebar({
}
}, [fileTreeReady, openInitialDoc, projectJoined, rootDocId])
// Keep the editor file tree around so that it is available and up to date
// when restoring a file
const leftColumnContent = (
<>
<EditorSidebar
shouldShow={!historyIsOpen}
shouldPersistLayout={shouldPersistLayout}
onFileTreeInit={handleFileTreeInit}
onFileTreeSelect={handleFileTreeSelect}
onFileTreeDelete={handleFileTreeDelete}
// Keep the editor file tree around so that it is available and up to date when restoring a file
const editorSidebar = (
<EditorSidebar
shouldShow={view !== 'history'}
onFileTreeInit={handleFileTreeInit}
onFileTreeSelect={handleFileTreeSelect}
onFileTreeDelete={handleFileTreeDelete}
/>
)
if (view === 'history') {
return (
<TwoColumnMainContent
leftColumnId="editor-left-column"
leftColumnContent={
<>
{editorSidebar}
<HistorySidebar />
</>
}
leftColumnDefaultSize={leftColumnDefaultSize}
setLeftColumnDefaultSize={setLeftColumnDefaultSize}
rightColumnContent={
<HistoryProvider>
<History />
</HistoryProvider>
}
leftColumnIsOpen={leftColumnIsOpen}
setLeftColumnIsOpen={setLeftColumnIsOpen}
/>
{historyIsOpen ? <HistorySidebar /> : null}
</>
)
}
// Always have the editor mounted when not in history view, and hide and
// show it as necessary
const editorPane = (
<EditorPane
show={openEntity?.type === 'doc' && selectedEntityCount === 1}
/>
)
let rightColumnContent
if (historyIsOpen) {
if (openDocId === undefined) {
rightColumnContent = <NoOpenDocPane />
} else if (selectedEntityCount === 0) {
rightColumnContent = (
<HistoryProvider>
<History />
</HistoryProvider>
<>
{editorPane}
<NoSelectionPane />
</>
)
} else {
let editorContent = null
// Always have the editor mounted when not in history view, and hide and
// show it as necessary
const editorPane = (
<EditorPane
shouldPersistLayout={shouldPersistLayout}
show={openEntity?.type === 'doc' && selectedEntityCount === 1}
/>
)
if (openDocId === undefined) {
rightColumnContent = <NoOpenDocPane />
} else if (selectedEntityCount === 0) {
rightColumnContent = (
<>
{editorPane}
<NoSelectionPane />
</>
)
} else if (selectedEntityCount > 1) {
editorContent = (
<>
{editorPane}
<MultipleSelectionPane selectedEntityCount={selectedEntityCount} />
</>
)
} else if (openEntity) {
editorContent =
openEntity.type === 'doc' ? (
editorPane
) : (
} else if (selectedEntityCount > 1) {
rightColumnContent = (
<EditorAndPdf
editorContent={
<>
{editorPane}
<FileView file={fileViewFile(openEntity.entity)} />
<MultipleSelectionPane selectedEntityCount={selectedEntityCount} />
</>
)
}
if (editorContent) {
rightColumnContent = (
<EditorAndPdf
editorContent={editorContent}
shouldPersistLayout={shouldPersistLayout}
/>
)
}
}
/>
)
} else if (openEntity) {
rightColumnContent = (
<EditorAndPdf
editorContent={
<>
{editorPane}
{openEntity.type !== 'doc' && (
<FileView file={fileViewFile(openEntity.entity)} />
)}
</>
}
/>
)
}
return (
<TwoColumnMainContent
leftColumnId="editor-left-column"
leftColumnContent={leftColumnContent}
leftColumnContent={editorSidebar}
leftColumnDefaultSize={leftColumnDefaultSize}
setLeftColumnDefaultSize={setLeftColumnDefaultSize}
rightColumnContent={rightColumnContent}
leftColumnIsOpen={leftColumnIsOpen}
setLeftColumnIsOpen={setLeftColumnIsOpen}
shouldPersistLayout={shouldPersistLayout}
/>
)
}

View file

@ -9,46 +9,40 @@ import {
import classNames from 'classnames'
type EditorSidebarProps = {
shouldShow: boolean
shouldPersistLayout: boolean
shouldShow?: boolean
onFileTreeInit: () => void
onFileTreeSelect: FileTreeSelectHandler
onFileTreeDelete: FileTreeDeleteHandler
}
export default function EditorSidebar({
shouldShow,
shouldPersistLayout,
shouldShow = false,
onFileTreeInit,
onFileTreeSelect,
onFileTreeDelete,
}: EditorSidebarProps) {
return (
<>
<aside
className={classNames('ide-react-editor-sidebar', {
hidden: !shouldShow,
})}
<aside
className={classNames('ide-react-editor-sidebar', {
hidden: !shouldShow,
})}
>
<PanelGroup
autoSaveId="ide-react-editor-sidebar-layout"
direction="vertical"
>
<PanelGroup
autoSaveId={
shouldPersistLayout ? 'ide-react-editor-sidebar-layout' : undefined
}
direction="vertical"
>
<Panel defaultSize={75} className="ide-react-file-tree-panel">
<FileTree
onInit={onFileTreeInit}
onSelect={onFileTreeSelect}
onDelete={onFileTreeDelete}
/>
</Panel>
<VerticalResizeHandle />
<Panel defaultSize={25}>
<div className="outline-container" />
</Panel>
</PanelGroup>
</aside>
</>
<Panel defaultSize={75} className="ide-react-file-tree-panel">
<FileTree
onInit={onFileTreeInit}
onSelect={onFileTreeSelect}
onDelete={onFileTreeDelete}
/>
</Panel>
<VerticalResizeHandle />
<Panel defaultSize={25}>
<div className="outline-container" />
</Panel>
</PanelGroup>
</aside>
)
}

View file

@ -1,58 +1,39 @@
import { Panel, PanelGroup } from 'react-resizable-panels'
import { VerticalResizeHandle } from '@/features/ide-react/components/resize/vertical-resize-handle'
import React, { ElementType } from 'react'
import React, { FC, lazy, Suspense } from 'react'
import useScopeValue from '@/shared/hooks/use-scope-value'
import SourceEditor from '@/features/source-editor/components/source-editor'
import { EditorScopeValue } from '@/features/ide-react/context/editor-manager-context'
import importOverleafModules from '../../../../../macros/import-overleaf-module.macro'
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(
'sourceEditorSymbolPalette'
) as { import: { default: ElementType }; path: string }[]
const SymbolPalettePane = lazy(
() => import('@/features/ide-react/components/editor/symbol-palette-pane')
)
export type EditorPaneProps = {
shouldPersistLayout?: boolean
show: boolean
}
export function EditorPane({ shouldPersistLayout, show }: EditorPaneProps) {
const { t } = useTranslation()
export const EditorPane: FC<{ show: boolean }> = ({ show }) => {
const [editor] = useScopeValue<EditorScopeValue>('editor')
const isLoading = Boolean(
(!editor.sharejs_doc || editor.opening) &&
!editor.error_state &&
editor.open_doc_id
)
return (
<PanelGroup
autoSaveId={
shouldPersistLayout
? 'ide-react-editor-and-symbol-palette-layout'
: undefined
}
autoSaveId="ide-react-editor-and-symbol-palette-layout"
direction="vertical"
units="pixels"
className={classNames({ hidden: !show })}
>
<Panel
id="sourceEditor"
order={1}
style={{
display: 'flex',
flexDirection: 'column',
}}
>
<Panel id="sourceEditor" order={1} className="ide-react-editor-panel">
<SourceEditor />
{(!editor.sharejs_doc || editor.opening) &&
!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}
{isLoading && <LoadingPane />}
</Panel>
{editor.showSymbolPalette ? (
{editor.showSymbolPalette && (
<>
<VerticalResizeHandle id="editor-symbol-palette" />
<Panel
@ -62,16 +43,12 @@ export function EditorPane({ shouldPersistLayout, show }: EditorPaneProps) {
minSize={250}
maxSize={336}
>
<div className="ide-react-symbol-palette">
{symbolPaletteComponents.map(
({ import: { default: Component }, path }) => (
<Component key={path} />
)
)}
</div>
<Suspense fallback={<FullSizeLoadingSpinner delay={500} />}>
<SymbolPalettePane />
</Suspense>
</Panel>
</>
) : null}
)}
</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 { useLayoutContext } from '@/shared/context/layout-context'
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 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 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 { useEditingSessionHeartbeat } from '@/features/ide-react/hooks/use-editing-session-heartbeat'
import { useRegisterUserActivity } from '@/features/ide-react/hooks/use-register-user-activity'
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() {
useLayoutEventTracking()
useSocketListeners()
useEditingSessionHeartbeat()
useRegisterUserActivity()
useHasLintingError()
// This returns a function to open a binary file but for now we just use the
// 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
/>
)
useLayoutEventTracking() // send event when the layout changes
useSocketListeners() // listen for project-related websocket messages
useEditingSessionHeartbeat() // send a batched event when user is active
useRegisterUserActivity() // record activity and ensure connection when user is active
useHasLintingError() // pass editor:lint hasLintingError to the compiler
useOpenFile() // create ide.binaryFilesManager (TODO: move to the history file restore component)
useConnectionState() // show modal when editor is forcefully disconnected
return (
<>
<Alerts />
<EditorLeftMenu />
<MainLayout
headerContent={<EditorNavigationToolbar />}
chatContent={<ChatPane />}
mainContent={mainContent}
chatIsOpen={chatIsOpen}
shouldPersistLayout
/>
<MainLayout />
</>
)
}

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 { ReactNode, useState } from 'react'
import { useState } 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'
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
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) {
export default function MainLayout() {
const { chatIsOpen } = useLayoutContext()
const { fixedPanelRef: chatPanelRef, handleLayout } = useFixedSizeColumn(
CHAT_DEFAULT_SIZE,
chatIsOpen
@ -37,10 +29,10 @@ export default function MainLayout({
return (
<div className="ide-react-main">
{headerContent}
<EditorNavigationToolbar />
<div className="ide-react-body">
<PanelGroup
autoSaveId={shouldPersistLayout ? 'ide-react-chat-layout' : undefined}
autoSaveId="ide-react-chat-layout"
direction="horizontal"
onLayout={handleLayout}
className={classNames({
@ -48,7 +40,7 @@ export default function MainLayout({
})}
>
<Panel id="main" order={1}>
{mainContent}
<EditorAndSidebar />
</Panel>
{chatIsOpen ? (
<>
@ -61,7 +53,7 @@ export default function MainLayout({
minSize={5}
collapsible
>
{chatContent}
<ChatPane />
</Panel>
</>
) : 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: (
leftColumnIsOpen: TwoColumnMainContentProps['leftColumnIsOpen']
) => void
shouldPersistLayout?: boolean
}
export default function TwoColumnMainContent({
@ -28,7 +27,6 @@ export default function TwoColumnMainContent({
rightColumnContent,
leftColumnIsOpen,
setLeftColumnIsOpen,
shouldPersistLayout = false,
}: TwoColumnMainContentProps) {
const { t } = useTranslation()
@ -52,9 +50,7 @@ export default function TwoColumnMainContent({
return (
<PanelGroup
autoSaveId={
shouldPersistLayout ? 'ide-react-main-content-layout' : undefined
}
autoSaveId="ide-react-main-content-layout"
direction="horizontal"
onLayout={handleLayout}
className={classNames({

View file

@ -6,14 +6,12 @@ export default function useCollapsiblePanel(
panelRef: RefObject<ImperativePanelHandle>
) {
useEffect(() => {
const panel = panelRef.current
if (!panel) {
return
}
if (panelIsOpen) {
panel.expand()
} else {
panel.collapse()
if (panelRef.current) {
if (panelIsOpen) {
panelRef.current.expand()
} else {
panelRef.current.collapse()
}
}
}, [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
}, [fixedPanelRef])
const handleLayout = useCallback(
const handleLayout: PanelGroupOnLayout = useCallback(
sizes => {
// Measure the pixel width here because it's not always up to date in the
// panel's onResize
@ -26,7 +26,7 @@ export default function useFixedSizeColumn(
setInitialLayoutDone(true)
},
[measureFixedPanelSizePixels]
) as PanelGroupOnLayout
)
useEffect(() => {
if (!isOpen) {

View file

@ -21,6 +21,4 @@ export function useOpenFile() {
// Expose BinaryFilesManager via ide object solely for the benefit of the file
// restore feature in history. This can be removed once Angular is gone.
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
.ide-react-panel {
position: relative;