Просмотр исходного кода

Merge pull request #23384 from overleaf/mj-ide-collapsible-rail

[web] Make panels collapsible in editor redesign

GitOrigin-RevId: bc979e3b6028666d3e0aa751e341d838615c1aac
David 1 год назад
Родитель
Сommit
b5c11370d6

+ 2 - 0
services/web/frontend/extracted-translations.json

@@ -1737,8 +1737,10 @@
   "toolbar_undo": "",
   "toolbar_undo_redo_actions": "",
   "tooltip_hide_filetree": "",
+  "tooltip_hide_panel": "",
   "tooltip_hide_pdf": "",
   "tooltip_show_filetree": "",
+  "tooltip_show_panel": "",
   "tooltip_show_pdf": "",
   "total_due_today": "",
   "total_per_month": "",

+ 10 - 1
services/web/frontend/js/features/ide-redesign/components/file-tree-outline-panel.tsx

@@ -2,8 +2,11 @@ import { Panel, PanelGroup } from 'react-resizable-panels'
 import { FileTree } from '@/features/ide-react/components/file-tree'
 import { OutlineContainer } from '@/features/outline/components/outline-container'
 import { VerticalResizeHandle } from '@/features/ide-react/components/resize/vertical-resize-handle'
+import { useOutlinePane } from '@/features/ide-react/hooks/use-outline-pane'
 
 function FileTreeOutlinePanel() {
+  const { outlineEnabled, outlinePanelRef } = useOutlinePane()
+
   return (
     <PanelGroup
       autoSaveId="ide-redesign-file-tree-outline"
@@ -17,12 +20,18 @@ function FileTreeOutlinePanel() {
       >
         <FileTree />
       </Panel>
-      <VerticalResizeHandle hitAreaMargins={{ coarse: 0, fine: 0 }} />
+      <VerticalResizeHandle
+        hitAreaMargins={{ coarse: 0, fine: 0 }}
+        disabled={!outlineEnabled}
+      />
       <Panel
         defaultSize={50}
         maxSize={75}
         id="ide-redesign-file-outline"
         order={2}
+        collapsible
+        ref={outlinePanelRef}
+        style={{ minHeight: 36 }} // keep the header visible
       >
         <OutlineContainer />
       </Panel>

+ 27 - 5
services/web/frontend/js/features/ide-redesign/components/main-layout.tsx

@@ -5,8 +5,20 @@ import PdfPreview from '@/features/pdf-preview/components/pdf-preview'
 import { Editor } from './editor'
 import { RailLayout } from './rail'
 import { Toolbar } from './toolbar/toolbar'
+import { HorizontalToggler } from '@/features/ide-react/components/resize/horizontal-toggler'
+import { useTranslation } from 'react-i18next'
+import { usePdfPane } from '../hooks/use-pdf-pane'
 
 export default function MainLayout() {
+  const {
+    isOpen: isPdfOpen,
+    setIsOpen: setIsPdfOpen,
+    panelRef: pdfPanelRef,
+    handlePaneCollapse: handlePdfPaneCollapse,
+    handlePaneExpand: handlePdfPaneExpand,
+    togglePane: togglePdfPane,
+  } = usePdfPane()
+  const { t } = useTranslation()
   return (
     <div className="ide-redesign-main">
       <Toolbar />
@@ -19,10 +31,6 @@ export default function MainLayout() {
           })}
         >
           <RailLayout />
-          <HorizontalResizeHandle
-            resizable
-            hitAreaMargins={{ coarse: 0, fine: 0 }}
-          />
           <Panel id="ide-redesign-editor-panel" order={2}>
             <div className="ide-redesign-editor-container">
               <Editor />
@@ -30,12 +38,26 @@ export default function MainLayout() {
           </Panel>
           <HorizontalResizeHandle
             resizable
+            onDoubleClick={togglePdfPane}
             hitAreaMargins={{ coarse: 0, fine: 0 }}
-          />
+          >
+            <HorizontalToggler
+              id="ide-redesign-pdf-panel"
+              togglerType="east"
+              isOpen={isPdfOpen}
+              setIsOpen={setIsPdfOpen}
+              tooltipWhenOpen={t('tooltip_hide_pdf')}
+              tooltipWhenClosed={t('tooltip_show_pdf')}
+            />
+          </HorizontalResizeHandle>
           <Panel
+            collapsible
             className="ide-redesign-pdf-container"
             id="ide-redesign-pdf-panel"
             order={2}
+            ref={pdfPanelRef}
+            onExpand={handlePdfPaneExpand}
+            onCollapse={handlePdfPaneCollapse}
           >
             <PdfPreview />
           </Panel>

+ 56 - 9
services/web/frontend/js/features/ide-redesign/components/rail.tsx

@@ -10,6 +10,11 @@ import { RailTabKey, useRailTabContext } from '../contexts/rail-tab-context'
 import FileTreeOutlinePanel from './file-tree-outline-panel'
 import { ChatIndicator, ChatPane } from './chat'
 import getMeta from '@/utils/meta'
+import { HorizontalResizeHandle } from '@/features/ide-react/components/resize/horizontal-resize-handle'
+import { HorizontalToggler } from '@/features/ide-react/components/resize/horizontal-toggler'
+import { useRail } from '../hooks/use-rail'
+import { useTranslation } from 'react-i18next'
+import classNames from 'classnames'
 
 type RailElement = {
   icon: AvailableUnfilledIcon
@@ -59,6 +64,15 @@ const RAIL_TABS: RailElement[] = [
 ]
 
 export const RailLayout = () => {
+  const { t } = useTranslation()
+  const {
+    isOpen,
+    setIsOpen,
+    panelRef,
+    handlePaneCollapse,
+    handlePaneExpand,
+    togglePane,
+  } = useRail()
   const { selectedTab, setSelectedTab } = useRailTabContext()
 
   const { setLeftMenuShown } = useLayoutContext()
@@ -74,16 +88,26 @@ export const RailLayout = () => {
     [setLeftMenuShown]
   )
 
+  const onTabSelect = useCallback(
+    (key: string | null) => {
+      if (key === selectedTab) {
+        togglePane()
+      } else {
+        // Change the selected tab and make sure it's open
+        setSelectedTab((key ?? 'file-tree') as RailTabKey)
+        setIsOpen(true)
+      }
+    },
+    [setSelectedTab, selectedTab, setIsOpen, togglePane]
+  )
+
   return (
     <TabContainer
       mountOnEnter // Only render when necessary (so that we can lazy load tab content)
       unmountOnExit={false} // TODO: Should we unmount the tabs when they're not used?
       transition={false}
       activeKey={selectedTab}
-      onSelect={useCallback(
-        key => setSelectedTab(key ?? undefined),
-        [setSelectedTab]
-      )}
+      onSelect={onTabSelect}
       id="ide-rail-tabs"
     >
       <div className="ide-rail">
@@ -94,7 +118,7 @@ export const RailLayout = () => {
           {RAIL_TABS.filter(({ hide }) => !hide).map(
             ({ icon, key, indicator }) => (
               <RailTab
-                active={selectedTab === key}
+                open={isOpen && selectedTab === key}
                 key={key}
                 eventKey={key}
                 icon={icon}
@@ -114,6 +138,10 @@ export const RailLayout = () => {
         defaultSize={15}
         minSize={5}
         maxSize={80}
+        ref={panelRef}
+        collapsible
+        onCollapse={handlePaneCollapse}
+        onExpand={handlePaneExpand}
       >
         <div className="ide-rail-content">
           <Tab.Content>
@@ -125,6 +153,20 @@ export const RailLayout = () => {
           </Tab.Content>
         </div>
       </Panel>
+      <HorizontalResizeHandle
+        resizable
+        hitAreaMargins={{ coarse: 0, fine: 0 }}
+        onDoubleClick={togglePane}
+      >
+        <HorizontalToggler
+          id="ide-redesign-sidebar-panel"
+          togglerType="west"
+          isOpen={isOpen}
+          setIsOpen={setIsOpen}
+          tooltipWhenOpen={t('tooltip_hide_panel')}
+          tooltipWhenClosed={t('tooltip_show_panel')}
+        />
+      </HorizontalResizeHandle>
     </TabContainer>
   )
 }
@@ -132,17 +174,22 @@ export const RailLayout = () => {
 const RailTab = ({
   icon,
   eventKey,
-  active,
+  open,
   indicator,
 }: {
   icon: AvailableUnfilledIcon
   eventKey: string
-  active: boolean
+  open: boolean
   indicator?: ReactElement
 }) => {
   return (
-    <NavLink eventKey={eventKey} className="ide-rail-tab-link">
-      {active ? (
+    <NavLink
+      eventKey={eventKey}
+      className={classNames('ide-rail-tab-link', {
+        'open-rail': open,
+      })}
+    >
+      {open ? (
         <MaterialIcon className="ide-rail-tab-link-icon" type={icon} />
       ) : (
         <MaterialIcon className="ide-rail-tab-link-icon" type={icon} unfilled />

+ 7 - 0
services/web/frontend/js/features/ide-redesign/hooks/use-pdf-pane.tsx

@@ -0,0 +1,7 @@
+import { useRail } from './use-rail'
+
+export const usePdfPane = () => {
+  // FIXME: This is temporary, to avoid clashing with the existing usePdfPane
+  // which uses the layout context. That's the correct approach.
+  return useRail()
+}

+ 33 - 0
services/web/frontend/js/features/ide-redesign/hooks/use-rail.tsx

@@ -0,0 +1,33 @@
+import { useCallback, useRef, useState } from 'react'
+import useCollapsiblePanel from '@/features/ide-react/hooks/use-collapsible-panel'
+import { ImperativePanelHandle } from 'react-resizable-panels'
+
+export const useRail = () => {
+  const [isOpen, setIsOpen] = useState(true)
+  const [resizing, setResizing] = useState(false)
+  const panelRef = useRef<ImperativePanelHandle>(null)
+  useCollapsiblePanel(isOpen, panelRef)
+
+  const togglePane = useCallback(() => {
+    setIsOpen(value => !value)
+  }, [])
+
+  const handlePaneExpand = useCallback(() => {
+    setIsOpen(true)
+  }, [])
+
+  const handlePaneCollapse = useCallback(() => {
+    setIsOpen(false)
+  }, [])
+
+  return {
+    isOpen,
+    setIsOpen,
+    panelRef,
+    togglePane,
+    handlePaneExpand,
+    handlePaneCollapse,
+    resizing,
+    setResizing,
+  }
+}

+ 1 - 1
services/web/frontend/stylesheets/bootstrap-5/pages/editor/rail.scss

@@ -28,7 +28,7 @@
     font-size: 20px;
   }
 
-  &.active {
+  &.open-rail {
     color: var(--ide-rail-color);
     background-color: var(--ide-rail-link-active-background);
 

+ 2 - 0
services/web/locales/en.json

@@ -2265,8 +2265,10 @@
   "toolbar_undo": "Undo",
   "toolbar_undo_redo_actions": "Undo/Redo actions",
   "tooltip_hide_filetree": "Click to hide the file tree",
+  "tooltip_hide_panel": "Click to hide the panel",
   "tooltip_hide_pdf": "Click to hide the PDF",
   "tooltip_show_filetree": "Click to show the file tree",
+  "tooltip_show_panel": "Click to show the panel",
   "tooltip_show_pdf": "Click to show the PDF",
   "top_pick": "Top pick",
   "total": "Total",