mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-03-10 08:27:40 +00:00
refator(code-editor): make CodeEditor feature based component
- replaced interfaces with types from main-content types
This commit is contained in:
@@ -1,115 +0,0 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import type { MouseEvent as ReactMouseEvent } from 'react';
|
||||
import type { Project } from '../../../types/app';
|
||||
import type { DiffInfo, EditingFile } from '../types/types';
|
||||
|
||||
type UseEditorSidebarOptions = {
|
||||
selectedProject: Project | null;
|
||||
isMobile: boolean;
|
||||
initialWidth?: number;
|
||||
};
|
||||
|
||||
export function useEditorSidebar({
|
||||
selectedProject,
|
||||
isMobile,
|
||||
initialWidth = 600,
|
||||
}: UseEditorSidebarOptions) {
|
||||
const [editingFile, setEditingFile] = useState<EditingFile | null>(null);
|
||||
const [editorWidth, setEditorWidth] = useState(initialWidth);
|
||||
const [editorExpanded, setEditorExpanded] = useState(false);
|
||||
const [isResizing, setIsResizing] = useState(false);
|
||||
const [hasManualWidth, setHasManualWidth] = useState(false);
|
||||
const resizeHandleRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const handleFileOpen = useCallback(
|
||||
(filePath: string, diffInfo: DiffInfo | null = null) => {
|
||||
const normalizedPath = filePath.replace(/\\/g, '/');
|
||||
const fileName = normalizedPath.split('/').pop() || filePath;
|
||||
|
||||
setEditingFile({
|
||||
name: fileName,
|
||||
path: filePath,
|
||||
projectName: selectedProject?.name,
|
||||
diffInfo,
|
||||
});
|
||||
},
|
||||
[selectedProject?.name],
|
||||
);
|
||||
|
||||
const handleCloseEditor = useCallback(() => {
|
||||
setEditingFile(null);
|
||||
setEditorExpanded(false);
|
||||
}, []);
|
||||
|
||||
const handleToggleEditorExpand = useCallback(() => {
|
||||
setEditorExpanded((prev) => !prev);
|
||||
}, []);
|
||||
|
||||
const handleResizeStart = useCallback(
|
||||
(event: ReactMouseEvent<HTMLDivElement>) => {
|
||||
if (isMobile) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Once the user starts dragging, width should be controlled by drag state
|
||||
// instead of "fill available space" layout mode.
|
||||
setHasManualWidth(true);
|
||||
setIsResizing(true);
|
||||
event.preventDefault();
|
||||
},
|
||||
[isMobile],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const handleMouseMove = (event: globalThis.MouseEvent) => {
|
||||
if (!isResizing) {
|
||||
return;
|
||||
}
|
||||
|
||||
const container = resizeHandleRef.current?.parentElement;
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
const newWidth = containerRect.right - event.clientX;
|
||||
|
||||
const minWidth = 300;
|
||||
const maxWidth = containerRect.width * 0.8;
|
||||
|
||||
if (newWidth >= minWidth && newWidth <= maxWidth) {
|
||||
setEditorWidth(newWidth);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
setIsResizing(false);
|
||||
};
|
||||
|
||||
if (isResizing) {
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
document.addEventListener('mouseup', handleMouseUp);
|
||||
document.body.style.cursor = 'col-resize';
|
||||
document.body.style.userSelect = 'none';
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('mouseup', handleMouseUp);
|
||||
document.body.style.cursor = '';
|
||||
document.body.style.userSelect = '';
|
||||
};
|
||||
}, [isResizing]);
|
||||
|
||||
return {
|
||||
editingFile,
|
||||
editorWidth,
|
||||
editorExpanded,
|
||||
hasManualWidth,
|
||||
resizeHandleRef,
|
||||
handleFileOpen,
|
||||
handleCloseEditor,
|
||||
handleToggleEditorExpand,
|
||||
handleResizeStart,
|
||||
};
|
||||
}
|
||||
@@ -1,23 +1,9 @@
|
||||
import type { Dispatch, MouseEvent, RefObject, SetStateAction } from 'react';
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
import type { AppTab, Project, ProjectSession } from '../../../types/app';
|
||||
|
||||
export type SessionLifecycleHandler = (sessionId?: string | null) => void;
|
||||
|
||||
export interface DiffInfo {
|
||||
old_string?: string;
|
||||
new_string?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface EditingFile {
|
||||
name: string;
|
||||
path: string;
|
||||
projectName?: string;
|
||||
diffInfo?: DiffInfo | null;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface TaskMasterTask {
|
||||
export type TaskMasterTask = {
|
||||
id: string | number;
|
||||
title?: string;
|
||||
description?: string;
|
||||
@@ -29,24 +15,24 @@ export interface TaskMasterTask {
|
||||
dependencies?: Array<string | number>;
|
||||
subtasks?: TaskMasterTask[];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
};
|
||||
|
||||
export interface TaskReference {
|
||||
export type TaskReference = {
|
||||
id: string | number;
|
||||
title?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
};
|
||||
|
||||
export type TaskSelection = TaskMasterTask | TaskReference;
|
||||
|
||||
export interface PrdFile {
|
||||
export type PrdFile = {
|
||||
name: string;
|
||||
content?: string;
|
||||
isExisting?: boolean;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
};
|
||||
|
||||
export interface MainContentProps {
|
||||
export type MainContentProps = {
|
||||
selectedProject: Project | null;
|
||||
selectedSession: ProjectSession | null;
|
||||
activeTab: AppTab;
|
||||
@@ -67,9 +53,9 @@ export interface MainContentProps {
|
||||
onNavigateToSession: (targetSessionId: string) => void;
|
||||
onShowSettings: () => void;
|
||||
externalMessageUpdate: number;
|
||||
}
|
||||
};
|
||||
|
||||
export interface MainContentHeaderProps {
|
||||
export type MainContentHeaderProps = {
|
||||
activeTab: AppTab;
|
||||
setActiveTab: Dispatch<SetStateAction<AppTab>>;
|
||||
selectedProject: Project;
|
||||
@@ -77,33 +63,19 @@ export interface MainContentHeaderProps {
|
||||
shouldShowTasksTab: boolean;
|
||||
isMobile: boolean;
|
||||
onMenuClick: () => void;
|
||||
}
|
||||
};
|
||||
|
||||
export interface MainContentStateViewProps {
|
||||
export type MainContentStateViewProps = {
|
||||
mode: 'loading' | 'empty';
|
||||
isMobile: boolean;
|
||||
onMenuClick: () => void;
|
||||
}
|
||||
};
|
||||
|
||||
export interface MobileMenuButtonProps {
|
||||
export type MobileMenuButtonProps = {
|
||||
onMenuClick: () => void;
|
||||
compact?: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
export interface EditorSidebarProps {
|
||||
editingFile: EditingFile | null;
|
||||
isMobile: boolean;
|
||||
editorExpanded: boolean;
|
||||
editorWidth: number;
|
||||
hasManualWidth: boolean;
|
||||
resizeHandleRef: RefObject<HTMLDivElement>;
|
||||
onResizeStart: (event: MouseEvent<HTMLDivElement>) => void;
|
||||
onCloseEditor: () => void;
|
||||
onToggleEditorExpand: () => void;
|
||||
projectPath?: string;
|
||||
fillSpace?: boolean;
|
||||
}
|
||||
|
||||
export interface TaskMasterPanelProps {
|
||||
export type TaskMasterPanelProps = {
|
||||
isVisible: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,14 +8,14 @@ import ErrorBoundary from '../../ErrorBoundary';
|
||||
|
||||
import MainContentHeader from './subcomponents/MainContentHeader';
|
||||
import MainContentStateView from './subcomponents/MainContentStateView';
|
||||
import EditorSidebar from './subcomponents/EditorSidebar';
|
||||
import TaskMasterPanel from './subcomponents/TaskMasterPanel';
|
||||
import type { MainContentProps } from '../types/types';
|
||||
|
||||
import { useTaskMaster } from '../../../contexts/TaskMasterContext';
|
||||
import { useTasksSettings } from '../../../contexts/TasksSettingsContext';
|
||||
import { useUiPreferences } from '../../../hooks/useUiPreferences';
|
||||
import { useEditorSidebar } from '../hooks/useEditorSidebar';
|
||||
import { useEditorSidebar } from '../../code-editor/hooks/useEditorSidebar';
|
||||
import EditorSidebar from '../../code-editor/view/EditorSidebar';
|
||||
import type { Project } from '../../../types/app';
|
||||
|
||||
const AnyStandaloneShell = StandaloneShell as any;
|
||||
@@ -162,16 +162,16 @@ function MainContent({
|
||||
<div className={`h-full overflow-hidden ${activeTab === 'preview' ? 'block' : 'hidden'}`} />
|
||||
</div>
|
||||
|
||||
<EditorSidebar
|
||||
editingFile={editingFile}
|
||||
isMobile={isMobile}
|
||||
editorExpanded={editorExpanded}
|
||||
editorWidth={editorWidth}
|
||||
hasManualWidth={hasManualWidth}
|
||||
resizeHandleRef={resizeHandleRef}
|
||||
onResizeStart={handleResizeStart}
|
||||
onCloseEditor={handleCloseEditor}
|
||||
onToggleEditorExpand={handleToggleEditorExpand}
|
||||
<EditorSidebar
|
||||
editingFile={editingFile}
|
||||
isMobile={isMobile}
|
||||
editorExpanded={editorExpanded}
|
||||
editorWidth={editorWidth}
|
||||
hasManualWidth={hasManualWidth}
|
||||
resizeHandleRef={resizeHandleRef}
|
||||
onResizeStart={handleResizeStart}
|
||||
onCloseEditor={handleCloseEditor}
|
||||
onToggleEditorExpand={handleToggleEditorExpand}
|
||||
projectPath={selectedProject.path}
|
||||
fillSpace={activeTab === 'files'}
|
||||
/>
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import CodeEditor from '../../../CodeEditor';
|
||||
import type { EditorSidebarProps } from '../../types/types';
|
||||
|
||||
const AnyCodeEditor = CodeEditor as any;
|
||||
|
||||
export default function EditorSidebar({
|
||||
editingFile,
|
||||
isMobile,
|
||||
editorExpanded,
|
||||
editorWidth,
|
||||
hasManualWidth,
|
||||
resizeHandleRef,
|
||||
onResizeStart,
|
||||
onCloseEditor,
|
||||
onToggleEditorExpand,
|
||||
projectPath,
|
||||
fillSpace,
|
||||
}: EditorSidebarProps) {
|
||||
const [poppedOut, setPoppedOut] = useState(false);
|
||||
|
||||
if (!editingFile) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isMobile || poppedOut) {
|
||||
return (
|
||||
<AnyCodeEditor
|
||||
file={editingFile}
|
||||
onClose={() => {
|
||||
setPoppedOut(false);
|
||||
onCloseEditor();
|
||||
}}
|
||||
projectPath={projectPath}
|
||||
isSidebar={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Keep "fill space" as default in files tab, but allow user drag to take control.
|
||||
const useFlex = editorExpanded || (fillSpace && !hasManualWidth);
|
||||
|
||||
return (
|
||||
<>
|
||||
{!editorExpanded && (
|
||||
<div
|
||||
ref={resizeHandleRef}
|
||||
onMouseDown={onResizeStart}
|
||||
className="flex-shrink-0 w-1 bg-gray-200 dark:bg-gray-700 hover:bg-blue-500 dark:hover:bg-blue-600 cursor-col-resize transition-colors relative group"
|
||||
title="Drag to resize"
|
||||
>
|
||||
<div className="absolute inset-y-0 left-1/2 -translate-x-1/2 w-1 bg-blue-500 dark:bg-blue-600 opacity-0 group-hover:opacity-100 transition-opacity" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={`flex-shrink-0 border-l border-gray-200 dark:border-gray-700 h-full overflow-hidden ${useFlex ? 'flex-1' : ''}`}
|
||||
style={useFlex ? undefined : { width: `${editorWidth}px` }}
|
||||
>
|
||||
<AnyCodeEditor
|
||||
file={editingFile}
|
||||
onClose={onCloseEditor}
|
||||
projectPath={projectPath}
|
||||
isSidebar
|
||||
isExpanded={editorExpanded}
|
||||
onToggleExpand={onToggleEditorExpand}
|
||||
onPopOut={() => setPoppedOut(true)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user