From d5b3c10cb549562f5d11e958d9c956a5c7430d21 Mon Sep 17 00:00:00 2001 From: Alf Eaton Date: Tue, 5 Dec 2023 10:19:00 +0000 Subject: [PATCH] Upgrade react-resizable-panels (#15998) GitOrigin-RevId: af799f1a5b4945ad2acbb460806d559fae7416b9 --- package-lock.json | 16 +- .../ide-react/components/editor-and-pdf.tsx | 162 ++++++++-------- .../ide-react/components/editor-sidebar.tsx | 13 +- .../components/editor/editor-pane.tsx | 59 +++--- .../ide-react/components/layout/ide-page.tsx | 4 +- .../components/layout/main-layout.tsx | 159 ++++++++++++---- .../layout/two-column-main-content.tsx | 86 --------- .../features/ide-react/hooks/use-chat-pane.ts | 13 ++ .../use-file-tree.ts} | 178 ++++-------------- .../ide-react/hooks/use-fixed-size-column.ts | 53 +++--- .../features/ide-react/hooks/use-pdf-pane.ts | 65 +++++++ .../ide-react/hooks/use-sidebar-pane.ts | 34 ++++ .../stylesheets/app/editor/ide-react.less | 18 +- services/web/package.json | 2 +- 14 files changed, 438 insertions(+), 424 deletions(-) delete mode 100644 services/web/frontend/js/features/ide-react/components/layout/two-column-main-content.tsx create mode 100644 services/web/frontend/js/features/ide-react/hooks/use-chat-pane.ts rename services/web/frontend/js/features/ide-react/{components/editor-and-sidebar.tsx => hooks/use-file-tree.ts} (56%) create mode 100644 services/web/frontend/js/features/ide-react/hooks/use-pdf-pane.ts create mode 100644 services/web/frontend/js/features/ide-react/hooks/use-sidebar-pane.ts diff --git a/package-lock.json b/package-lock.json index 878361e867..b671bbbdad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34095,9 +34095,9 @@ } }, "node_modules/react-resizable-panels": { - "version": "0.0.55", - "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-0.0.55.tgz", - "integrity": "sha512-J/LTFzUEjJiqwSjVh8gjUXkQDA8MRPjARASfn++d2+KOgA+9UcRYUfE3QBJixer2vkk+ffQ4cq3QzWzzHgqYpQ==", + "version": "0.0.63", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-0.0.63.tgz", + "integrity": "sha512-AfA8b6kouhL4rBvgUGs17uzWVlYPaJIwwTCVeWRxNpUHJlCG1h9RIMlzA1849AZGsaNJO3j/SNdI5SS4GZDE3g==", "dev": true, "peerDependencies": { "react": "^16.14.0 || ^17.0.0 || ^18.0.0", @@ -43709,7 +43709,7 @@ "react-i18next": "^13.3.1", "react-linkify": "^1.0.0-alpha", "react-refresh": "^0.14.0", - "react-resizable-panels": "^0.0.55", + "react-resizable-panels": "^0.0.63", "react2angular": "^4.0.6", "react2angular-shared-context": "^1.1.0", "requirejs": "^2.3.6", @@ -52297,7 +52297,7 @@ "react-i18next": "^13.3.1", "react-linkify": "^1.0.0-alpha", "react-refresh": "^0.14.0", - "react-resizable-panels": "^0.0.55", + "react-resizable-panels": "^0.0.63", "react2angular": "^4.0.6", "react2angular-shared-context": "^1.1.0", "recurly": "^4.0.0", @@ -73465,9 +73465,9 @@ } }, "react-resizable-panels": { - "version": "0.0.55", - "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-0.0.55.tgz", - "integrity": "sha512-J/LTFzUEjJiqwSjVh8gjUXkQDA8MRPjARASfn++d2+KOgA+9UcRYUfE3QBJixer2vkk+ffQ4cq3QzWzzHgqYpQ==", + "version": "0.0.63", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-0.0.63.tgz", + "integrity": "sha512-AfA8b6kouhL4rBvgUGs17uzWVlYPaJIwwTCVeWRxNpUHJlCG1h9RIMlzA1849AZGsaNJO3j/SNdI5SS4GZDE3g==", "dev": true, "requires": {} }, diff --git a/services/web/frontend/js/features/ide-react/components/editor-and-pdf.tsx b/services/web/frontend/js/features/ide-react/components/editor-and-pdf.tsx index 31e3e52536..199cc8e238 100644 --- a/services/web/frontend/js/features/ide-react/components/editor-and-pdf.tsx +++ b/services/web/frontend/js/features/ide-react/components/editor-and-pdf.tsx @@ -1,99 +1,103 @@ -import { ReactNode, useRef, useState } from 'react' -import { useTranslation } from 'react-i18next' -import { - ImperativePanelHandle, - Panel, - PanelGroup, -} from 'react-resizable-panels' +import React, { FC, useState } from 'react' +import { Panel, PanelGroup } from 'react-resizable-panels' +import NoSelectionPane from '@/features/ide-react/components/editor/no-selection-pane' +import FileView from '@/features/file-view/components/file-view' +import { fileViewFile } from '@/features/ide-react/hooks/use-file-tree' +import MultipleSelectionPane from '@/features/ide-react/components/editor/multiple-selection-pane' import { HorizontalResizeHandle } from '@/features/ide-react/components/resize/horizontal-resize-handle' 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 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 = { - editorContent: ReactNode -} - -export default function EditorAndPdf({ editorContent }: EditorProps) { - const { t } = useTranslation() - const { view, pdfLayout, changeLayout, detachRole, reattach } = - useLayoutContext() +export const EditorAndPdf: FC<{ + editorPane: React.ReactNode + selectedEntityCount: number + openEntity: any // TODO +}> = ({ editorPane, selectedEntityCount, openEntity }) => { const [resizing, setResizing] = useState(false) - const pdfPanelRef = useRef(null) - const isDualPane = pdfLayout === 'sideBySide' - const editorIsVisible = isDualPane || view === 'editor' || view === 'file' - const pdfIsOpen = isDualPane || view === 'pdf' + const { t } = useTranslation() - useCollapsiblePanel(pdfIsOpen, pdfPanelRef) + const { + togglePdfPane, + handlePdfPaneExpand, + handlePdfPaneCollapse, + setPdfIsOpen, + pdfIsOpen, + pdfPanelRef, + } = usePdfPane() - const historyIsOpen = view === 'history' + const { view, pdfLayout } = useLayoutContext() - function setPdfIsOpen(isOpen: boolean) { - if (isOpen) { - if (detachRole === 'detacher') { - reattach() - } - changeLayout('sideBySide') - } else { - changeLayout('flat', 'editor') - } - } + const editorIsOpen = + view === 'editor' || view === 'file' || pdfLayout === 'sideBySide' return ( - - {editorIsVisible ? ( - -
- {editorContent} -
-
- ) : null} - setPdfIsOpen(!pdfIsOpen)} - onDragging={setResizing} - > - - {isDualPane ? ( -
- -
- ) : null} -
+ + {/* main */} + {editorIsOpen && ( + <> + + {selectedEntityCount === 0 && } + {selectedEntityCount === 1 && openEntity?.type === 'fileRef' && ( + + )} + {selectedEntityCount === 1 && editorPane} + {selectedEntityCount > 1 && ( + + )} + + + + + + {pdfLayout === 'sideBySide' && ( +
+ +
+ )} +
+ + )} + + {/* pdf */} setPdfIsOpen(!collapsed)} + onCollapse={handlePdfPaneCollapse} + onExpand={handlePdfPaneExpand} className="ide-react-panel" > - {pdfIsOpen || resizing ? : null} + {pdfIsOpen && }
) diff --git a/services/web/frontend/js/features/ide-react/components/editor-sidebar.tsx b/services/web/frontend/js/features/ide-react/components/editor-sidebar.tsx index 128098e203..a12250db8f 100644 --- a/services/web/frontend/js/features/ide-react/components/editor-sidebar.tsx +++ b/services/web/frontend/js/features/ide-react/components/editor-sidebar.tsx @@ -27,11 +27,12 @@ export default function EditorSidebar({ hidden: !shouldShow, })} > - - + + - +
diff --git a/services/web/frontend/js/features/ide-react/components/editor/editor-pane.tsx b/services/web/frontend/js/features/ide-react/components/editor/editor-pane.tsx index 523571bf0c..5c0cb32464 100644 --- a/services/web/frontend/js/features/ide-react/components/editor/editor-pane.tsx +++ b/services/web/frontend/js/features/ide-react/components/editor/editor-pane.tsx @@ -22,33 +22,38 @@ export const EditorPane: FC<{ show: boolean }> = ({ show }) => { ) return ( - - - - {isLoading && } - +
+ + + + {isLoading && } + - {editor.showSymbolPalette && ( - <> - - - }> - - - - - )} - + {editor.showSymbolPalette && ( + <> + + + }> + + + + + )} + +
) } diff --git a/services/web/frontend/js/features/ide-react/components/layout/ide-page.tsx b/services/web/frontend/js/features/ide-react/components/layout/ide-page.tsx index 9b876cfa16..b2e2a64dea 100644 --- a/services/web/frontend/js/features/ide-react/components/layout/ide-page.tsx +++ b/services/web/frontend/js/features/ide-react/components/layout/ide-page.tsx @@ -1,5 +1,5 @@ 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 { useLayoutEventTracking } from '@/features/ide-react/hooks/use-layout-event-tracking' 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' 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 useEditingSessionHeartbeat() // send a batched event when user is active useRegisterUserActivity() // record activity and ensure connection when user is active diff --git a/services/web/frontend/js/features/ide-react/components/layout/main-layout.tsx b/services/web/frontend/js/features/ide-react/components/layout/main-layout.tsx index 4aa409d64b..4500fc7d30 100644 --- a/services/web/frontend/js/features/ide-react/components/layout/main-layout.tsx +++ b/services/web/frontend/js/features/ide-react/components/layout/main-layout.tsx @@ -1,62 +1,149 @@ import { Panel, PanelGroup } from 'react-resizable-panels' -import { useState } from 'react' +import { FC } 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' +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. -// 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() { - const { chatIsOpen } = useLayoutContext() + const { + isOpen: sidebarIsOpen, + setIsOpen: setSidebarIsOpen, + fixedPanelRef: sidebarPanelRef, + handleLayout: handleSidebarLayout, + togglePane: toggleSidebar, + handlePaneExpand: handleSidebarExpand, + handlePaneCollapse: handleSidebarCollapse, + resizing: sidebarResizing, + setResizing: setSidebarResizing, + } = useSidebarPane() - const { fixedPanelRef: chatPanelRef, handleLayout } = useFixedSizeColumn( - CHAT_DEFAULT_SIZE, - chatIsOpen - ) + const { + isOpen: 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 ? ( + + ) : null return (
- - + {/* sidebar */} + + + {view === 'history' && } - {chatIsOpen ? ( - <> - - - + + + + + + + + + {view === 'history' ? ( + + + + ) : ( + + )} - - ) : null} + + {chatIsOpen && ( + <> + + + {/* chat */} + + + + + )} + +
diff --git a/services/web/frontend/js/features/ide-react/components/layout/two-column-main-content.tsx b/services/web/frontend/js/features/ide-react/components/layout/two-column-main-content.tsx deleted file mode 100644 index ad53bbdc88..0000000000 --- a/services/web/frontend/js/features/ide-react/components/layout/two-column-main-content.tsx +++ /dev/null @@ -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> - 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 ( - - setLeftColumnIsOpen(!collapsed)} - > - {leftColumnContent} - - setLeftColumnIsOpen(!leftColumnIsOpen)} - resizable={leftColumnIsOpen} - onDragging={setResizing} - > - - - {rightColumnContent} - - ) -} diff --git a/services/web/frontend/js/features/ide-react/hooks/use-chat-pane.ts b/services/web/frontend/js/features/ide-react/hooks/use-chat-pane.ts new file mode 100644 index 0000000000..6091ec3998 --- /dev/null +++ b/services/web/frontend/js/features/ide-react/hooks/use-chat-pane.ts @@ -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 } +} diff --git a/services/web/frontend/js/features/ide-react/components/editor-and-sidebar.tsx b/services/web/frontend/js/features/ide-react/hooks/use-file-tree.ts similarity index 56% rename from services/web/frontend/js/features/ide-react/components/editor-and-sidebar.tsx rename to services/web/frontend/js/features/ide-react/hooks/use-file-tree.ts index 268e1b5cc2..f0558e4b0d 100644 --- a/services/web/frontend/js/features/ide-react/components/editor-and-sidebar.tsx +++ b/services/web/frontend/js/features/ide-react/hooks/use-file-tree.ts @@ -1,60 +1,20 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react' -import TwoColumnMainContent from '@/features/ide-react/components/layout/two-column-main-content' -import EditorSidebar from '@/features/ide-react/components/editor-sidebar' -import History from '@/features/ide-react/components/history' -import { HistoryProvider } from '@/features/history/context/history-context' -import { useProjectContext } from '@/shared/context/project-context' -import { useLayoutContext } from '@/shared/context/layout-context' +import { FileRef } from '../../../../../types/file-ref' +import { BinaryFile } from '@/features/file-view/types/binary-file' +import { useSelectFileTreeEntity } from '@/features/ide-react/hooks/use-select-file-tree-entity' +import useScopeValue from '@/shared/hooks/use-scope-value' +import { useCallback, useEffect, useRef, useState } from 'react' import { FileTreeDeleteHandler, FileTreeDocumentFindResult, FileTreeFileRefFindResult, FileTreeFindResult, } from '@/features/ide-react/types/file-tree' -import FileView from '@/features/file-view/components/file-view' -import { FileRef } from '../../../../../types/file-ref' -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 { debugConsole } from '@/utils/debugging' +import { useProjectContext } from '@/shared/context/project-context' import { useIdeReactContext } from '@/features/ide-react/context/ide-react-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 { - 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) +export const useFileTree = () => { const { rootDocId } = useProjectContext() const { eventEmitter } = useIdeReactContext() const { @@ -62,7 +22,6 @@ export function EditorAndSidebar() { currentDocumentId: openDocId, openInitialDoc, } = useEditorManagerContext() - const { view } = useLayoutContext() const { projectJoined } = useIdeReactContext() const { selectEntity } = useSelectFileTreeEntity() const [, setOpenFile] = useScopeValue('openFile') @@ -145,93 +104,38 @@ 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 editorSidebar = ( - - ) - - if (view === 'history') { - return ( - - {editorSidebar} - - - } - leftColumnDefaultSize={leftColumnDefaultSize} - setLeftColumnDefaultSize={setLeftColumnDefaultSize} - rightColumnContent={ - - - - } - leftColumnIsOpen={leftColumnIsOpen} - setLeftColumnIsOpen={setLeftColumnIsOpen} - /> - ) + return { + selectedEntityCount, + openEntity, + openDocId, + handleFileTreeInit, + handleFileTreeSelect, + handleFileTreeDelete, + } +} + +function convertFileRefToBinaryFile(fileRef: FileRef): BinaryFile { + 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. +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 = ( - - ) - - let rightColumnContent - - if (openDocId === undefined) { - rightColumnContent = - } else if (selectedEntityCount === 0) { - rightColumnContent = ( - <> - {editorPane} - - - ) - } else if (selectedEntityCount > 1) { - rightColumnContent = ( - - {editorPane} - - - } - /> - ) - } else if (openEntity) { - rightColumnContent = ( - - {editorPane} - {openEntity.type !== 'doc' && ( - - )} - - } - /> - ) - } - - return ( - - ) } diff --git a/services/web/frontend/js/features/ide-react/hooks/use-fixed-size-column.ts b/services/web/frontend/js/features/ide-react/hooks/use-fixed-size-column.ts index fb0fd760cd..95e4984680 100644 --- a/services/web/frontend/js/features/ide-react/hooks/use-fixed-size-column.ts +++ b/services/web/frontend/js/features/ide-react/hooks/use-fixed-size-column.ts @@ -1,32 +1,21 @@ import { useCallback, useEffect, useRef, useState } from 'react' -import { ImperativePanelHandle } from 'react-resizable-panels' -import { PanelGroupOnLayout } from 'react-resizable-panels/src/types' +import { + ImperativePanelHandle, + PanelGroupOnLayout, +} from 'react-resizable-panels' -export default function useFixedSizeColumn( - defaultSize: number, - isOpen: boolean -) { +export default function useFixedSizeColumn(isOpen: boolean) { const fixedPanelRef = useRef(null) - const fixedPanelWidthRef = useRef({ size: defaultSize, pixels: 0 }) + const fixedPanelSizeRef = useRef(0) const [initialLayoutDone, setInitialLayoutDone] = useState(false) - const measureFixedPanelSizePixels = useCallback(() => { - return fixedPanelRef.current?.getSize('pixels') || 0 - }, [fixedPanelRef]) - - 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(), - } + const handleLayout: PanelGroupOnLayout = useCallback(() => { + if (fixedPanelRef.current) { + fixedPanelSizeRef.current = fixedPanelRef.current.getSize().sizePixels setInitialLayoutDone(true) - }, - [measureFixedPanelSizePixels] - ) + } + }, []) useEffect(() => { if (!isOpen) { @@ -43,24 +32,26 @@ export default function useFixedSizeColumn( const fixedPanelElement = document.querySelector( `[data-panel-id="${fixedPanelRef.current.getId()}"]` ) + if (!fixedPanelElement) { + return + } - const panelGroupElement = fixedPanelElement?.closest('[data-panel-group]') - if (!panelGroupElement || !fixedPanelElement) { + const panelGroupElement = fixedPanelElement.closest('[data-panel-group]') + if (!panelGroupElement) { return } 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) return () => resizeObserver.unobserve(panelGroupElement) - }, [fixedPanelRef, measureFixedPanelSizePixels, initialLayoutDone, isOpen]) + }, [fixedPanelRef, initialLayoutDone, isOpen]) - return { - fixedPanelRef, - fixedPanelWidthRef, - handleLayout, - } + return { fixedPanelRef, handleLayout } } diff --git a/services/web/frontend/js/features/ide-react/hooks/use-pdf-pane.ts b/services/web/frontend/js/features/ide-react/hooks/use-pdf-pane.ts new file mode 100644 index 0000000000..dae5401cad --- /dev/null +++ b/services/web/frontend/js/features/ide-react/hooks/use-pdf-pane.ts @@ -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(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, + } +} diff --git a/services/web/frontend/js/features/ide-react/hooks/use-sidebar-pane.ts b/services/web/frontend/js/features/ide-react/hooks/use-sidebar-pane.ts new file mode 100644 index 0000000000..bf4f5cc33f --- /dev/null +++ b/services/web/frontend/js/features/ide-react/hooks/use-sidebar-pane.ts @@ -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, + } +} diff --git a/services/web/frontend/stylesheets/app/editor/ide-react.less b/services/web/frontend/stylesheets/app/editor/ide-react.less index f48a93a90e..3269b32d0a 100644 --- a/services/web/frontend/stylesheets/app/editor/ide-react.less +++ b/services/web/frontend/stylesheets/app/editor/ide-react.less @@ -145,16 +145,12 @@ height: 100%; } -// Hide panel contents while 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 { +.ide-panel-group-resizing { background-color: white; + + // Hide panel contents while resizing + .ide-react-editor-content, + .pdf { + display: none !important; + } } diff --git a/services/web/package.json b/services/web/package.json index 4e2ee14611..89d2a4ad57 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -321,7 +321,7 @@ "react-i18next": "^13.3.1", "react-linkify": "^1.0.0-alpha", "react-refresh": "^0.14.0", - "react-resizable-panels": "^0.0.55", + "react-resizable-panels": "^0.0.63", "react2angular": "^4.0.6", "react2angular-shared-context": "^1.1.0", "requirejs": "^2.3.6",