refactor(frontend): move code editor settings to its own modal

This commit is contained in:
Haileyesus
2026-03-30 16:25:02 +03:00
parent dfe9c75cfd
commit 6b4c435cd3
11 changed files with 208 additions and 233 deletions

View File

@@ -46,11 +46,12 @@ export const useCodeEditorSettings = () => {
// Keep legacy behavior where the editor writes theme and wrap settings directly. // Keep legacy behavior where the editor writes theme and wrap settings directly.
useEffect(() => { useEffect(() => {
localStorage.setItem(CODE_EDITOR_STORAGE_KEYS.theme, isDarkMode ? 'dark' : 'light'); localStorage.setItem(CODE_EDITOR_STORAGE_KEYS.theme, isDarkMode ? 'dark' : 'light');
}, [isDarkMode]);
useEffect(() => {
localStorage.setItem(CODE_EDITOR_STORAGE_KEYS.wordWrap, String(wordWrap)); localStorage.setItem(CODE_EDITOR_STORAGE_KEYS.wordWrap, String(wordWrap));
}, [wordWrap]); localStorage.setItem(CODE_EDITOR_STORAGE_KEYS.showMinimap, String(minimapEnabled));
localStorage.setItem(CODE_EDITOR_STORAGE_KEYS.lineNumbers, String(showLineNumbers));
localStorage.setItem(CODE_EDITOR_STORAGE_KEYS.fontSize, String(fontSize));
window.dispatchEvent(new Event(CODE_EDITOR_SETTINGS_CHANGED_EVENT));
}, [fontSize, isDarkMode, minimapEnabled, showLineNumbers, wordWrap]);
useEffect(() => { useEffect(() => {
const refreshFromStorage = () => { const refreshFromStorage = () => {

View File

@@ -15,6 +15,7 @@ import CodeEditorHeader from './subcomponents/CodeEditorHeader';
import CodeEditorLoadingState from './subcomponents/CodeEditorLoadingState'; import CodeEditorLoadingState from './subcomponents/CodeEditorLoadingState';
import CodeEditorSurface from './subcomponents/CodeEditorSurface'; import CodeEditorSurface from './subcomponents/CodeEditorSurface';
import CodeEditorBinaryFile from './subcomponents/CodeEditorBinaryFile'; import CodeEditorBinaryFile from './subcomponents/CodeEditorBinaryFile';
import CodeEditorSettingsModal from './modals/CodeEditorSettingsModal';
type CodeEditorProps = { type CodeEditorProps = {
file: CodeEditorFile; file: CodeEditorFile;
@@ -39,13 +40,19 @@ export default function CodeEditor({
const [isFullscreen, setIsFullscreen] = useState(false); const [isFullscreen, setIsFullscreen] = useState(false);
const [showDiff, setShowDiff] = useState(Boolean(file.diffInfo)); const [showDiff, setShowDiff] = useState(Boolean(file.diffInfo));
const [markdownPreview, setMarkdownPreview] = useState(false); const [markdownPreview, setMarkdownPreview] = useState(false);
const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false);
const { const {
isDarkMode, isDarkMode,
setIsDarkMode,
wordWrap, wordWrap,
setWordWrap,
minimapEnabled, minimapEnabled,
setMinimapEnabled,
showLineNumbers, showLineNumbers,
setShowLineNumbers,
fontSize, fontSize,
setFontSize,
} = useCodeEditorSettings(); } = useCodeEditorSettings();
const { const {
@@ -199,7 +206,7 @@ export default function CodeEditor({
saving={saving} saving={saving}
saveSuccess={saveSuccess} saveSuccess={saveSuccess}
onToggleMarkdownPreview={() => setMarkdownPreview((previous) => !previous)} onToggleMarkdownPreview={() => setMarkdownPreview((previous) => !previous)}
onOpenSettings={() => window.openSettings?.('appearance')} onOpenSettings={() => setIsSettingsModalOpen(true)}
onDownload={handleDownload} onDownload={handleDownload}
onSave={handleSave} onSave={handleSave}
onToggleFullscreen={() => setIsFullscreen((previous) => !previous)} onToggleFullscreen={() => setIsFullscreen((previous) => !previous)}
@@ -246,6 +253,20 @@ export default function CodeEditor({
/> />
</div> </div>
</div> </div>
<CodeEditorSettingsModal
isOpen={isSettingsModalOpen}
onClose={() => setIsSettingsModalOpen(false)}
isDarkMode={isDarkMode}
onThemeChange={(value) => setIsDarkMode(value === 'dark')}
wordWrap={wordWrap}
onWordWrapChange={setWordWrap}
minimapEnabled={minimapEnabled}
onMinimapChange={setMinimapEnabled}
showLineNumbers={showLineNumbers}
onShowLineNumbersChange={setShowLineNumbers}
fontSize={fontSize}
onFontSizeChange={setFontSize}
/>
</> </>
); );
} }

View File

@@ -0,0 +1,171 @@
import { X } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import type { ReactNode } from 'react';
import { Button, DarkModeToggle } from '../../../../shared/view/ui';
import { cn } from '../../../../lib/utils';
type CodeEditorSettingsModalProps = {
isOpen: boolean;
onClose: () => void;
isDarkMode: boolean;
onThemeChange: (value: 'dark' | 'light') => void;
wordWrap: boolean;
onWordWrapChange: (value: boolean) => void;
minimapEnabled: boolean;
onMinimapChange: (value: boolean) => void;
showLineNumbers: boolean;
onShowLineNumbersChange: (value: boolean) => void;
fontSize: number;
onFontSizeChange: (value: number) => void;
};
const SwitchControl = ({
checked,
onChange,
ariaLabel,
}: {
checked: boolean;
onChange: (value: boolean) => void;
ariaLabel: string;
}) => (
<button
type="button"
role="switch"
aria-checked={checked}
aria-label={ariaLabel}
onClick={() => onChange(!checked)}
className={cn(
'relative inline-flex h-7 w-12 flex-shrink-0 items-center rounded-full border-2 transition-colors duration-200',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background',
checked ? 'border-primary bg-primary' : 'border-border bg-muted',
)}
>
<span
className={cn(
'pointer-events-none inline-block h-5 w-5 rounded-full shadow-sm transition-transform duration-200',
checked ? 'translate-x-[22px] bg-white' : 'translate-x-[2px] bg-foreground/60 dark:bg-foreground/80',
)}
/>
</button>
);
export default function CodeEditorSettingsModal({
isOpen,
onClose,
isDarkMode,
onThemeChange,
wordWrap,
onWordWrapChange,
minimapEnabled,
onMinimapChange,
showLineNumbers,
onShowLineNumbersChange,
fontSize,
onFontSizeChange,
}: CodeEditorSettingsModalProps) {
const { t } = useTranslation('settings');
if (!isOpen) {
return null;
}
const renderRow = ({
label,
description,
control,
}: {
label: string;
description: string;
control: ReactNode;
}) => (
<div className="rounded-lg border border-border bg-card p-4">
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<div>
<p className="text-sm font-semibold text-foreground">{label}</p>
<p className="text-xs text-muted-foreground">{description}</p>
</div>
<div className="flex-shrink-0">{control}</div>
</div>
</div>
);
const fontSizeOptions = ['10', '11', '12', '13', '14', '15', '16', '18', '20'];
return (
<div className="fixed inset-0 z-[110] flex items-center justify-center bg-background/80 p-4">
<div className="w-full max-w-md rounded-xl border border-border bg-card shadow-2xl">
<div className="flex items-center justify-between border-b border-border px-4 py-3">
<p className="text-lg font-semibold text-foreground">{t('appearanceSettings.codeEditor.title')}</p>
<Button variant="ghost" size="sm" onClick={onClose} className="h-9 w-9 p-0 text-muted-foreground">
<X className="h-4 w-4" />
</Button>
</div>
<div className="space-y-4 p-4">
{renderRow({
label: t('appearanceSettings.codeEditor.theme.label'),
description: t('appearanceSettings.codeEditor.theme.description'),
control: (
<DarkModeToggle
checked={isDarkMode}
onToggle={(enabled) => onThemeChange(enabled ? 'dark' : 'light')}
ariaLabel={t('appearanceSettings.codeEditor.theme.label')}
/>
),
})}
{renderRow({
label: t('appearanceSettings.codeEditor.wordWrap.label'),
description: t('appearanceSettings.codeEditor.wordWrap.description'),
control: (
<SwitchControl
checked={wordWrap}
onChange={onWordWrapChange}
ariaLabel={t('appearanceSettings.codeEditor.wordWrap.label')}
/>
),
})}
{renderRow({
label: t('appearanceSettings.codeEditor.showMinimap.label'),
description: t('appearanceSettings.codeEditor.showMinimap.description'),
control: (
<SwitchControl
checked={minimapEnabled}
onChange={onMinimapChange}
ariaLabel={t('appearanceSettings.codeEditor.showMinimap.label')}
/>
),
})}
{renderRow({
label: t('appearanceSettings.codeEditor.lineNumbers.label'),
description: t('appearanceSettings.codeEditor.lineNumbers.description'),
control: (
<SwitchControl
checked={showLineNumbers}
onChange={onShowLineNumbersChange}
ariaLabel={t('appearanceSettings.codeEditor.lineNumbers.label')}
/>
),
})}
<div className="rounded-lg border border-border bg-card p-4">
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<div>
<p className="text-sm font-semibold text-foreground">{t('appearanceSettings.codeEditor.fontSize.label')}</p>
<p className="text-xs text-muted-foreground">{t('appearanceSettings.codeEditor.fontSize.description')}</p>
</div>
<select
value={String(fontSize)}
onChange={(event) => onFontSizeChange(Number(event.target.value))}
className="w-full max-w-[120px] rounded-lg border border-input bg-background px-3 py-2 text-sm text-foreground focus:border-primary focus:ring-1 focus:ring-primary"
>
{fontSizeOptions.map((value) => (
<option key={value} value={value}>
{value}px
</option>
))}
</select>
</div>
</div>
</div>
</div>
</div>
);
}

View File

@@ -4,11 +4,9 @@ import type {
AuthStatus, AuthStatus,
ClaudeMcpFormState, ClaudeMcpFormState,
CodexMcpFormState, CodexMcpFormState,
CodeEditorSettingsState,
CursorPermissionsState, CursorPermissionsState,
McpToolsResult, McpToolsResult,
McpTestResult, McpTestResult,
ProjectSortOrder,
SettingsMainTab, SettingsMainTab,
} from '../types/types'; } from '../types/types';
@@ -24,15 +22,8 @@ export const SETTINGS_MAIN_TABS: SettingsMainTab[] = [
export const AGENT_PROVIDERS: AgentProvider[] = ['claude', 'cursor', 'codex']; export const AGENT_PROVIDERS: AgentProvider[] = ['claude', 'cursor', 'codex'];
export const AGENT_CATEGORIES: AgentCategory[] = ['account', 'permissions', 'mcp']; export const AGENT_CATEGORIES: AgentCategory[] = ['account', 'permissions', 'mcp'];
export const DEFAULT_PROJECT_SORT_ORDER: ProjectSortOrder = 'name';
export const DEFAULT_SAVE_STATUS = null; export const DEFAULT_SAVE_STATUS = null;
export const DEFAULT_CODE_EDITOR_SETTINGS: CodeEditorSettingsState = { // Keep default code editor values in each module that uses them.
theme: 'dark',
wordWrap: false,
showMinimap: true,
lineNumbers: true,
fontSize: '14',
};
export const DEFAULT_AUTH_STATUS: AuthStatus = { export const DEFAULT_AUTH_STATUS: AuthStatus = {
authenticated: false, authenticated: false,

View File

@@ -4,7 +4,6 @@ import { authenticatedFetch } from '../../../utils/api';
import { import {
AUTH_STATUS_ENDPOINTS, AUTH_STATUS_ENDPOINTS,
DEFAULT_AUTH_STATUS, DEFAULT_AUTH_STATUS,
DEFAULT_CODE_EDITOR_SETTINGS,
DEFAULT_CURSOR_PERMISSIONS, DEFAULT_CURSOR_PERMISSIONS,
} from '../constants/constants'; } from '../constants/constants';
import type { import type {
@@ -12,7 +11,6 @@ import type {
AuthStatus, AuthStatus,
ClaudeMcpFormState, ClaudeMcpFormState,
ClaudePermissionsState, ClaudePermissionsState,
CodeEditorSettingsState,
CodexMcpFormState, CodexMcpFormState,
CodexPermissionMode, CodexPermissionMode,
CursorPermissionsState, CursorPermissionsState,
@@ -21,7 +19,6 @@ import type {
McpToolsResult, McpToolsResult,
McpTestResult, McpTestResult,
NotificationPreferencesState, NotificationPreferencesState,
ProjectSortOrder,
SettingsMainTab, SettingsMainTab,
SettingsProject, SettingsProject,
} from '../types/types'; } from '../types/types';
@@ -84,7 +81,6 @@ type ClaudeSettingsStorage = {
allowedTools?: string[]; allowedTools?: string[];
disallowedTools?: string[]; disallowedTools?: string[];
skipPermissions?: boolean; skipPermissions?: boolean;
projectSortOrder?: ProjectSortOrder;
}; };
type CursorSettingsStorage = { type CursorSettingsStorage = {
@@ -139,14 +135,6 @@ const toCodexPermissionMode = (value: unknown): CodexPermissionMode => {
return 'default'; return 'default';
}; };
const readCodeEditorSettings = (): CodeEditorSettingsState => ({
theme: localStorage.getItem('codeEditorTheme') === 'light' ? 'light' : 'dark',
wordWrap: localStorage.getItem('codeEditorWordWrap') === 'true',
showMinimap: localStorage.getItem('codeEditorShowMinimap') !== 'false',
lineNumbers: localStorage.getItem('codeEditorLineNumbers') !== 'false',
fontSize: localStorage.getItem('codeEditorFontSize') ?? DEFAULT_CODE_EDITOR_SETTINGS.fontSize,
});
const mapCliServersToMcpServers = (servers: McpCliServer[] = []): McpServer[] => ( const mapCliServersToMcpServers = (servers: McpCliServer[] = []): McpServer[] => (
servers.map((server) => ({ servers.map((server) => ({
id: server.name, id: server.name,
@@ -211,11 +199,6 @@ export function useSettingsController({ isOpen, initialTab, projects, onClose }:
const [activeTab, setActiveTab] = useState<SettingsMainTab>(() => normalizeMainTab(initialTab)); const [activeTab, setActiveTab] = useState<SettingsMainTab>(() => normalizeMainTab(initialTab));
const [saveStatus, setSaveStatus] = useState<'success' | 'error' | null>(null); const [saveStatus, setSaveStatus] = useState<'success' | 'error' | null>(null);
const [deleteError, setDeleteError] = useState<string | null>(null); const [deleteError, setDeleteError] = useState<string | null>(null);
const [projectSortOrder, setProjectSortOrder] = useState<ProjectSortOrder>('name');
const [codeEditorSettings, setCodeEditorSettings] = useState<CodeEditorSettingsState>(() => (
readCodeEditorSettings()
));
const [claudePermissions, setClaudePermissions] = useState<ClaudePermissionsState>(() => ( const [claudePermissions, setClaudePermissions] = useState<ClaudePermissionsState>(() => (
createEmptyClaudePermissions() createEmptyClaudePermissions()
)); ));
@@ -667,7 +650,6 @@ export function useSettingsController({ isOpen, initialTab, projects, onClose }:
disallowedTools: savedClaudeSettings.disallowedTools || [], disallowedTools: savedClaudeSettings.disallowedTools || [],
skipPermissions: Boolean(savedClaudeSettings.skipPermissions), skipPermissions: Boolean(savedClaudeSettings.skipPermissions),
}); });
setProjectSortOrder(savedClaudeSettings.projectSortOrder === 'date' ? 'date' : 'name');
const savedCursorSettings = parseJson<CursorSettingsStorage>( const savedCursorSettings = parseJson<CursorSettingsStorage>(
localStorage.getItem('cursor-tools-settings'), localStorage.getItem('cursor-tools-settings'),
@@ -718,7 +700,6 @@ export function useSettingsController({ isOpen, initialTab, projects, onClose }:
setCursorPermissions(createEmptyCursorPermissions()); setCursorPermissions(createEmptyCursorPermissions());
setNotificationPreferences(createDefaultNotificationPreferences()); setNotificationPreferences(createDefaultNotificationPreferences());
setCodexPermissionMode('default'); setCodexPermissionMode('default');
setProjectSortOrder('name');
} }
}, [fetchCodexMcpServers, fetchCursorMcpServers, fetchMcpServers]); }, [fetchCodexMcpServers, fetchCursorMcpServers, fetchMcpServers]);
@@ -746,7 +727,6 @@ export function useSettingsController({ isOpen, initialTab, projects, onClose }:
allowedTools: claudePermissions.allowedTools, allowedTools: claudePermissions.allowedTools,
disallowedTools: claudePermissions.disallowedTools, disallowedTools: claudePermissions.disallowedTools,
skipPermissions: claudePermissions.skipPermissions, skipPermissions: claudePermissions.skipPermissions,
projectSortOrder,
lastUpdated: now, lastUpdated: now,
})); }));
@@ -790,16 +770,8 @@ export function useSettingsController({ isOpen, initialTab, projects, onClose }:
cursorPermissions.skipPermissions, cursorPermissions.skipPermissions,
notificationPreferences, notificationPreferences,
geminiPermissionMode, geminiPermissionMode,
projectSortOrder,
]); ]);
const updateCodeEditorSetting = useCallback(
<K extends keyof CodeEditorSettingsState>(key: K, value: CodeEditorSettingsState[K]) => {
setCodeEditorSettings((prev) => ({ ...prev, [key]: value }));
},
[],
);
const openMcpForm = useCallback((server?: McpServer) => { const openMcpForm = useCallback((server?: McpServer) => {
setEditingMcpServer(server || null); setEditingMcpServer(server || null);
setShowMcpForm(true); setShowMcpForm(true);
@@ -833,15 +805,6 @@ export function useSettingsController({ isOpen, initialTab, projects, onClose }:
void checkAuthStatus('gemini'); void checkAuthStatus('gemini');
}, [checkAuthStatus, initialTab, isOpen, loadSettings]); }, [checkAuthStatus, initialTab, isOpen, loadSettings]);
useEffect(() => {
localStorage.setItem('codeEditorTheme', codeEditorSettings.theme);
localStorage.setItem('codeEditorWordWrap', String(codeEditorSettings.wordWrap));
localStorage.setItem('codeEditorShowMinimap', String(codeEditorSettings.showMinimap));
localStorage.setItem('codeEditorLineNumbers', String(codeEditorSettings.lineNumbers));
localStorage.setItem('codeEditorFontSize', codeEditorSettings.fontSize);
window.dispatchEvent(new Event('codeEditorSettingsChanged'));
}, [codeEditorSettings]);
// Auto-save permissions and sort order with debounce // Auto-save permissions and sort order with debounce
const autoSaveTimerRef = useRef<number | null>(null); const autoSaveTimerRef = useRef<number | null>(null);
const isInitialLoadRef = useRef(true); const isInitialLoadRef = useRef(true);
@@ -903,10 +866,6 @@ export function useSettingsController({ isOpen, initialTab, projects, onClose }:
toggleDarkMode, toggleDarkMode,
saveStatus, saveStatus,
deleteError, deleteError,
projectSortOrder,
setProjectSortOrder,
codeEditorSettings,
updateCodeEditorSetting,
claudePermissions, claudePermissions,
setClaudePermissions, setClaudePermissions,
cursorPermissions, cursorPermissions,

View File

@@ -3,7 +3,6 @@ import type { Dispatch, SetStateAction } from 'react';
export type SettingsMainTab = 'agents' | 'appearance' | 'git' | 'api' | 'tasks' | 'notifications' | 'plugins'; export type SettingsMainTab = 'agents' | 'appearance' | 'git' | 'api' | 'tasks' | 'notifications' | 'plugins';
export type AgentProvider = 'claude' | 'cursor' | 'codex' | 'gemini'; export type AgentProvider = 'claude' | 'cursor' | 'codex' | 'gemini';
export type AgentCategory = 'account' | 'permissions' | 'mcp'; export type AgentCategory = 'account' | 'permissions' | 'mcp';
export type ProjectSortOrder = 'name' | 'date';
export type SaveStatus = 'success' | 'error' | null; export type SaveStatus = 'success' | 'error' | null;
export type CodexPermissionMode = 'default' | 'acceptEdits' | 'bypassPermissions'; export type CodexPermissionMode = 'default' | 'acceptEdits' | 'bypassPermissions';
export type GeminiPermissionMode = 'default' | 'auto_edit' | 'yolo'; export type GeminiPermissionMode = 'default' | 'auto_edit' | 'yolo';
@@ -124,20 +123,6 @@ export type CursorPermissionsState = {
skipPermissions: boolean; skipPermissions: boolean;
}; };
export type CodeEditorSettingsState = {
theme: 'dark' | 'light';
wordWrap: boolean;
showMinimap: boolean;
lineNumbers: boolean;
fontSize: string;
};
export type SettingsStoragePayload = {
claude: ClaudePermissionsState & { projectSortOrder: ProjectSortOrder; lastUpdated: string };
cursor: CursorPermissionsState & { lastUpdated: string };
codex: { permissionMode: CodexPermissionMode; lastUpdated: string };
};
export type SettingsProps = { export type SettingsProps = {
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;

View File

@@ -23,10 +23,6 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }: Set
setActiveTab, setActiveTab,
saveStatus, saveStatus,
deleteError, deleteError,
projectSortOrder,
setProjectSortOrder,
codeEditorSettings,
updateCodeEditorSetting,
claudePermissions, claudePermissions,
setClaudePermissions, setClaudePermissions,
notificationPreferences, notificationPreferences,
@@ -140,18 +136,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }: Set
{/* Content */} {/* Content */}
<main className="flex-1 overflow-y-auto"> <main className="flex-1 overflow-y-auto">
<div key={activeTab} className="settings-content-enter space-y-6 p-4 pb-safe-area-inset-bottom md:space-y-8 md:p-6"> <div key={activeTab} className="settings-content-enter space-y-6 p-4 pb-safe-area-inset-bottom md:space-y-8 md:p-6">
{activeTab === 'appearance' && ( {activeTab === 'appearance' && <AppearanceSettingsTab />}
<AppearanceSettingsTab
projectSortOrder={projectSortOrder}
onProjectSortOrderChange={setProjectSortOrder}
codeEditorSettings={codeEditorSettings}
onCodeEditorThemeChange={(value) => updateCodeEditorSetting('theme', value)}
onCodeEditorWordWrapChange={(value) => updateCodeEditorSetting('wordWrap', value)}
onCodeEditorShowMinimapChange={(value) => updateCodeEditorSetting('showMinimap', value)}
onCodeEditorLineNumbersChange={(value) => updateCodeEditorSetting('lineNumbers', value)}
onCodeEditorFontSizeChange={(value) => updateCodeEditorSetting('fontSize', value)}
/>
)}
{activeTab === 'git' && <GitSettingsTab />} {activeTab === 'git' && <GitSettingsTab />}

View File

@@ -1,33 +1,11 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { DarkModeToggle } from '../../../../shared/view/ui'; import { DarkModeToggle } from '../../../../shared/view/ui';
import type { CodeEditorSettingsState, ProjectSortOrder } from '../../types/types';
import LanguageSelector from '../../../../shared/view/ui/LanguageSelector'; import LanguageSelector from '../../../../shared/view/ui/LanguageSelector';
import SettingsCard from '../SettingsCard'; import SettingsCard from '../SettingsCard';
import SettingsRow from '../SettingsRow'; import SettingsRow from '../SettingsRow';
import SettingsSection from '../SettingsSection'; import SettingsSection from '../SettingsSection';
import SettingsToggle from '../SettingsToggle';
type AppearanceSettingsTabProps = { export default function AppearanceSettingsTab() {
projectSortOrder: ProjectSortOrder;
onProjectSortOrderChange: (value: ProjectSortOrder) => void;
codeEditorSettings: CodeEditorSettingsState;
onCodeEditorThemeChange: (value: 'dark' | 'light') => void;
onCodeEditorWordWrapChange: (value: boolean) => void;
onCodeEditorShowMinimapChange: (value: boolean) => void;
onCodeEditorLineNumbersChange: (value: boolean) => void;
onCodeEditorFontSizeChange: (value: string) => void;
};
export default function AppearanceSettingsTab({
projectSortOrder,
onProjectSortOrderChange,
codeEditorSettings,
onCodeEditorThemeChange,
onCodeEditorWordWrapChange,
onCodeEditorShowMinimapChange,
onCodeEditorLineNumbersChange,
onCodeEditorFontSizeChange,
}: AppearanceSettingsTabProps) {
const { t } = useTranslation('settings'); const { t } = useTranslation('settings');
return ( return (
@@ -48,93 +26,6 @@ export default function AppearanceSettingsTab({
<LanguageSelector /> <LanguageSelector />
</SettingsCard> </SettingsCard>
</SettingsSection> </SettingsSection>
<SettingsSection title={t('appearanceSettings.projectSorting.label')}>
<SettingsCard>
<SettingsRow
label={t('appearanceSettings.projectSorting.label')}
description={t('appearanceSettings.projectSorting.description')}
>
<select
value={projectSortOrder}
onChange={(event) => onProjectSortOrderChange(event.target.value as ProjectSortOrder)}
className="w-full rounded-lg border border-input bg-card p-2.5 text-sm text-foreground touch-manipulation focus:border-primary focus:ring-1 focus:ring-primary sm:w-36"
>
<option value="name">{t('appearanceSettings.projectSorting.alphabetical')}</option>
<option value="date">{t('appearanceSettings.projectSorting.recentActivity')}</option>
</select>
</SettingsRow>
</SettingsCard>
</SettingsSection>
<SettingsSection title={t('appearanceSettings.codeEditor.title')}>
<SettingsCard divided>
<SettingsRow
label={t('appearanceSettings.codeEditor.theme.label')}
description={t('appearanceSettings.codeEditor.theme.description')}
>
<DarkModeToggle
checked={codeEditorSettings.theme === 'dark'}
onToggle={(enabled) => onCodeEditorThemeChange(enabled ? 'dark' : 'light')}
ariaLabel={t('appearanceSettings.codeEditor.theme.label')}
/>
</SettingsRow>
<SettingsRow
label={t('appearanceSettings.codeEditor.wordWrap.label')}
description={t('appearanceSettings.codeEditor.wordWrap.description')}
>
<SettingsToggle
checked={codeEditorSettings.wordWrap}
onChange={onCodeEditorWordWrapChange}
ariaLabel={t('appearanceSettings.codeEditor.wordWrap.label')}
/>
</SettingsRow>
<SettingsRow
label={t('appearanceSettings.codeEditor.showMinimap.label')}
description={t('appearanceSettings.codeEditor.showMinimap.description')}
>
<SettingsToggle
checked={codeEditorSettings.showMinimap}
onChange={onCodeEditorShowMinimapChange}
ariaLabel={t('appearanceSettings.codeEditor.showMinimap.label')}
/>
</SettingsRow>
<SettingsRow
label={t('appearanceSettings.codeEditor.lineNumbers.label')}
description={t('appearanceSettings.codeEditor.lineNumbers.description')}
>
<SettingsToggle
checked={codeEditorSettings.lineNumbers}
onChange={onCodeEditorLineNumbersChange}
ariaLabel={t('appearanceSettings.codeEditor.lineNumbers.label')}
/>
</SettingsRow>
<SettingsRow
label={t('appearanceSettings.codeEditor.fontSize.label')}
description={t('appearanceSettings.codeEditor.fontSize.description')}
>
<select
value={codeEditorSettings.fontSize}
onChange={(event) => onCodeEditorFontSizeChange(event.target.value)}
className="w-full rounded-lg border border-input bg-card p-2.5 text-sm text-foreground touch-manipulation focus:border-primary focus:ring-1 focus:ring-primary sm:w-28"
>
<option value="10">10px</option>
<option value="11">11px</option>
<option value="12">12px</option>
<option value="13">13px</option>
<option value="14">14px</option>
<option value="15">15px</option>
<option value="16">16px</option>
<option value="18">18px</option>
<option value="20">20px</option>
</select>
</SettingsRow>
</SettingsCard>
</SettingsSection>
</div> </div>
); );
} }

View File

@@ -201,10 +201,6 @@ function Sidebar({
onConfirmDeleteSession={confirmDeleteSession} onConfirmDeleteSession={confirmDeleteSession}
showVersionModal={showVersionModal} showVersionModal={showVersionModal}
onCloseVersionModal={() => setShowVersionModal(false)} onCloseVersionModal={() => setShowVersionModal(false)}
releaseInfo={releaseInfo}
currentVersion={currentVersion}
latestVersion={latestVersion}
installMode={installMode}
t={t} t={t}
/> />

View File

@@ -6,8 +6,6 @@ import { Button } from '../../../../shared/view/ui';
import Settings from '../../../settings/view/Settings'; import Settings from '../../../settings/view/Settings';
import VersionUpgradeModal from '../../../version-upgrade/view'; import VersionUpgradeModal from '../../../version-upgrade/view';
import type { Project } from '../../../../types/app'; import type { Project } from '../../../../types/app';
import type { ReleaseInfo } from '../../../../types/sharedTypes';
import type { InstallMode } from '../../../../hooks/useVersionCheck';
import { normalizeProjectForSettings } from '../../utils/utils'; import { normalizeProjectForSettings } from '../../utils/utils';
import type { DeleteProjectConfirmation, SessionDeleteConfirmation, SettingsProject } from '../../types/types'; import type { DeleteProjectConfirmation, SessionDeleteConfirmation, SettingsProject } from '../../types/types';
import ProjectCreationWizard from '../../../project-creation-wizard'; import ProjectCreationWizard from '../../../project-creation-wizard';
@@ -28,10 +26,6 @@ type SidebarModalsProps = {
onConfirmDeleteSession: () => void; onConfirmDeleteSession: () => void;
showVersionModal: boolean; showVersionModal: boolean;
onCloseVersionModal: () => void; onCloseVersionModal: () => void;
releaseInfo: ReleaseInfo | null;
currentVersion: string;
latestVersion: string | null;
installMode: InstallMode;
t: TFunction; t: TFunction;
}; };
@@ -64,10 +58,6 @@ export default function SidebarModals({
onConfirmDeleteSession, onConfirmDeleteSession,
showVersionModal, showVersionModal,
onCloseVersionModal, onCloseVersionModal,
releaseInfo,
currentVersion,
latestVersion,
installMode,
t, t,
}: SidebarModalsProps) { }: SidebarModalsProps) {
// Settings expects project identity/path fields to be present for dropdown labels and local-scope MCP config. // Settings expects project identity/path fields to be present for dropdown labels and local-scope MCP config.
@@ -196,14 +186,7 @@ export default function SidebarModals({
document.body, document.body,
)} )}
<VersionUpgradeModal <VersionUpgradeModal isOpen={showVersionModal} onClose={onCloseVersionModal} />
isOpen={showVersionModal}
onClose={onCloseVersionModal}
releaseInfo={releaseInfo}
currentVersion={currentVersion}
latestVersion={latestVersion}
installMode={installMode}
/>
</> </>
); );
} }

View File

@@ -1,28 +1,20 @@
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { authenticatedFetch } from "../../../utils/api"; import { authenticatedFetch } from "../../../utils/api";
import { ReleaseInfo } from "../../../types/sharedTypes";
import { copyTextToClipboard } from "../../../utils/clipboard"; import { copyTextToClipboard } from "../../../utils/clipboard";
import type { InstallMode } from "../../../hooks/useVersionCheck"; import { useVersionCheck } from "../../../hooks/useVersionCheck";
interface VersionUpgradeModalProps { interface VersionUpgradeModalProps {
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
releaseInfo: ReleaseInfo | null;
currentVersion: string;
latestVersion: string | null;
installMode: InstallMode;
} }
export function VersionUpgradeModal({ export function VersionUpgradeModal({ isOpen, onClose }: VersionUpgradeModalProps) {
isOpen,
onClose,
releaseInfo,
currentVersion,
latestVersion,
installMode
}: VersionUpgradeModalProps) {
const { t } = useTranslation('common'); const { t } = useTranslation('common');
const { latestVersion, currentVersion, releaseInfo, installMode } = useVersionCheck(
'siteboon',
'claudecodeui',
);
const upgradeCommand = installMode === 'npm' const upgradeCommand = installMode === 'npm'
? t('versionUpdate.npmUpgradeCommand') ? t('versionUpdate.npmUpgradeCommand')
: 'git checkout main && git pull && npm install'; : 'git checkout main && git pull && npm install';