Upgrade react-resizable-panels (#15998)

GitOrigin-RevId: af799f1a5b4945ad2acbb460806d559fae7416b9
This commit is contained in:
Alf Eaton
2023-12-06 09:04:40 +00:00
committed by Copybot
parent 57f752d0b4
commit 02cb26e3bb
14 changed files with 438 additions and 424 deletions
+8 -8
View File
@@ -34095,9 +34095,9 @@
} }
}, },
"node_modules/react-resizable-panels": { "node_modules/react-resizable-panels": {
"version": "0.0.55", "version": "0.0.63",
"resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-0.0.55.tgz", "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-0.0.63.tgz",
"integrity": "sha512-J/LTFzUEjJiqwSjVh8gjUXkQDA8MRPjARASfn++d2+KOgA+9UcRYUfE3QBJixer2vkk+ffQ4cq3QzWzzHgqYpQ==", "integrity": "sha512-AfA8b6kouhL4rBvgUGs17uzWVlYPaJIwwTCVeWRxNpUHJlCG1h9RIMlzA1849AZGsaNJO3j/SNdI5SS4GZDE3g==",
"dev": true, "dev": true,
"peerDependencies": { "peerDependencies": {
"react": "^16.14.0 || ^17.0.0 || ^18.0.0", "react": "^16.14.0 || ^17.0.0 || ^18.0.0",
@@ -43709,7 +43709,7 @@
"react-i18next": "^13.3.1", "react-i18next": "^13.3.1",
"react-linkify": "^1.0.0-alpha", "react-linkify": "^1.0.0-alpha",
"react-refresh": "^0.14.0", "react-refresh": "^0.14.0",
"react-resizable-panels": "^0.0.55", "react-resizable-panels": "^0.0.63",
"react2angular": "^4.0.6", "react2angular": "^4.0.6",
"react2angular-shared-context": "^1.1.0", "react2angular-shared-context": "^1.1.0",
"requirejs": "^2.3.6", "requirejs": "^2.3.6",
@@ -52297,7 +52297,7 @@
"react-i18next": "^13.3.1", "react-i18next": "^13.3.1",
"react-linkify": "^1.0.0-alpha", "react-linkify": "^1.0.0-alpha",
"react-refresh": "^0.14.0", "react-refresh": "^0.14.0",
"react-resizable-panels": "^0.0.55", "react-resizable-panels": "^0.0.63",
"react2angular": "^4.0.6", "react2angular": "^4.0.6",
"react2angular-shared-context": "^1.1.0", "react2angular-shared-context": "^1.1.0",
"recurly": "^4.0.0", "recurly": "^4.0.0",
@@ -73465,9 +73465,9 @@
} }
}, },
"react-resizable-panels": { "react-resizable-panels": {
"version": "0.0.55", "version": "0.0.63",
"resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-0.0.55.tgz", "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-0.0.63.tgz",
"integrity": "sha512-J/LTFzUEjJiqwSjVh8gjUXkQDA8MRPjARASfn++d2+KOgA+9UcRYUfE3QBJixer2vkk+ffQ4cq3QzWzzHgqYpQ==", "integrity": "sha512-AfA8b6kouhL4rBvgUGs17uzWVlYPaJIwwTCVeWRxNpUHJlCG1h9RIMlzA1849AZGsaNJO3j/SNdI5SS4GZDE3g==",
"dev": true, "dev": true,
"requires": {} "requires": {}
}, },
@@ -1,99 +1,103 @@
import { ReactNode, useRef, useState } from 'react' import React, { FC, useState } from 'react'
import { useTranslation } from 'react-i18next' import { Panel, PanelGroup } from 'react-resizable-panels'
import { import NoSelectionPane from '@/features/ide-react/components/editor/no-selection-pane'
ImperativePanelHandle, import FileView from '@/features/file-view/components/file-view'
Panel, import { fileViewFile } from '@/features/ide-react/hooks/use-file-tree'
PanelGroup, import MultipleSelectionPane from '@/features/ide-react/components/editor/multiple-selection-pane'
} from 'react-resizable-panels'
import { HorizontalResizeHandle } from '@/features/ide-react/components/resize/horizontal-resize-handle' import { HorizontalResizeHandle } from '@/features/ide-react/components/resize/horizontal-resize-handle'
import { HorizontalToggler } from '@/features/ide-react/components/resize/horizontal-toggler' import { HorizontalToggler } from '@/features/ide-react/components/resize/horizontal-toggler'
import useCollapsiblePanel from '@/features/ide-react/hooks/use-collapsible-panel'
import { useLayoutContext } from '@/shared/context/layout-context'
import PdfPreview from '@/features/pdf-preview/components/pdf-preview'
import { DefaultSynctexControl } from '@/features/pdf-preview/components/detach-synctex-control' import { DefaultSynctexControl } from '@/features/pdf-preview/components/detach-synctex-control'
import classnames from 'classnames' import PdfPreview from '@/features/pdf-preview/components/pdf-preview'
import { usePdfPane } from '@/features/ide-react/hooks/use-pdf-pane'
import { useLayoutContext } from '@/shared/context/layout-context'
import { useTranslation } from 'react-i18next'
import classNames from 'classnames'
export type EditorProps = { export const EditorAndPdf: FC<{
editorContent: ReactNode editorPane: React.ReactNode
} selectedEntityCount: number
openEntity: any // TODO
export default function EditorAndPdf({ editorContent }: EditorProps) { }> = ({ editorPane, selectedEntityCount, openEntity }) => {
const { t } = useTranslation()
const { view, pdfLayout, changeLayout, detachRole, reattach } =
useLayoutContext()
const [resizing, setResizing] = useState(false) const [resizing, setResizing] = useState(false)
const pdfPanelRef = useRef<ImperativePanelHandle>(null) const { t } = useTranslation()
const isDualPane = pdfLayout === 'sideBySide'
const editorIsVisible = isDualPane || view === 'editor' || view === 'file'
const pdfIsOpen = isDualPane || view === 'pdf'
useCollapsiblePanel(pdfIsOpen, pdfPanelRef) const {
togglePdfPane,
handlePdfPaneExpand,
handlePdfPaneCollapse,
setPdfIsOpen,
pdfIsOpen,
pdfPanelRef,
} = usePdfPane()
const historyIsOpen = view === 'history' const { view, pdfLayout } = useLayoutContext()
function setPdfIsOpen(isOpen: boolean) { const editorIsOpen =
if (isOpen) { view === 'editor' || view === 'file' || pdfLayout === 'sideBySide'
if (detachRole === 'detacher') {
reattach()
}
changeLayout('sideBySide')
} else {
changeLayout('flat', 'editor')
}
}
return ( return (
<PanelGroup <PanelGroup autoSaveId="ide-editor-pdf-layout" direction="horizontal">
autoSaveId="ide-react-editor-and-pdf-layout" {/* main */}
direction="horizontal" {editorIsOpen && (
className={classnames('ide-react-editor-and-pdf', { <>
hide: historyIsOpen, <Panel
'ide-react-editor-and-pdf-resizing': resizing, id="panel-main"
})} order={1}
> defaultSizePercentage={50}
{editorIsVisible ? ( minSizePercentage={25}
<Panel className={classNames('ide-react-panel', {
id="editor" 'ide-panel-group-resizing': resizing,
order={1} })}
defaultSize={50} >
className="ide-react-panel" {selectedEntityCount === 0 && <NoSelectionPane />}
> {selectedEntityCount === 1 && openEntity?.type === 'fileRef' && (
<div className="ide-react-editor-content full-size"> <FileView file={fileViewFile(openEntity.entity)} />
{editorContent} )}
</div> {selectedEntityCount === 1 && editorPane}
</Panel> {selectedEntityCount > 1 && (
) : null} <MultipleSelectionPane
<HorizontalResizeHandle selectedEntityCount={selectedEntityCount}
resizable={isDualPane} />
onDoubleClick={() => setPdfIsOpen(!pdfIsOpen)} )}
onDragging={setResizing} </Panel>
>
<HorizontalToggler <HorizontalResizeHandle
id="editor-pdf" resizable={pdfLayout === 'sideBySide'}
togglerType="east" onDoubleClick={togglePdfPane}
isOpen={pdfIsOpen} onDragging={setResizing}
setIsOpen={setPdfIsOpen} >
tooltipWhenOpen={t('tooltip_hide_pdf')} <HorizontalToggler
tooltipWhenClosed={t('tooltip_show_pdf')} id="editor-pdf"
/> togglerType="east"
{isDualPane ? ( isOpen={pdfIsOpen}
<div className="synctex-controls"> setIsOpen={setPdfIsOpen}
<DefaultSynctexControl /> tooltipWhenOpen={t('tooltip_hide_pdf')}
</div> tooltipWhenClosed={t('tooltip_show_pdf')}
) : null} />
</HorizontalResizeHandle>
{pdfLayout === 'sideBySide' && (
<div className="synctex-controls">
<DefaultSynctexControl />
</div>
)}
</HorizontalResizeHandle>
</>
)}
{/* pdf */}
<Panel <Panel
ref={pdfPanelRef} ref={pdfPanelRef}
id="pdf" id="panel-pdf"
order={2} order={2}
defaultSize={50} defaultSizePercentage={50}
minSize={5} minSizePercentage={25}
collapsible collapsible
onCollapse={collapsed => setPdfIsOpen(!collapsed)} onCollapse={handlePdfPaneCollapse}
onExpand={handlePdfPaneExpand}
className="ide-react-panel" className="ide-react-panel"
> >
{pdfIsOpen || resizing ? <PdfPreview /> : null} {pdfIsOpen && <PdfPreview />}
</Panel> </Panel>
</PanelGroup> </PanelGroup>
) )
@@ -27,11 +27,12 @@ export default function EditorSidebar({
hidden: !shouldShow, hidden: !shouldShow,
})} })}
> >
<PanelGroup <PanelGroup autoSaveId="ide-editor-sidebar-layout" direction="vertical">
autoSaveId="ide-react-editor-sidebar-layout" <Panel
direction="vertical" defaultSizePercentage={75}
> className="ide-react-file-tree-panel"
<Panel defaultSize={75} className="ide-react-file-tree-panel"> id="panel-file-tree"
>
<FileTree <FileTree
onInit={onFileTreeInit} onInit={onFileTreeInit}
onSelect={onFileTreeSelect} onSelect={onFileTreeSelect}
@@ -39,7 +40,7 @@ export default function EditorSidebar({
/> />
</Panel> </Panel>
<VerticalResizeHandle /> <VerticalResizeHandle />
<Panel defaultSize={25}> <Panel defaultSizePercentage={25} id="panel-outline">
<div className="outline-container" /> <div className="outline-container" />
</Panel> </Panel>
</PanelGroup> </PanelGroup>
@@ -22,33 +22,38 @@ export const EditorPane: FC<{ show: boolean }> = ({ show }) => {
) )
return ( return (
<PanelGroup <div className="ide-react-editor-content full-size">
autoSaveId="ide-react-editor-and-symbol-palette-layout" <PanelGroup
direction="vertical" autoSaveId="ide-editor-layout"
units="pixels" direction="vertical"
className={classNames({ hidden: !show })} className={classNames({ hidden: !show })}
> >
<Panel id="sourceEditor" order={1} className="ide-react-editor-panel"> <Panel
<SourceEditor /> id="panel-source-editor"
{isLoading && <LoadingPane />} order={1}
</Panel> className="ide-react-editor-panel"
>
<SourceEditor />
{isLoading && <LoadingPane />}
</Panel>
{editor.showSymbolPalette && ( {editor.showSymbolPalette && (
<> <>
<VerticalResizeHandle id="editor-symbol-palette" /> <VerticalResizeHandle id="editor-symbol-palette" />
<Panel <Panel
id="symbol-palette" id="panel-symbol-palette"
order={2} order={2}
defaultSize={250} defaultSizePixels={250}
minSize={250} minSizePixels={250}
maxSize={336} maxSizePixels={336}
> >
<Suspense fallback={<FullSizeLoadingSpinner delay={500} />}> <Suspense fallback={<FullSizeLoadingSpinner delay={500} />}>
<SymbolPalettePane /> <SymbolPalettePane />
</Suspense> </Suspense>
</Panel> </Panel>
</> </>
)} )}
</PanelGroup> </PanelGroup>
</div>
) )
} }
@@ -1,5 +1,5 @@
import { Alerts } from '@/features/ide-react/components/alerts/alerts' import { Alerts } from '@/features/ide-react/components/alerts/alerts'
import MainLayout from '@/features/ide-react/components/layout/main-layout' import { MainLayout } from '@/features/ide-react/components/layout/main-layout'
import EditorLeftMenu from '@/features/editor-left-menu/components/editor-left-menu' import EditorLeftMenu from '@/features/editor-left-menu/components/editor-left-menu'
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'
@@ -10,7 +10,7 @@ import { useHasLintingError } from '@/features/ide-react/hooks/use-has-linting-e
import { useConnectionState } from '@/features/ide-react/hooks/use-connection-state' import { useConnectionState } from '@/features/ide-react/hooks/use-connection-state'
export default function IdePage() { export default function IdePage() {
useLayoutEventTracking() // send event when the layout changes useLayoutEventTracking() // sent event when the layout changes
useSocketListeners() // listen for project-related websocket messages useSocketListeners() // listen for project-related websocket messages
useEditingSessionHeartbeat() // send a batched event when user is active useEditingSessionHeartbeat() // send a batched event when user is active
useRegisterUserActivity() // record activity and ensure connection when user is active useRegisterUserActivity() // record activity and ensure connection when user is active
@@ -1,62 +1,149 @@
import { Panel, PanelGroup } from 'react-resizable-panels' import { Panel, PanelGroup } from 'react-resizable-panels'
import { useState } from 'react' import { FC } 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 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 { useLayoutContext } from '@/shared/context/layout-context'
import EditorNavigationToolbar from '@/features/ide-react/components/editor-navigation-toolbar' import EditorNavigationToolbar from '@/features/ide-react/components/editor-navigation-toolbar'
import ChatPane from '@/features/chat/components/chat-pane' import ChatPane from '@/features/chat/components/chat-pane'
import { EditorAndSidebar } from '@/features/ide-react/components/editor-and-sidebar' import { HorizontalToggler } from '@/features/ide-react/components/resize/horizontal-toggler'
import { HistorySidebar } from '@/features/ide-react/components/history-sidebar'
import { HistoryProvider } from '@/features/history/context/history-context'
import History from '@/features/ide-react/components/history'
import EditorSidebar from '@/features/ide-react/components/editor-sidebar'
import { EditorPane } from '@/features/ide-react/components/editor/editor-pane'
import { useFileTree } from '@/features/ide-react/hooks/use-file-tree'
import { useTranslation } from 'react-i18next'
import { useSidebarPane } from '@/features/ide-react/hooks/use-sidebar-pane'
import { useChatPane } from '@/features/ide-react/hooks/use-chat-pane'
import { EditorAndPdf } from '@/features/ide-react/components/editor-and-pdf'
const CHAT_DEFAULT_SIZE = 20 export const MainLayout: FC = () => {
const { view } = useLayoutContext()
// The main area below the header is split into two: the main content and chat. const {
// The reason for not splitting the left column containing the file tree and isOpen: sidebarIsOpen,
// outline here is that the history view has its own file tree, so it is more setIsOpen: setSidebarIsOpen,
// convenient to replace the whole of the main content when in history view. fixedPanelRef: sidebarPanelRef,
export default function MainLayout() { handleLayout: handleSidebarLayout,
const { chatIsOpen } = useLayoutContext() togglePane: toggleSidebar,
handlePaneExpand: handleSidebarExpand,
handlePaneCollapse: handleSidebarCollapse,
resizing: sidebarResizing,
setResizing: setSidebarResizing,
} = useSidebarPane()
const { fixedPanelRef: chatPanelRef, handleLayout } = useFixedSizeColumn( const {
CHAT_DEFAULT_SIZE, isOpen: chatIsOpen,
chatIsOpen fixedPanelRef: chatPanelRef,
) handleLayout: handleChatLayout,
resizing: chatResizing,
setResizing: setChatResizing,
} = useChatPane()
useCollapsiblePanel(chatIsOpen, chatPanelRef) const {
selectedEntityCount,
openEntity,
openDocId,
handleFileTreeInit,
handleFileTreeSelect,
handleFileTreeDelete,
} = useFileTree()
const [resizing, setResizing] = useState(false) const { t } = useTranslation()
// keep the editor pane open
const editorPane = openDocId ? (
<EditorPane
show={openEntity?.type === 'doc' && selectedEntityCount === 1}
/>
) : null
return ( return (
<div className="ide-react-main"> <div className="ide-react-main">
<EditorNavigationToolbar /> <EditorNavigationToolbar />
<div className="ide-react-body"> <div className="ide-react-body">
<PanelGroup <PanelGroup
autoSaveId="ide-react-chat-layout" autoSaveId="ide-outer-layout"
direction="horizontal" direction="horizontal"
onLayout={handleLayout} onLayout={handleSidebarLayout}
className={classNames({ className={classNames({
'ide-react-main-resizing': resizing, 'ide-panel-group-resizing': sidebarResizing || chatResizing,
})} })}
> >
<Panel id="main" order={1}> {/* sidebar */}
<EditorAndSidebar /> <Panel
ref={sidebarPanelRef}
id="panel-sidebar"
order={1}
defaultSizePixels={200}
minSizePixels={150}
collapsible
onCollapse={handleSidebarCollapse}
onExpand={handleSidebarExpand}
>
<EditorSidebar
shouldShow={view !== 'history'}
onFileTreeInit={handleFileTreeInit}
onFileTreeSelect={handleFileTreeSelect}
onFileTreeDelete={handleFileTreeDelete}
/>
{view === 'history' && <HistorySidebar />}
</Panel> </Panel>
{chatIsOpen ? (
<> <HorizontalResizeHandle
<HorizontalResizeHandle onDragging={setResizing} /> onDoubleClick={toggleSidebar}
<Panel resizable={sidebarIsOpen}
ref={chatPanelRef} onDragging={setSidebarResizing}
id="chat" >
order={2} <HorizontalToggler
defaultSize={CHAT_DEFAULT_SIZE} id="panel-sidebar"
minSize={5} togglerType="west"
collapsible isOpen={sidebarIsOpen}
> setIsOpen={setSidebarIsOpen}
<ChatPane /> tooltipWhenOpen={t('tooltip_hide_filetree')}
tooltipWhenClosed={t('tooltip_show_filetree')}
/>
</HorizontalResizeHandle>
<Panel id="panel-outer-main" order={2}>
<PanelGroup
autoSaveId="ide-inner-layout"
direction="horizontal"
onLayout={handleChatLayout}
>
<Panel className="ide-react-panel" id="panel-main" order={1}>
{view === 'history' ? (
<HistoryProvider>
<History />
</HistoryProvider>
) : (
<EditorAndPdf
editorPane={editorPane}
selectedEntityCount={selectedEntityCount}
openEntity={openEntity}
/>
)}
</Panel> </Panel>
</>
) : null} {chatIsOpen && (
<>
<HorizontalResizeHandle onDragging={setChatResizing} />
{/* chat */}
<Panel
ref={chatPanelRef}
id="panel-chat"
order={2}
defaultSizePixels={200}
minSizePixels={150}
collapsible
>
<ChatPane />
</Panel>
</>
)}
</PanelGroup>
</Panel>
</PanelGroup> </PanelGroup>
</div> </div>
</div> </div>
@@ -1,86 +0,0 @@
import React, { ReactNode, useEffect, useState } 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'
import classNames from 'classnames'
type TwoColumnMainContentProps = {
leftColumnId: string
leftColumnContent: ReactNode
leftColumnDefaultSize: number
setLeftColumnDefaultSize: React.Dispatch<React.SetStateAction<number>>
rightColumnContent: ReactNode
leftColumnIsOpen: boolean
setLeftColumnIsOpen: (
leftColumnIsOpen: TwoColumnMainContentProps['leftColumnIsOpen']
) => void
}
export default function TwoColumnMainContent({
leftColumnId,
leftColumnContent,
leftColumnDefaultSize,
setLeftColumnDefaultSize,
rightColumnContent,
leftColumnIsOpen,
setLeftColumnIsOpen,
}: TwoColumnMainContentProps) {
const { t } = useTranslation()
const {
fixedPanelRef: leftColumnPanelRef,
fixedPanelWidthRef: leftColumnWidthRef,
handleLayout,
} = useFixedSizeColumn(leftColumnDefaultSize, leftColumnIsOpen)
useCollapsiblePanel(leftColumnIsOpen, leftColumnPanelRef)
const [resizing, setResizing] = useState(false)
// 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="ide-react-main-content-layout"
direction="horizontal"
onLayout={handleLayout}
className={classNames({
'ide-react-main-content-resizing': resizing,
})}
>
<Panel
ref={leftColumnPanelRef}
defaultSize={leftColumnDefaultSize}
minSize={5}
collapsible
onCollapse={collapsed => setLeftColumnIsOpen(!collapsed)}
>
{leftColumnContent}
</Panel>
<HorizontalResizeHandle
onDoubleClick={() => setLeftColumnIsOpen(!leftColumnIsOpen)}
resizable={leftColumnIsOpen}
onDragging={setResizing}
>
<HorizontalToggler
id={leftColumnId}
togglerType="west"
isOpen={leftColumnIsOpen}
setIsOpen={setLeftColumnIsOpen}
tooltipWhenOpen={t('tooltip_hide_filetree')}
tooltipWhenClosed={t('tooltip_show_filetree')}
/>
</HorizontalResizeHandle>
<Panel className="ide-react-panel">{rightColumnContent}</Panel>
</PanelGroup>
)
}
@@ -0,0 +1,13 @@
import { useLayoutContext } from '@/shared/context/layout-context'
import useFixedSizeColumn from '@/features/ide-react/hooks/use-fixed-size-column'
import useCollapsiblePanel from '@/features/ide-react/hooks/use-collapsible-panel'
import { useState } from 'react'
export const useChatPane = () => {
const { chatIsOpen: isOpen } = useLayoutContext()
const [resizing, setResizing] = useState(false)
const { fixedPanelRef, handleLayout } = useFixedSizeColumn(isOpen)
useCollapsiblePanel(isOpen, fixedPanelRef)
return { isOpen, fixedPanelRef, handleLayout, resizing, setResizing }
}
@@ -1,60 +1,20 @@
import React, { useCallback, useEffect, useRef, useState } from 'react' import { FileRef } from '../../../../../types/file-ref'
import TwoColumnMainContent from '@/features/ide-react/components/layout/two-column-main-content' import { BinaryFile } from '@/features/file-view/types/binary-file'
import EditorSidebar from '@/features/ide-react/components/editor-sidebar' import { useSelectFileTreeEntity } from '@/features/ide-react/hooks/use-select-file-tree-entity'
import History from '@/features/ide-react/components/history' import useScopeValue from '@/shared/hooks/use-scope-value'
import { HistoryProvider } from '@/features/history/context/history-context' import { useCallback, useEffect, useRef, useState } from 'react'
import { useProjectContext } from '@/shared/context/project-context'
import { useLayoutContext } from '@/shared/context/layout-context'
import { import {
FileTreeDeleteHandler, FileTreeDeleteHandler,
FileTreeDocumentFindResult, FileTreeDocumentFindResult,
FileTreeFileRefFindResult, FileTreeFileRefFindResult,
FileTreeFindResult, FileTreeFindResult,
} from '@/features/ide-react/types/file-tree' } from '@/features/ide-react/types/file-tree'
import FileView from '@/features/file-view/components/file-view' import { debugConsole } from '@/utils/debugging'
import { FileRef } from '../../../../../types/file-ref' import { useProjectContext } from '@/shared/context/project-context'
import { EditorPane } from '@/features/ide-react/components/editor/editor-pane'
import EditorAndPdf from '@/features/ide-react/components/editor-and-pdf'
import MultipleSelectionPane from '@/features/ide-react/components/editor/multiple-selection-pane'
import NoSelectionPane from '@/features/ide-react/components/editor/no-selection-pane'
import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context' import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context' import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
import NoOpenDocPane from '@/features/ide-react/components/editor/no-open-doc-pane'
import { debugConsole } from '@/utils/debugging'
import { HistorySidebar } from '@/features/ide-react/components/history-sidebar'
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'
function convertFileRefToBinaryFile(fileRef: FileRef): BinaryFile { export const useFileTree = () => {
return {
_id: fileRef._id,
name: fileRef.name,
id: fileRef._id,
type: 'file',
selected: true,
linkedFileData: fileRef.linkedFileData,
created: fileRef.created ? new Date(fileRef.created) : new Date(),
}
}
// `FileViewHeader`, which is TypeScript, expects a BinaryFile, which has a
// `created` property of type `Date`, while `TPRFileViewInfo`, written in JS,
// into which `FileViewHeader` passes its BinaryFile, expects a file object with
// `created` property of type `string`, which is a mismatch. `TPRFileViewInfo`
// is the only one making runtime complaints and it seems that other uses of
// `FileViewHeader` pass in a string for `created`, so that's what this function
// does too.
function fileViewFile(fileRef: FileRef) {
return {
...convertFileRefToBinaryFile(fileRef),
created: fileRef.created,
}
}
export function EditorAndSidebar() {
const [leftColumnDefaultSize, setLeftColumnDefaultSize] = useState(20)
const [leftColumnIsOpen, setLeftColumnIsOpen] = useState(true)
const { rootDocId } = useProjectContext() const { rootDocId } = useProjectContext()
const { eventEmitter } = useIdeReactContext() const { eventEmitter } = useIdeReactContext()
const { const {
@@ -62,7 +22,6 @@ export function EditorAndSidebar() {
currentDocumentId: openDocId, currentDocumentId: openDocId,
openInitialDoc, openInitialDoc,
} = useEditorManagerContext() } = useEditorManagerContext()
const { view } = useLayoutContext()
const { projectJoined } = useIdeReactContext() const { projectJoined } = useIdeReactContext()
const { selectEntity } = useSelectFileTreeEntity() const { selectEntity } = useSelectFileTreeEntity()
const [, setOpenFile] = useScopeValue<BinaryFile | null>('openFile') const [, setOpenFile] = useScopeValue<BinaryFile | null>('openFile')
@@ -145,93 +104,38 @@ 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 when restoring a file return {
const editorSidebar = ( selectedEntityCount,
<EditorSidebar openEntity,
shouldShow={view !== 'history'} openDocId,
onFileTreeInit={handleFileTreeInit} handleFileTreeInit,
onFileTreeSelect={handleFileTreeSelect} handleFileTreeSelect,
onFileTreeDelete={handleFileTreeDelete} handleFileTreeDelete,
/> }
) }
if (view === 'history') { function convertFileRefToBinaryFile(fileRef: FileRef): BinaryFile {
return ( return {
<TwoColumnMainContent _id: fileRef._id,
leftColumnId="editor-left-column" name: fileRef.name,
leftColumnContent={ id: fileRef._id,
<> type: 'file',
{editorSidebar} selected: true,
<HistorySidebar /> linkedFileData: fileRef.linkedFileData,
</> created: fileRef.created ? new Date(fileRef.created) : new Date(),
} }
leftColumnDefaultSize={leftColumnDefaultSize} }
setLeftColumnDefaultSize={setLeftColumnDefaultSize}
rightColumnContent={ // `FileViewHeader`, which is TypeScript, expects a BinaryFile, which has a
<HistoryProvider> // `created` property of type `Date`, while `TPRFileViewInfo`, written in JS,
<History /> // into which `FileViewHeader` passes its BinaryFile, expects a file object with
</HistoryProvider> // `created` property of type `string`, which is a mismatch. `TPRFileViewInfo`
} // is the only one making runtime complaints and it seems that other uses of
leftColumnIsOpen={leftColumnIsOpen} // `FileViewHeader` pass in a string for `created`, so that's what this function
setLeftColumnIsOpen={setLeftColumnIsOpen} // does too.
/> export function fileViewFile(fileRef: FileRef) {
) return {
...convertFileRefToBinaryFile(fileRef),
created: fileRef.created,
} }
// 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 (openDocId === undefined) {
rightColumnContent = <NoOpenDocPane />
} else if (selectedEntityCount === 0) {
rightColumnContent = (
<>
{editorPane}
<NoSelectionPane />
</>
)
} else if (selectedEntityCount > 1) {
rightColumnContent = (
<EditorAndPdf
editorContent={
<>
{editorPane}
<MultipleSelectionPane selectedEntityCount={selectedEntityCount} />
</>
}
/>
)
} else if (openEntity) {
rightColumnContent = (
<EditorAndPdf
editorContent={
<>
{editorPane}
{openEntity.type !== 'doc' && (
<FileView file={fileViewFile(openEntity.entity)} />
)}
</>
}
/>
)
}
return (
<TwoColumnMainContent
leftColumnId="editor-left-column"
leftColumnContent={editorSidebar}
leftColumnDefaultSize={leftColumnDefaultSize}
setLeftColumnDefaultSize={setLeftColumnDefaultSize}
rightColumnContent={rightColumnContent}
leftColumnIsOpen={leftColumnIsOpen}
setLeftColumnIsOpen={setLeftColumnIsOpen}
/>
)
} }
@@ -1,32 +1,21 @@
import { useCallback, useEffect, useRef, useState } from 'react' import { useCallback, useEffect, useRef, useState } from 'react'
import { ImperativePanelHandle } from 'react-resizable-panels' import {
import { PanelGroupOnLayout } from 'react-resizable-panels/src/types' ImperativePanelHandle,
PanelGroupOnLayout,
} from 'react-resizable-panels'
export default function useFixedSizeColumn( export default function useFixedSizeColumn(isOpen: boolean) {
defaultSize: number,
isOpen: boolean
) {
const fixedPanelRef = useRef<ImperativePanelHandle>(null) const fixedPanelRef = useRef<ImperativePanelHandle>(null)
const fixedPanelWidthRef = useRef({ size: defaultSize, pixels: 0 }) const fixedPanelSizeRef = useRef<number>(0)
const [initialLayoutDone, setInitialLayoutDone] = useState(false) const [initialLayoutDone, setInitialLayoutDone] = useState(false)
const measureFixedPanelSizePixels = useCallback(() => { const handleLayout: PanelGroupOnLayout = useCallback(() => {
return fixedPanelRef.current?.getSize('pixels') || 0 if (fixedPanelRef.current) {
}, [fixedPanelRef]) fixedPanelSizeRef.current = fixedPanelRef.current.getSize().sizePixels
const handleLayout: PanelGroupOnLayout = useCallback(
sizes => {
// Measure the pixel width here because it's not always up to date in the
// panel's onResize
fixedPanelWidthRef.current = {
size: sizes[0],
pixels: measureFixedPanelSizePixels(),
}
setInitialLayoutDone(true) setInitialLayoutDone(true)
}, }
[measureFixedPanelSizePixels] }, [])
)
useEffect(() => { useEffect(() => {
if (!isOpen) { if (!isOpen) {
@@ -43,24 +32,26 @@ export default function useFixedSizeColumn(
const fixedPanelElement = document.querySelector( const fixedPanelElement = document.querySelector(
`[data-panel-id="${fixedPanelRef.current.getId()}"]` `[data-panel-id="${fixedPanelRef.current.getId()}"]`
) )
if (!fixedPanelElement) {
return
}
const panelGroupElement = fixedPanelElement?.closest('[data-panel-group]') const panelGroupElement = fixedPanelElement.closest('[data-panel-group]')
if (!panelGroupElement || !fixedPanelElement) { if (!panelGroupElement) {
return return
} }
const resizeObserver = new ResizeObserver(() => { const resizeObserver = new ResizeObserver(() => {
fixedPanelRef.current?.resize(fixedPanelWidthRef.current.pixels, 'pixels') // when the panel group resizes, set the size of this panel to the previous size, in pixels
fixedPanelRef.current?.resize({
sizePixels: fixedPanelSizeRef.current,
})
}) })
resizeObserver.observe(panelGroupElement) resizeObserver.observe(panelGroupElement)
return () => resizeObserver.unobserve(panelGroupElement) return () => resizeObserver.unobserve(panelGroupElement)
}, [fixedPanelRef, measureFixedPanelSizePixels, initialLayoutDone, isOpen]) }, [fixedPanelRef, initialLayoutDone, isOpen])
return { return { fixedPanelRef, handleLayout }
fixedPanelRef,
fixedPanelWidthRef,
handleLayout,
}
} }
@@ -0,0 +1,65 @@
import { useCallback, useEffect, useRef } from 'react'
import { ImperativePanelHandle } from 'react-resizable-panels'
import useCollapsiblePanel from '@/features/ide-react/hooks/use-collapsible-panel'
import { useLayoutContext } from '@/shared/context/layout-context'
export const usePdfPane = () => {
const { view, pdfLayout, changeLayout, detachRole, reattach } =
useLayoutContext()
const pdfPanelRef = useRef<ImperativePanelHandle>(null)
const pdfIsOpen = pdfLayout === 'sideBySide' || view === 'pdf'
useCollapsiblePanel(pdfIsOpen, pdfPanelRef)
// close a detached PDF when the detacher PDF view opens
useEffect(() => {
if (pdfIsOpen && detachRole === 'detacher') {
reattach()
}
}, [detachRole, pdfIsOpen, reattach])
// triggered by a double-click on the resizer
const togglePdfPane = useCallback(() => {
if (pdfIsOpen) {
changeLayout('flat', 'editor')
} else {
changeLayout('sideBySide')
}
}, [changeLayout, pdfIsOpen])
// triggered by a click on the toggle button
const setPdfIsOpen = useCallback(
(value: boolean) => {
if (value) {
changeLayout('sideBySide')
} else {
changeLayout('flat', 'editor')
}
},
[changeLayout]
)
// triggered when the PDF pane becomes open
const handlePdfPaneExpand = useCallback(() => {
if (pdfLayout === 'flat' && view === 'editor') {
changeLayout('sideBySide', 'editor')
}
}, [changeLayout, pdfLayout, view])
// triggered when the PDF pane becomes closed (either by dragging or toggling)
const handlePdfPaneCollapse = useCallback(() => {
if (pdfLayout === 'sideBySide') {
changeLayout('flat', 'editor')
}
}, [changeLayout, pdfLayout])
return {
togglePdfPane,
handlePdfPaneExpand,
handlePdfPaneCollapse,
setPdfIsOpen,
pdfIsOpen,
pdfPanelRef,
}
}
@@ -0,0 +1,34 @@
import { useCallback, useState } from 'react'
import useFixedSizeColumn from '@/features/ide-react/hooks/use-fixed-size-column'
import useCollapsiblePanel from '@/features/ide-react/hooks/use-collapsible-panel'
export const useSidebarPane = () => {
const [isOpen, setIsOpen] = useState(true)
const [resizing, setResizing] = useState(false)
const { fixedPanelRef, handleLayout } = useFixedSizeColumn(isOpen)
useCollapsiblePanel(isOpen, fixedPanelRef)
const togglePane = useCallback(() => {
setIsOpen(value => !value)
}, [])
const handlePaneExpand = useCallback(() => {
setIsOpen(true)
}, [])
const handlePaneCollapse = useCallback(() => {
setIsOpen(false)
}, [])
return {
isOpen,
setIsOpen,
fixedPanelRef,
handleLayout,
togglePane,
handlePaneExpand,
handlePaneCollapse,
resizing,
setResizing,
}
}
@@ -145,16 +145,12 @@
height: 100%; height: 100%;
} }
// Hide panel contents while resizing .ide-panel-group-resizing {
.ide-react-main-resizing .ide-react-editor-and-pdf,
.ide-react-main-content-resizing .ide-react-editor-and-pdf,
.ide-react-editor-and-pdf-resizing .ide-react-editor-content,
.ide-react-editor-and-pdf-resizing .pdf {
display: none !important;
}
.ide-react-main-resizing,
.ide-react-main-content-resizing,
.ide-react-editor-and-pdf-resizing {
background-color: white; background-color: white;
// Hide panel contents while resizing
.ide-react-editor-content,
.pdf {
display: none !important;
}
} }
+1 -1
View File
@@ -321,7 +321,7 @@
"react-i18next": "^13.3.1", "react-i18next": "^13.3.1",
"react-linkify": "^1.0.0-alpha", "react-linkify": "^1.0.0-alpha",
"react-refresh": "^0.14.0", "react-refresh": "^0.14.0",
"react-resizable-panels": "^0.0.55", "react-resizable-panels": "^0.0.63",
"react2angular": "^4.0.6", "react2angular": "^4.0.6",
"react2angular-shared-context": "^1.1.0", "react2angular-shared-context": "^1.1.0",
"requirejs": "^2.3.6", "requirejs": "^2.3.6",