Refactor Settings, FileTree, GitPanel, Shell, and CodeEditor components (#402)

This commit is contained in:
Haileyesus
2026-02-25 19:07:07 +03:00
committed by GitHub
parent 23801e9cc1
commit 5e3a7b69d7
149 changed files with 11627 additions and 8453 deletions

View File

@@ -0,0 +1,234 @@
import { EditorView } from '@codemirror/view';
import { unifiedMergeView } from '@codemirror/merge';
import type { Extension } from '@codemirror/state';
import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useCodeEditorDocument } from '../hooks/useCodeEditorDocument';
import { useCodeEditorSettings } from '../hooks/useCodeEditorSettings';
import { useEditorKeyboardShortcuts } from '../hooks/useEditorKeyboardShortcuts';
import type { CodeEditorFile } from '../types/types';
import { createMinimapExtension, createScrollToFirstChunkExtension, getLanguageExtensions } from '../utils/editorExtensions';
import { getEditorStyles } from '../utils/editorStyles';
import { createEditorToolbarPanelExtension } from '../utils/editorToolbarPanel';
import CodeEditorFooter from './subcomponents/CodeEditorFooter';
import CodeEditorHeader from './subcomponents/CodeEditorHeader';
import CodeEditorLoadingState from './subcomponents/CodeEditorLoadingState';
import CodeEditorSurface from './subcomponents/CodeEditorSurface';
type CodeEditorProps = {
file: CodeEditorFile;
onClose: () => void;
projectPath?: string;
isSidebar?: boolean;
isExpanded?: boolean;
onToggleExpand?: (() => void) | null;
onPopOut?: (() => void) | null;
};
export default function CodeEditor({
file,
onClose,
projectPath,
isSidebar = false,
isExpanded = false,
onToggleExpand = null,
onPopOut = null,
}: CodeEditorProps) {
const { t } = useTranslation('codeEditor');
const [isFullscreen, setIsFullscreen] = useState(false);
const [showDiff, setShowDiff] = useState(Boolean(file.diffInfo));
const [markdownPreview, setMarkdownPreview] = useState(false);
const {
isDarkMode,
wordWrap,
minimapEnabled,
showLineNumbers,
fontSize,
} = useCodeEditorSettings();
const {
content,
setContent,
loading,
saving,
saveSuccess,
saveError,
handleSave,
handleDownload,
} = useCodeEditorDocument({
file,
projectPath,
});
const isMarkdownFile = useMemo(() => {
const extension = file.name.split('.').pop()?.toLowerCase();
return extension === 'md' || extension === 'markdown';
}, [file.name]);
const minimapExtension = useMemo(
() => (
createMinimapExtension({
file,
showDiff,
minimapEnabled,
isDarkMode,
})
),
[file, isDarkMode, minimapEnabled, showDiff],
);
const scrollToFirstChunkExtension = useMemo(
() => createScrollToFirstChunkExtension({ file, showDiff }),
[file, showDiff],
);
const toolbarPanelExtension = useMemo(
() => (
createEditorToolbarPanelExtension({
file,
showDiff,
isSidebar,
isExpanded,
onToggleDiff: () => setShowDiff((previous) => !previous),
onPopOut,
onToggleExpand,
labels: {
changes: t('toolbar.changes'),
previousChange: t('toolbar.previousChange'),
nextChange: t('toolbar.nextChange'),
hideDiff: t('toolbar.hideDiff'),
showDiff: t('toolbar.showDiff'),
collapse: t('toolbar.collapse'),
expand: t('toolbar.expand'),
},
})
),
[file, isExpanded, isSidebar, onPopOut, onToggleExpand, showDiff, t],
);
const extensions = useMemo(() => {
const allExtensions: Extension[] = [
...getLanguageExtensions(file.name),
...toolbarPanelExtension,
];
if (file.diffInfo && showDiff && file.diffInfo.old_string !== undefined) {
allExtensions.push(
unifiedMergeView({
original: file.diffInfo.old_string,
mergeControls: false,
highlightChanges: true,
syntaxHighlightDeletions: false,
gutter: true,
}),
);
allExtensions.push(...minimapExtension);
allExtensions.push(...scrollToFirstChunkExtension);
}
if (wordWrap) {
allExtensions.push(EditorView.lineWrapping);
}
return allExtensions;
}, [
file.diffInfo,
file.name,
minimapExtension,
scrollToFirstChunkExtension,
showDiff,
toolbarPanelExtension,
wordWrap,
]);
useEditorKeyboardShortcuts({
onSave: handleSave,
onClose,
dependency: content,
});
if (loading) {
return (
<CodeEditorLoadingState
isDarkMode={isDarkMode}
isSidebar={isSidebar}
loadingText={t('loading', { fileName: file.name })}
/>
);
}
const outerContainerClassName = isSidebar
? 'w-full h-full flex flex-col'
: `fixed inset-0 z-[9999] md:bg-black/50 md:flex md:items-center md:justify-center md:p-4 ${isFullscreen ? 'md:p-0' : ''}`;
const innerContainerClassName = isSidebar
? 'bg-background flex flex-col w-full h-full'
: `bg-background shadow-2xl flex flex-col w-full h-full md:rounded-lg md:shadow-2xl${
isFullscreen ? ' md:w-full md:h-full md:rounded-none' : ' md:w-full md:max-w-6xl md:h-[80vh] md:max-h-[80vh]'
}`;
return (
<>
<style>{getEditorStyles(isDarkMode)}</style>
<div className={outerContainerClassName}>
<div className={innerContainerClassName}>
<CodeEditorHeader
file={file}
isSidebar={isSidebar}
isFullscreen={isFullscreen}
isMarkdownFile={isMarkdownFile}
markdownPreview={markdownPreview}
saving={saving}
saveSuccess={saveSuccess}
onToggleMarkdownPreview={() => setMarkdownPreview((previous) => !previous)}
onOpenSettings={() => window.openSettings?.('appearance')}
onDownload={handleDownload}
onSave={handleSave}
onToggleFullscreen={() => setIsFullscreen((previous) => !previous)}
onClose={onClose}
labels={{
showingChanges: t('header.showingChanges'),
editMarkdown: t('actions.editMarkdown'),
previewMarkdown: t('actions.previewMarkdown'),
settings: t('toolbar.settings'),
download: t('actions.download'),
save: t('actions.save'),
saving: t('actions.saving'),
saved: t('actions.saved'),
fullscreen: t('actions.fullscreen'),
exitFullscreen: t('actions.exitFullscreen'),
close: t('actions.close'),
}}
/>
{saveError && (
<div className="px-3 py-1.5 text-xs text-red-700 bg-red-50 border-b border-red-200 dark:bg-red-900/20 dark:text-red-300 dark:border-red-900/40">
{saveError}
</div>
)}
<div className="flex-1 overflow-hidden">
<CodeEditorSurface
content={content}
onChange={setContent}
markdownPreview={markdownPreview}
isMarkdownFile={isMarkdownFile}
isDarkMode={isDarkMode}
fontSize={fontSize}
showLineNumbers={showLineNumbers}
extensions={extensions}
/>
</div>
<CodeEditorFooter
content={content}
linesLabel={t('footer.lines')}
charactersLabel={t('footer.characters')}
shortcutsLabel={t('footer.shortcuts')}
/>
</div>
</div>
</>
);
}