feat: Advanced file editor and file tree improvements (#444)

# Features
- File drag and drop upload: Support uploading files and folders via drag and drop
- Binary file handling: Detect binary files and display a friendly message instead of trying to edit them
- Folder download: Download folders as ZIP files (using JSZip library)
- Context menu integration: Full right-click context menu for file operations (rename, delete, copy path, download, new file/folder)
This commit is contained in:
朱见
2026-03-03 20:19:46 +08:00
committed by GitHub
parent 503c384685
commit 97689588aa
30 changed files with 2270 additions and 94 deletions

View File

@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, useEffect, useRef } from 'react';
import type { MouseEvent, MutableRefObject } from 'react';
import type { CodeEditorFile } from '../types/types';
import CodeEditor from './CodeEditor';
@@ -17,6 +17,11 @@ type EditorSidebarProps = {
fillSpace?: boolean;
};
// Minimum width for the left content (file tree, chat, etc.)
const MIN_LEFT_CONTENT_WIDTH = 200;
// Minimum width for the editor sidebar
const MIN_EDITOR_WIDTH = 280;
export default function EditorSidebar({
editingFile,
isMobile,
@@ -31,6 +36,49 @@ export default function EditorSidebar({
fillSpace,
}: EditorSidebarProps) {
const [poppedOut, setPoppedOut] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
const [effectiveWidth, setEffectiveWidth] = useState(editorWidth);
// Adjust editor width when container size changes to ensure buttons are always visible
useEffect(() => {
if (!editingFile || isMobile || poppedOut) return;
const updateWidth = () => {
if (!containerRef.current) return;
const parentElement = containerRef.current.parentElement;
if (!parentElement) return;
const containerWidth = parentElement.clientWidth;
// Calculate maximum allowed editor width
const maxEditorWidth = containerWidth - MIN_LEFT_CONTENT_WIDTH;
if (maxEditorWidth < MIN_EDITOR_WIDTH) {
// Not enough space - pop out the editor so user can still see everything
setPoppedOut(true);
} else if (editorWidth > maxEditorWidth) {
// Editor is too wide - constrain it to ensure left content has space
setEffectiveWidth(maxEditorWidth);
} else {
setEffectiveWidth(editorWidth);
}
};
updateWidth();
window.addEventListener('resize', updateWidth);
// Also use ResizeObserver for more accurate detection
const resizeObserver = new ResizeObserver(updateWidth);
const parentEl = containerRef.current?.parentElement;
if (parentEl) {
resizeObserver.observe(parentEl);
}
return () => {
window.removeEventListener('resize', updateWidth);
resizeObserver.disconnect();
};
}, [editingFile, isMobile, poppedOut, editorWidth]);
if (!editingFile) {
return null;
@@ -54,7 +102,7 @@ export default function EditorSidebar({
const useFlexLayout = editorExpanded || (fillSpace && !hasManualWidth);
return (
<>
<div ref={containerRef} className={`flex h-full flex-shrink-0 min-w-0 ${editorExpanded ? 'flex-1' : ''}`}>
{!editorExpanded && (
<div
ref={resizeHandleRef}
@@ -67,8 +115,8 @@ export default function EditorSidebar({
)}
<div
className={`flex-shrink-0 border-l border-gray-200 dark:border-gray-700 h-full overflow-hidden ${useFlexLayout ? 'flex-1' : ''}`}
style={useFlexLayout ? undefined : { width: `${editorWidth}px` }}
className={`border-l border-gray-200 dark:border-gray-700 h-full overflow-hidden ${useFlexLayout ? 'flex-1 min-w-0' : `flex-shrink-0 min-w-[${MIN_EDITOR_WIDTH}px]`}`}
style={useFlexLayout ? undefined : { width: `${effectiveWidth}px`, minWidth: `${MIN_EDITOR_WIDTH}px` }}
>
<CodeEditor
file={editingFile}
@@ -80,6 +128,6 @@ export default function EditorSidebar({
onPopOut={() => setPoppedOut(true)}
/>
</div>
</>
</div>
);
}