[ide-react] Improve file tree and outline components in the editor sidebar (#16225)

* Upgrade react-resizable-panels
* Add FileTreeOpenProvider
* Add OutlineProvider and OutlineContainer
* Convert Outline tests to Cypress

GitOrigin-RevId: afd9ae8190edf37642e36a4ffb331f1182c8982d
This commit is contained in:
Alf Eaton
2023-12-18 09:03:53 +00:00
committed by Copybot
parent 5e7665e322
commit c2b553e915
37 changed files with 869 additions and 721 deletions
@@ -2,7 +2,6 @@ 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'
@@ -12,12 +11,12 @@ 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'
import { fileViewFile } from '@/features/ide-react/util/file-view'
import { useFileTreeOpenContext } from '@/features/ide-react/context/file-tree-open-context'
export const EditorAndPdf: FC<{
editorPane: React.ReactNode
selectedEntityCount: number
openEntity: any // TODO
}> = ({ editorPane, selectedEntityCount, openEntity }) => {
}> = ({ editorPane }) => {
const [resizing, setResizing] = useState(false)
const { t } = useTranslation()
@@ -33,6 +32,8 @@ export const EditorAndPdf: FC<{
const { view, pdfLayout } = useLayoutContext()
const { selectedEntityCount, openEntity } = useFileTreeOpenContext()
const editorIsOpen =
view === 'editor' || view === 'file' || pdfLayout === 'sideBySide'
@@ -50,8 +51,8 @@ export const EditorAndPdf: FC<{
<Panel
id="panel-main"
order={1}
defaultSizePercentage={50}
minSizePercentage={25}
defaultSize={50}
minSize={25}
className={classNames('ide-react-panel', {
'ide-panel-group-resizing': resizing,
})}
@@ -96,8 +97,8 @@ export const EditorAndPdf: FC<{
ref={pdfPanelRef}
id="panel-pdf"
order={2}
defaultSizePercentage={50}
minSizePercentage={25}
defaultSize={50}
minSize={25}
collapsible
onCollapse={handlePdfPaneCollapse}
onExpand={handlePdfPaneExpand}
@@ -1,47 +1,45 @@
import React from 'react'
import { Panel, PanelGroup } from 'react-resizable-panels'
import { VerticalResizeHandle } from '@/features/ide-react/components/resize/vertical-resize-handle'
import { FileTree } from '@/features/ide-react/components/file-tree'
import {
FileTreeDeleteHandler,
FileTreeSelectHandler,
} from '@/features/ide-react/types/file-tree'
import classNames from 'classnames'
import { useLayoutContext } from '@/shared/context/layout-context'
import { OutlineContainer } from '@/features/outline/components/outline-container'
import { useOutlinePane } from '@/features/ide-react/hooks/use-outline-pane'
type EditorSidebarProps = {
shouldShow?: boolean
onFileTreeInit: () => void
onFileTreeSelect: FileTreeSelectHandler
onFileTreeDelete: FileTreeDeleteHandler
}
export default function EditorSidebar() {
const { view } = useLayoutContext()
const { outlineDisabled, outlineRef } = useOutlinePane()
export default function EditorSidebar({
shouldShow = false,
onFileTreeInit,
onFileTreeSelect,
onFileTreeDelete,
}: EditorSidebarProps) {
return (
<aside
className={classNames('ide-react-editor-sidebar', {
hidden: !shouldShow,
hidden: view === 'history',
})}
>
<PanelGroup autoSaveId="ide-editor-sidebar-layout" direction="vertical">
<Panel
defaultSizePercentage={75}
defaultSize={50}
minSize={25}
className="ide-react-file-tree-panel"
id="panel-file-tree"
order={1}
>
<FileTree
onInit={onFileTreeInit}
onSelect={onFileTreeSelect}
onDelete={onFileTreeDelete}
/>
<FileTree />
</Panel>
<VerticalResizeHandle />
<Panel defaultSizePercentage={25} id="panel-outline">
<div className="outline-container" />
<VerticalResizeHandle disabled={outlineDisabled} />
<Panel
defaultSize={50}
maxSize={75}
id="panel-outline"
order={2}
collapsible
ref={outlineRef}
style={{ minHeight: 32 }} // keep the header visible
>
<OutlineContainer />
</Panel>
</PanelGroup>
</aside>
@@ -7,13 +7,15 @@ import classNames from 'classnames'
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'
import { useFileTreeOpenContext } from '@/features/ide-react/context/file-tree-open-context'
const SymbolPalettePane = lazy(
() => import('@/features/ide-react/components/editor/symbol-palette-pane')
)
export const EditorPane: FC<{ show: boolean }> = ({ show }) => {
export const EditorPane: FC = () => {
const [editor] = useScopeValue<EditorScopeValue>('editor')
const { selectedEntityCount, openEntity } = useFileTreeOpenContext()
const isLoading = Boolean(
(!editor.sharejs_doc || editor.opening) &&
@@ -22,12 +24,12 @@ export const EditorPane: FC<{ show: boolean }> = ({ show }) => {
)
return (
<div className="ide-react-editor-content full-size">
<PanelGroup
autoSaveId="ide-editor-layout"
direction="vertical"
className={classNames({ hidden: !show })}
>
<div
className={classNames('ide-react-editor-content', 'full-size', {
hidden: openEntity?.type !== 'doc' || selectedEntityCount !== 1,
})}
>
<PanelGroup autoSaveId="ide-editor-layout" direction="vertical">
<Panel
id="panel-source-editor"
order={1}
@@ -43,9 +45,9 @@ export const EditorPane: FC<{ show: boolean }> = ({ show }) => {
<Panel
id="panel-symbol-palette"
order={2}
defaultSizePixels={250}
minSizePixels={250}
maxSizePixels={336}
defaultSize={25}
minSize={10}
maxSize={50}
>
<Suspense fallback={<FullSizeLoadingSpinner delay={500} />}>
<SymbolPalettePane />
@@ -1,26 +1,19 @@
import FileTreeRoot from '@/features/file-tree/components/file-tree-root'
import React, { useCallback, useState } from 'react'
import { useUserContext } from '@/shared/context/user-context'
import { useReferencesContext } from '@/features/ide-react/context/references-context'
import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
import { useConnectionContext } from '@/features/ide-react/context/connection-context'
import {
FileTreeDeleteHandler,
FileTreeSelectHandler,
} from '@/features/ide-react/types/file-tree'
import { RefProviders } from '../../../../../types/user'
import FileTreeRoot from '@/features/file-tree/components/file-tree-root'
import { useFileTreeOpenContext } from '@/features/ide-react/context/file-tree-open-context'
type FileTreeProps = {
onInit: () => void
onSelect: FileTreeSelectHandler
onDelete: FileTreeDeleteHandler
}
export function FileTree({ onInit, onSelect, onDelete }: FileTreeProps) {
export function FileTree() {
const user = useUserContext()
const { indexAllReferences } = useReferencesContext()
const { setStartedFreeTrial } = useIdeReactContext()
const { isConnected } = useConnectionContext()
const { handleFileTreeInit, handleFileTreeSelect, handleFileTreeDelete } =
useFileTreeOpenContext()
const [refProviders, setRefProviders] = useState<RefProviders>(
() => user.refProviders || {}
@@ -45,9 +38,9 @@ export function FileTree({ onInit, onSelect, onDelete }: FileTreeProps) {
setRefProviderEnabled={setRefProviderEnabled}
setStartedFreeTrial={setStartedFreeTrial}
isConnected={isConnected}
onInit={onInit}
onSelect={onSelect}
onDelete={onDelete}
onInit={handleFileTreeInit}
onSelect={handleFileTreeSelect}
onDelete={handleFileTreeDelete}
/>
</div>
)
@@ -11,11 +11,11 @@ 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'
import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
export const MainLayout: FC = () => {
const { view } = useLayoutContext()
@@ -43,23 +43,12 @@ export const MainLayout: FC = () => {
handlePaneExpand: handleChatExpand,
} = useChatPane()
const {
selectedEntityCount,
openEntity,
openDocId,
handleFileTreeInit,
handleFileTreeSelect,
handleFileTreeDelete,
} = useFileTree()
const { currentDocumentId } = useEditorManagerContext()
const { t } = useTranslation()
// keep the editor pane open
const editorPane = openDocId ? (
<EditorPane
show={openEntity?.type === 'doc' && selectedEntityCount === 1}
/>
) : null
// keep the editor pane open when a doc is open, even if the history view is open
const editorPane = currentDocumentId ? <EditorPane /> : null
return (
<div className="ide-react-main">
@@ -78,18 +67,14 @@ export const MainLayout: FC = () => {
ref={sidebarPanelRef}
id="panel-sidebar"
order={1}
defaultSizePixels={200}
minSizePixels={150}
defaultSize={10}
minSize={10}
maxSize={30}
collapsible
onCollapse={handleSidebarCollapse}
onExpand={handleSidebarExpand}
>
<EditorSidebar
shouldShow={view !== 'history'}
onFileTreeInit={handleFileTreeInit}
onFileTreeSelect={handleFileTreeSelect}
onFileTreeDelete={handleFileTreeDelete}
/>
<EditorSidebar />
{view === 'history' && <HistorySidebar />}
</Panel>
@@ -120,11 +105,7 @@ export const MainLayout: FC = () => {
<History />
</HistoryProvider>
) : (
<EditorAndPdf
editorPane={editorPane}
selectedEntityCount={selectedEntityCount}
openEntity={openEntity}
/>
<EditorAndPdf editorPane={editorPane} />
)}
</Panel>
@@ -139,8 +120,9 @@ export const MainLayout: FC = () => {
ref={chatPanelRef}
id="panel-chat"
order={2}
defaultSizePixels={200}
minSizePixels={150}
defaultSize={20}
minSize={10}
maxSize={30}
collapsible
onCollapse={handleChatCollapse}
onExpand={handleChatExpand}
@@ -1,13 +1,19 @@
import { PanelResizeHandle } from 'react-resizable-panels'
import { useTranslation } from 'react-i18next'
import { PanelResizeHandleProps } from 'react-resizable-panels/dist/declarations/src/PanelResizeHandle'
import classNames from 'classnames'
export function VerticalResizeHandle(props: PanelResizeHandleProps) {
const { t } = useTranslation()
return (
<PanelResizeHandle {...props}>
<div className="vertical-resize-handle" title={t('resize')} />
<div
className={classNames('vertical-resize-handle', {
'vertical-resize-handle-enabled': !props.disabled,
})}
title={t('resize')}
/>
</PanelResizeHandle>
)
}