mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-06-26 18:26:38 +00:00
* Add persistent file locking feature with enhanced UI * Fix file locking to be scoped by chat ID * Add folder locking functionality * Update CHANGES.md to include folder locking functionality * Add early detection of locked files/folders in user prompts * Improve locked files detection with smarter pattern matching and prevent AI from attempting to modify locked files * Add detection for unlocked files to allow AI to continue with modifications in the same chat session * Implement dialog-based Lock Manager with improved styling for dark/light modes * Add remaining files for file locking implementation * refactor(lock-manager): simplify lock management UI and remove scoped lock options Consolidate lock management UI by removing scoped lock options and integrating LockManager directly into the EditorPanel. Simplify the lock management interface by removing the dialog and replacing it with a tab-based view. This improves maintainability and user experience by reducing complexity and streamlining the lock management process. Change Lock & Unlock action to use toast instead of alert. Remove LockManagerDialog as it is now tab based. * Optimize file locking mechanism for better performance - Add in-memory caching to reduce localStorage reads - Implement debounced localStorage writes - Use Map data structures for faster lookups - Add batch operations for locking/unlocking multiple items - Reduce polling frequency and add event-based updates - Add performance monitoring and cross-tab synchronization * refactor(file-locking): simplify file locking mechanism and remove scoped locks This commit removes the scoped locking feature and simplifies the file locking mechanism. The `LockMode` type and related logic have been removed, and all locks are now treated as full locks. The `isLocked` property has been standardized across the codebase, replacing the previous `locked` and `lockMode` properties. Additionally, the `useLockedFilesChecker` hook and `LockAlert` component have been removed as they are no longer needed with the simplified locking system. This gives the LLM a clear understanding of locked files and strict instructions not to make any changes to these files * refactor: remove debug console.log statements --------- Co-authored-by: KevIsDev <zennerd404@gmail.com>
187 lines
7.7 KiB
TypeScript
187 lines
7.7 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 {
|
|
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
|
|
import { LockManager } from './LockManager'; // <-- Import LockManager
|
|
|
|
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(() => {
|
|
if (!editorDocument || !unsavedFiles) {
|
|
return false;
|
|
}
|
|
|
|
// Make sure unsavedFiles is a Set before calling has()
|
|
return unsavedFiles instanceof Set && 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">
|
|
<div className="h-full">
|
|
<Tabs.Root defaultValue="files" className="flex flex-col h-full">
|
|
<PanelHeader className="w-full text-sm font-medium text-bolt-elements-textSecondary px-1">
|
|
<div className="h-full flex-shrink-0 flex items-center justify-between w-full">
|
|
<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.Trigger
|
|
value="locks"
|
|
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',
|
|
)}
|
|
>
|
|
Locks
|
|
</Tabs.Trigger>
|
|
</Tabs.List>
|
|
</div>
|
|
</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.Content value="locks" className="flex-grow overflow-auto focus-visible:outline-none">
|
|
<LockManager />
|
|
</Tabs.Content>
|
|
</Tabs.Root>
|
|
</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 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>
|
|
);
|
|
},
|
|
);
|