mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-06-26 18:26:38 +00:00
- Add support for `PREVIEW_CONSOLE_ERROR` in WebContainer error handling - Introduce new Search component for text search functionality - Extend `ScrollPosition` interface to include `line` and `column` - Implement scroll-to-line functionality in CodeMirrorEditor - Add tab-based navigation for files and search in EditorPanel This commit introduces several enhancements to the editor, including improved error handling, better scrolling capabilities, and a new search feature. The changes are focused on improving the user experience and adding new functionality to the editor components.
165 lines
6.5 KiB
TypeScript
165 lines
6.5 KiB
TypeScript
import { useStore } from '@nanostores/react';
|
|
import { memo, useMemo } from 'react';
|
|
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
|
import * as Tabs from '@radix-ui/react-tabs'; // <-- Import Radix UI Tabs
|
|
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';
|
|
import { Search } from './Search'; // <-- Ensure Search is imported
|
|
import { classNames } from '~/utils/classNames'; // <-- Import classNames if not already present
|
|
|
|
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={15} collapsible className="border-r border-bolt-elements-borderColor">
|
|
<Tabs.Root defaultValue="files" className="flex flex-col h-full">
|
|
<PanelHeader className="w-full text-sm font-medium text-bolt-elements-textSecondary px-1">
|
|
<Tabs.List className="h-full flex-shrink-0 flex items-center">
|
|
<Tabs.Trigger
|
|
value="files"
|
|
className={classNames(
|
|
'h-full bg-transparent hover:bg-bolt-elements-background-depth-3 py-0.5 px-2 rounded-lg text-sm font-medium text-bolt-elements-textTertiary hover:text-bolt-elements-textPrimary data-[state=active]:text-bolt-elements-textPrimary',
|
|
)}
|
|
>
|
|
Files
|
|
</Tabs.Trigger>
|
|
<Tabs.Trigger
|
|
value="search"
|
|
className={classNames(
|
|
'h-full bg-transparent hover:bg-bolt-elements-background-depth-3 py-0.5 px-2 rounded-lg text-sm font-medium text-bolt-elements-textTertiary hover:text-bolt-elements-textPrimary data-[state=active]:text-bolt-elements-textPrimary',
|
|
)}
|
|
>
|
|
Search
|
|
</Tabs.Trigger>
|
|
</Tabs.List>
|
|
</PanelHeader>
|
|
|
|
<Tabs.Content value="files" className="flex-grow overflow-auto focus-visible:outline-none">
|
|
<FileTree
|
|
className="h-full"
|
|
files={files}
|
|
hideRoot
|
|
unsavedFiles={unsavedFiles}
|
|
fileHistory={fileHistory}
|
|
rootFolder={WORK_DIR}
|
|
selectedFile={selectedFile}
|
|
onFileSelect={onFileSelect}
|
|
/>
|
|
</Tabs.Content>
|
|
|
|
<Tabs.Content value="search" className="flex-grow overflow-auto focus-visible:outline-none">
|
|
<Search />
|
|
</Tabs.Content>
|
|
</Tabs.Root>
|
|
</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 modern-scrollbar">
|
|
<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>
|
|
);
|
|
},
|
|
);
|