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.
|
// 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 = () => {
|
||||||
|
|||||||
@@ -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}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 />}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -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}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
Reference in New Issue
Block a user