| import { useStore } from '@nanostores/react'; |
| import { memo, useMemo } from 'react'; |
| import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; |
| import { |
| CodeMirrorEditor, |
| type EditorDocument, |
| type EditorSettings, |
| type OnChangeCallback as OnEditorChange, |
| type OnSaveCallback as OnEditorSave, |
| type OnScrollCallback as OnEditorScroll, |
| } from '~/components/editor/codemirror/CodeMirrorEditor'; |
| import { PanelHeader } from '~/components/ui/PanelHeader'; |
| import { PanelHeaderButton } from '~/components/ui/PanelHeaderButton'; |
| import type { FileMap } from '~/lib/stores/files'; |
| import type { FileHistory } from '~/types/actions'; |
| import { themeStore } from '~/lib/stores/theme'; |
| import { WORK_DIR } from '~/utils/constants'; |
| import { renderLogger } from '~/utils/logger'; |
| import { isMobile } from '~/utils/mobile'; |
| import { FileBreadcrumb } from './FileBreadcrumb'; |
| import { FileTree } from './FileTree'; |
| import { DEFAULT_TERMINAL_SIZE, TerminalTabs } from './terminal/TerminalTabs'; |
| import { workbenchStore } from '~/lib/stores/workbench'; |
|
|
| interface EditorPanelProps { |
| files?: FileMap; |
| unsavedFiles?: Set<string>; |
| editorDocument?: EditorDocument; |
| selectedFile?: string | undefined; |
| isStreaming?: boolean; |
| fileHistory?: Record<string, FileHistory>; |
| onEditorChange?: OnEditorChange; |
| onEditorScroll?: OnEditorScroll; |
| onFileSelect?: (value?: string) => void; |
| onFileSave?: OnEditorSave; |
| onFileReset?: () => void; |
| } |
|
|
| const DEFAULT_EDITOR_SIZE = 100 - DEFAULT_TERMINAL_SIZE; |
|
|
| const editorSettings: EditorSettings = { tabSize: 2 }; |
|
|
| export const EditorPanel = memo( |
| ({ |
| files, |
| unsavedFiles, |
| editorDocument, |
| selectedFile, |
| isStreaming, |
| fileHistory, |
| onFileSelect, |
| onEditorChange, |
| onEditorScroll, |
| onFileSave, |
| onFileReset, |
| }: EditorPanelProps) => { |
| renderLogger.trace('EditorPanel'); |
|
|
| const theme = useStore(themeStore); |
| const showTerminal = useStore(workbenchStore.showTerminal); |
|
|
| const activeFileSegments = useMemo(() => { |
| if (!editorDocument) { |
| return undefined; |
| } |
|
|
| return editorDocument.filePath.split('/'); |
| }, [editorDocument]); |
|
|
| const activeFileUnsaved = useMemo(() => { |
| return editorDocument !== undefined && unsavedFiles?.has(editorDocument.filePath); |
| }, [editorDocument, unsavedFiles]); |
|
|
| return ( |
| <PanelGroup direction="vertical"> |
| <Panel defaultSize={showTerminal ? DEFAULT_EDITOR_SIZE : 100} minSize={20}> |
| <PanelGroup direction="horizontal"> |
| <Panel defaultSize={20} minSize={10} collapsible> |
| <div className="flex flex-col border-r border-bolt-elements-borderColor h-full"> |
| <PanelHeader> |
| <div className="i-ph:tree-structure-duotone shrink-0" /> |
| Files |
| </PanelHeader> |
| <FileTree |
| className="h-full" |
| files={files} |
| hideRoot |
| unsavedFiles={unsavedFiles} |
| fileHistory={fileHistory} |
| rootFolder={WORK_DIR} |
| selectedFile={selectedFile} |
| onFileSelect={onFileSelect} |
| /> |
| </div> |
| </Panel> |
| <PanelResizeHandle /> |
| <Panel className="flex flex-col" defaultSize={80} minSize={20}> |
| <PanelHeader className="overflow-x-auto"> |
| {activeFileSegments?.length && ( |
| <div className="flex items-center flex-1 text-sm"> |
| <FileBreadcrumb pathSegments={activeFileSegments} files={files} onFileSelect={onFileSelect} /> |
| {activeFileUnsaved && ( |
| <div className="flex gap-1 ml-auto -mr-1.5"> |
| <PanelHeaderButton onClick={onFileSave}> |
| <div className="i-ph:floppy-disk-duotone" /> |
| Save |
| </PanelHeaderButton> |
| <PanelHeaderButton onClick={onFileReset}> |
| <div className="i-ph:clock-counter-clockwise-duotone" /> |
| Reset |
| </PanelHeaderButton> |
| </div> |
| )} |
| </div> |
| )} |
| </PanelHeader> |
| <div className="h-full flex-1 overflow-hidden"> |
| <CodeMirrorEditor |
| theme={theme} |
| editable={!isStreaming && editorDocument !== undefined} |
| settings={editorSettings} |
| doc={editorDocument} |
| autoFocusOnDocumentChange={!isMobile()} |
| onScroll={onEditorScroll} |
| onChange={onEditorChange} |
| onSave={onFileSave} |
| /> |
| </div> |
| </Panel> |
| </PanelGroup> |
| </Panel> |
| <PanelResizeHandle /> |
| <TerminalTabs /> |
| </PanelGroup> |
| ); |
| }, |
| ); |
|
|