refator(code-editor): make CodeEditor feature based component

- replaced interfaces with types from main-content types
This commit is contained in:
Haileyesus
2026-02-20 08:55:28 +03:00
parent bf4bc361bc
commit b63d827ccc
21 changed files with 1369 additions and 950 deletions

View File

@@ -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,
};
}

View File

@@ -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;
}
};

View File

@@ -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'}
/>

View File

@@ -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>
</>
);
}