mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-05-16 01:12:46 +00:00
refactor(frontend): move code editor settings to its own modal
This commit is contained in:
@@ -46,11 +46,12 @@ export const useCodeEditorSettings = () => {
|
||||
// Keep legacy behavior where the editor writes theme and wrap settings directly.
|
||||
useEffect(() => {
|
||||
localStorage.setItem(CODE_EDITOR_STORAGE_KEYS.theme, isDarkMode ? 'dark' : 'light');
|
||||
}, [isDarkMode]);
|
||||
|
||||
useEffect(() => {
|
||||
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(() => {
|
||||
const refreshFromStorage = () => {
|
||||
|
||||
@@ -15,6 +15,7 @@ import CodeEditorHeader from './subcomponents/CodeEditorHeader';
|
||||
import CodeEditorLoadingState from './subcomponents/CodeEditorLoadingState';
|
||||
import CodeEditorSurface from './subcomponents/CodeEditorSurface';
|
||||
import CodeEditorBinaryFile from './subcomponents/CodeEditorBinaryFile';
|
||||
import CodeEditorSettingsModal from './modals/CodeEditorSettingsModal';
|
||||
|
||||
type CodeEditorProps = {
|
||||
file: CodeEditorFile;
|
||||
@@ -39,13 +40,19 @@ export default function CodeEditor({
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
const [showDiff, setShowDiff] = useState(Boolean(file.diffInfo));
|
||||
const [markdownPreview, setMarkdownPreview] = useState(false);
|
||||
const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false);
|
||||
|
||||
const {
|
||||
isDarkMode,
|
||||
setIsDarkMode,
|
||||
wordWrap,
|
||||
setWordWrap,
|
||||
minimapEnabled,
|
||||
setMinimapEnabled,
|
||||
showLineNumbers,
|
||||
setShowLineNumbers,
|
||||
fontSize,
|
||||
setFontSize,
|
||||
} = useCodeEditorSettings();
|
||||
|
||||
const {
|
||||
@@ -199,7 +206,7 @@ export default function CodeEditor({
|
||||
saving={saving}
|
||||
saveSuccess={saveSuccess}
|
||||
onToggleMarkdownPreview={() => setMarkdownPreview((previous) => !previous)}
|
||||
onOpenSettings={() => window.openSettings?.('appearance')}
|
||||
onOpenSettings={() => setIsSettingsModalOpen(true)}
|
||||
onDownload={handleDownload}
|
||||
onSave={handleSave}
|
||||
onToggleFullscreen={() => setIsFullscreen((previous) => !previous)}
|
||||
@@ -246,6 +253,20 @@ export default function CodeEditor({
|
||||
/>
|
||||
</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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -4,11 +4,9 @@ import type {
|
||||
AuthStatus,
|
||||
ClaudeMcpFormState,
|
||||
CodexMcpFormState,
|
||||
CodeEditorSettingsState,
|
||||
CursorPermissionsState,
|
||||
McpToolsResult,
|
||||
McpTestResult,
|
||||
ProjectSortOrder,
|
||||
SettingsMainTab,
|
||||
} from '../types/types';
|
||||
|
||||
@@ -24,15 +22,8 @@ export const SETTINGS_MAIN_TABS: SettingsMainTab[] = [
|
||||
export const AGENT_PROVIDERS: AgentProvider[] = ['claude', 'cursor', 'codex'];
|
||||
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_CODE_EDITOR_SETTINGS: CodeEditorSettingsState = {
|
||||
theme: 'dark',
|
||||
wordWrap: false,
|
||||
showMinimap: true,
|
||||
lineNumbers: true,
|
||||
fontSize: '14',
|
||||
};
|
||||
// Keep default code editor values in each module that uses them.
|
||||
|
||||
export const DEFAULT_AUTH_STATUS: AuthStatus = {
|
||||
authenticated: false,
|
||||
|
||||
@@ -4,7 +4,6 @@ import { authenticatedFetch } from '../../../utils/api';
|
||||
import {
|
||||
AUTH_STATUS_ENDPOINTS,
|
||||
DEFAULT_AUTH_STATUS,
|
||||
DEFAULT_CODE_EDITOR_SETTINGS,
|
||||
DEFAULT_CURSOR_PERMISSIONS,
|
||||
} from '../constants/constants';
|
||||
import type {
|
||||
@@ -12,7 +11,6 @@ import type {
|
||||
AuthStatus,
|
||||
ClaudeMcpFormState,
|
||||
ClaudePermissionsState,
|
||||
CodeEditorSettingsState,
|
||||
CodexMcpFormState,
|
||||
CodexPermissionMode,
|
||||
CursorPermissionsState,
|
||||
@@ -21,7 +19,6 @@ import type {
|
||||
McpToolsResult,
|
||||
McpTestResult,
|
||||
NotificationPreferencesState,
|
||||
ProjectSortOrder,
|
||||
SettingsMainTab,
|
||||
SettingsProject,
|
||||
} from '../types/types';
|
||||
@@ -84,7 +81,6 @@ type ClaudeSettingsStorage = {
|
||||
allowedTools?: string[];
|
||||
disallowedTools?: string[];
|
||||
skipPermissions?: boolean;
|
||||
projectSortOrder?: ProjectSortOrder;
|
||||
};
|
||||
|
||||
type CursorSettingsStorage = {
|
||||
@@ -139,14 +135,6 @@ const toCodexPermissionMode = (value: unknown): CodexPermissionMode => {
|
||||
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[] => (
|
||||
servers.map((server) => ({
|
||||
id: server.name,
|
||||
@@ -211,11 +199,6 @@ export function useSettingsController({ isOpen, initialTab, projects, onClose }:
|
||||
const [activeTab, setActiveTab] = useState<SettingsMainTab>(() => normalizeMainTab(initialTab));
|
||||
const [saveStatus, setSaveStatus] = useState<'success' | 'error' | 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>(() => (
|
||||
createEmptyClaudePermissions()
|
||||
));
|
||||
@@ -667,7 +650,6 @@ export function useSettingsController({ isOpen, initialTab, projects, onClose }:
|
||||
disallowedTools: savedClaudeSettings.disallowedTools || [],
|
||||
skipPermissions: Boolean(savedClaudeSettings.skipPermissions),
|
||||
});
|
||||
setProjectSortOrder(savedClaudeSettings.projectSortOrder === 'date' ? 'date' : 'name');
|
||||
|
||||
const savedCursorSettings = parseJson<CursorSettingsStorage>(
|
||||
localStorage.getItem('cursor-tools-settings'),
|
||||
@@ -718,7 +700,6 @@ export function useSettingsController({ isOpen, initialTab, projects, onClose }:
|
||||
setCursorPermissions(createEmptyCursorPermissions());
|
||||
setNotificationPreferences(createDefaultNotificationPreferences());
|
||||
setCodexPermissionMode('default');
|
||||
setProjectSortOrder('name');
|
||||
}
|
||||
}, [fetchCodexMcpServers, fetchCursorMcpServers, fetchMcpServers]);
|
||||
|
||||
@@ -746,7 +727,6 @@ export function useSettingsController({ isOpen, initialTab, projects, onClose }:
|
||||
allowedTools: claudePermissions.allowedTools,
|
||||
disallowedTools: claudePermissions.disallowedTools,
|
||||
skipPermissions: claudePermissions.skipPermissions,
|
||||
projectSortOrder,
|
||||
lastUpdated: now,
|
||||
}));
|
||||
|
||||
@@ -790,16 +770,8 @@ export function useSettingsController({ isOpen, initialTab, projects, onClose }:
|
||||
cursorPermissions.skipPermissions,
|
||||
notificationPreferences,
|
||||
geminiPermissionMode,
|
||||
projectSortOrder,
|
||||
]);
|
||||
|
||||
const updateCodeEditorSetting = useCallback(
|
||||
<K extends keyof CodeEditorSettingsState>(key: K, value: CodeEditorSettingsState[K]) => {
|
||||
setCodeEditorSettings((prev) => ({ ...prev, [key]: value }));
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const openMcpForm = useCallback((server?: McpServer) => {
|
||||
setEditingMcpServer(server || null);
|
||||
setShowMcpForm(true);
|
||||
@@ -833,15 +805,6 @@ export function useSettingsController({ isOpen, initialTab, projects, onClose }:
|
||||
void checkAuthStatus('gemini');
|
||||
}, [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
|
||||
const autoSaveTimerRef = useRef<number | null>(null);
|
||||
const isInitialLoadRef = useRef(true);
|
||||
@@ -903,10 +866,6 @@ export function useSettingsController({ isOpen, initialTab, projects, onClose }:
|
||||
toggleDarkMode,
|
||||
saveStatus,
|
||||
deleteError,
|
||||
projectSortOrder,
|
||||
setProjectSortOrder,
|
||||
codeEditorSettings,
|
||||
updateCodeEditorSetting,
|
||||
claudePermissions,
|
||||
setClaudePermissions,
|
||||
cursorPermissions,
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { Dispatch, SetStateAction } from 'react';
|
||||
export type SettingsMainTab = 'agents' | 'appearance' | 'git' | 'api' | 'tasks' | 'notifications' | 'plugins';
|
||||
export type AgentProvider = 'claude' | 'cursor' | 'codex' | 'gemini';
|
||||
export type AgentCategory = 'account' | 'permissions' | 'mcp';
|
||||
export type ProjectSortOrder = 'name' | 'date';
|
||||
export type SaveStatus = 'success' | 'error' | null;
|
||||
export type CodexPermissionMode = 'default' | 'acceptEdits' | 'bypassPermissions';
|
||||
export type GeminiPermissionMode = 'default' | 'auto_edit' | 'yolo';
|
||||
@@ -124,20 +123,6 @@ export type CursorPermissionsState = {
|
||||
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 = {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
|
||||
@@ -23,10 +23,6 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }: Set
|
||||
setActiveTab,
|
||||
saveStatus,
|
||||
deleteError,
|
||||
projectSortOrder,
|
||||
setProjectSortOrder,
|
||||
codeEditorSettings,
|
||||
updateCodeEditorSetting,
|
||||
claudePermissions,
|
||||
setClaudePermissions,
|
||||
notificationPreferences,
|
||||
@@ -140,18 +136,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'agents' }: Set
|
||||
{/* Content */}
|
||||
<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">
|
||||
{activeTab === 'appearance' && (
|
||||
<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 === 'appearance' && <AppearanceSettingsTab />}
|
||||
|
||||
{activeTab === 'git' && <GitSettingsTab />}
|
||||
|
||||
|
||||
@@ -1,33 +1,11 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { DarkModeToggle } from '../../../../shared/view/ui';
|
||||
import type { CodeEditorSettingsState, ProjectSortOrder } from '../../types/types';
|
||||
import LanguageSelector from '../../../../shared/view/ui/LanguageSelector';
|
||||
import SettingsCard from '../SettingsCard';
|
||||
import SettingsRow from '../SettingsRow';
|
||||
import SettingsSection from '../SettingsSection';
|
||||
import SettingsToggle from '../SettingsToggle';
|
||||
|
||||
type AppearanceSettingsTabProps = {
|
||||
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) {
|
||||
export default function AppearanceSettingsTab() {
|
||||
const { t } = useTranslation('settings');
|
||||
|
||||
return (
|
||||
@@ -48,93 +26,6 @@ export default function AppearanceSettingsTab({
|
||||
<LanguageSelector />
|
||||
</SettingsCard>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -201,10 +201,6 @@ function Sidebar({
|
||||
onConfirmDeleteSession={confirmDeleteSession}
|
||||
showVersionModal={showVersionModal}
|
||||
onCloseVersionModal={() => setShowVersionModal(false)}
|
||||
releaseInfo={releaseInfo}
|
||||
currentVersion={currentVersion}
|
||||
latestVersion={latestVersion}
|
||||
installMode={installMode}
|
||||
t={t}
|
||||
/>
|
||||
|
||||
|
||||
@@ -6,8 +6,6 @@ import { Button } from '../../../../shared/view/ui';
|
||||
import Settings from '../../../settings/view/Settings';
|
||||
import VersionUpgradeModal from '../../../version-upgrade/view';
|
||||
import type { Project } from '../../../../types/app';
|
||||
import type { ReleaseInfo } from '../../../../types/sharedTypes';
|
||||
import type { InstallMode } from '../../../../hooks/useVersionCheck';
|
||||
import { normalizeProjectForSettings } from '../../utils/utils';
|
||||
import type { DeleteProjectConfirmation, SessionDeleteConfirmation, SettingsProject } from '../../types/types';
|
||||
import ProjectCreationWizard from '../../../project-creation-wizard';
|
||||
@@ -28,10 +26,6 @@ type SidebarModalsProps = {
|
||||
onConfirmDeleteSession: () => void;
|
||||
showVersionModal: boolean;
|
||||
onCloseVersionModal: () => void;
|
||||
releaseInfo: ReleaseInfo | null;
|
||||
currentVersion: string;
|
||||
latestVersion: string | null;
|
||||
installMode: InstallMode;
|
||||
t: TFunction;
|
||||
};
|
||||
|
||||
@@ -64,10 +58,6 @@ export default function SidebarModals({
|
||||
onConfirmDeleteSession,
|
||||
showVersionModal,
|
||||
onCloseVersionModal,
|
||||
releaseInfo,
|
||||
currentVersion,
|
||||
latestVersion,
|
||||
installMode,
|
||||
t,
|
||||
}: SidebarModalsProps) {
|
||||
// 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,
|
||||
)}
|
||||
|
||||
<VersionUpgradeModal
|
||||
isOpen={showVersionModal}
|
||||
onClose={onCloseVersionModal}
|
||||
releaseInfo={releaseInfo}
|
||||
currentVersion={currentVersion}
|
||||
latestVersion={latestVersion}
|
||||
installMode={installMode}
|
||||
/>
|
||||
<VersionUpgradeModal isOpen={showVersionModal} onClose={onCloseVersionModal} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,28 +1,20 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { authenticatedFetch } from "../../../utils/api";
|
||||
import { ReleaseInfo } from "../../../types/sharedTypes";
|
||||
import { copyTextToClipboard } from "../../../utils/clipboard";
|
||||
import type { InstallMode } from "../../../hooks/useVersionCheck";
|
||||
import { useVersionCheck } from "../../../hooks/useVersionCheck";
|
||||
|
||||
interface VersionUpgradeModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
releaseInfo: ReleaseInfo | null;
|
||||
currentVersion: string;
|
||||
latestVersion: string | null;
|
||||
installMode: InstallMode;
|
||||
}
|
||||
|
||||
export function VersionUpgradeModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
releaseInfo,
|
||||
currentVersion,
|
||||
latestVersion,
|
||||
installMode
|
||||
}: VersionUpgradeModalProps) {
|
||||
export function VersionUpgradeModal({ isOpen, onClose }: VersionUpgradeModalProps) {
|
||||
const { t } = useTranslation('common');
|
||||
const { latestVersion, currentVersion, releaseInfo, installMode } = useVersionCheck(
|
||||
'siteboon',
|
||||
'claudecodeui',
|
||||
);
|
||||
const upgradeCommand = installMode === 'npm'
|
||||
? t('versionUpdate.npmUpgradeCommand')
|
||||
: 'git checkout main && git pull && npm install';
|
||||
|
||||
Reference in New Issue
Block a user