diff --git a/src/components/code-editor/hooks/useCodeEditorSettings.ts b/src/components/code-editor/hooks/useCodeEditorSettings.ts index 639054d7..e8b2b691 100644 --- a/src/components/code-editor/hooks/useCodeEditorSettings.ts +++ b/src/components/code-editor/hooks/useCodeEditorSettings.ts @@ -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 = () => { diff --git a/src/components/code-editor/view/CodeEditor.tsx b/src/components/code-editor/view/CodeEditor.tsx index e68a73d3..8503db7b 100644 --- a/src/components/code-editor/view/CodeEditor.tsx +++ b/src/components/code-editor/view/CodeEditor.tsx @@ -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({ /> + 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} + /> ); } diff --git a/src/components/code-editor/view/modals/CodeEditorSettingsModal.tsx b/src/components/code-editor/view/modals/CodeEditorSettingsModal.tsx new file mode 100644 index 00000000..ab7e3728 --- /dev/null +++ b/src/components/code-editor/view/modals/CodeEditorSettingsModal.tsx @@ -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; +}) => ( + +); + +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; + }) => ( +
+
+
+

{label}

+

{description}

+
+
{control}
+
+
+ ); + + const fontSizeOptions = ['10', '11', '12', '13', '14', '15', '16', '18', '20']; + + return ( +
+
+
+

{t('appearanceSettings.codeEditor.title')}

+ +
+
+ {renderRow({ + label: t('appearanceSettings.codeEditor.theme.label'), + description: t('appearanceSettings.codeEditor.theme.description'), + control: ( + onThemeChange(enabled ? 'dark' : 'light')} + ariaLabel={t('appearanceSettings.codeEditor.theme.label')} + /> + ), + })} + {renderRow({ + label: t('appearanceSettings.codeEditor.wordWrap.label'), + description: t('appearanceSettings.codeEditor.wordWrap.description'), + control: ( + + ), + })} + {renderRow({ + label: t('appearanceSettings.codeEditor.showMinimap.label'), + description: t('appearanceSettings.codeEditor.showMinimap.description'), + control: ( + + ), + })} + {renderRow({ + label: t('appearanceSettings.codeEditor.lineNumbers.label'), + description: t('appearanceSettings.codeEditor.lineNumbers.description'), + control: ( + + ), + })} +
+
+
+

{t('appearanceSettings.codeEditor.fontSize.label')}

+

{t('appearanceSettings.codeEditor.fontSize.description')}

+
+ +
+
+
+
+
+ ); +} diff --git a/src/components/settings/constants/constants.ts b/src/components/settings/constants/constants.ts index 36f45392..5b701805 100644 --- a/src/components/settings/constants/constants.ts +++ b/src/components/settings/constants/constants.ts @@ -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, diff --git a/src/components/settings/hooks/useSettingsController.ts b/src/components/settings/hooks/useSettingsController.ts index 26e3828b..120fd6b7 100644 --- a/src/components/settings/hooks/useSettingsController.ts +++ b/src/components/settings/hooks/useSettingsController.ts @@ -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(() => normalizeMainTab(initialTab)); const [saveStatus, setSaveStatus] = useState<'success' | 'error' | null>(null); const [deleteError, setDeleteError] = useState(null); - const [projectSortOrder, setProjectSortOrder] = useState('name'); - const [codeEditorSettings, setCodeEditorSettings] = useState(() => ( - readCodeEditorSettings() - )); - const [claudePermissions, setClaudePermissions] = useState(() => ( 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( 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( - (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(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, diff --git a/src/components/settings/types/types.ts b/src/components/settings/types/types.ts index 096059ce..495063fb 100644 --- a/src/components/settings/types/types.ts +++ b/src/components/settings/types/types.ts @@ -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; diff --git a/src/components/settings/view/Settings.tsx b/src/components/settings/view/Settings.tsx index 444d0e06..3c64196b 100644 --- a/src/components/settings/view/Settings.tsx +++ b/src/components/settings/view/Settings.tsx @@ -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 */}
- {activeTab === 'appearance' && ( - 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' && } {activeTab === 'git' && } diff --git a/src/components/settings/view/tabs/AppearanceSettingsTab.tsx b/src/components/settings/view/tabs/AppearanceSettingsTab.tsx index b320ec5f..4c1b424e 100644 --- a/src/components/settings/view/tabs/AppearanceSettingsTab.tsx +++ b/src/components/settings/view/tabs/AppearanceSettingsTab.tsx @@ -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({ - - - - - - - - - - - - - onCodeEditorThemeChange(enabled ? 'dark' : 'light')} - ariaLabel={t('appearanceSettings.codeEditor.theme.label')} - /> - - - - - - - - - - - - - - - - - - -
); } diff --git a/src/components/sidebar/view/Sidebar.tsx b/src/components/sidebar/view/Sidebar.tsx index cba08749..78a661f0 100644 --- a/src/components/sidebar/view/Sidebar.tsx +++ b/src/components/sidebar/view/Sidebar.tsx @@ -201,10 +201,6 @@ function Sidebar({ onConfirmDeleteSession={confirmDeleteSession} showVersionModal={showVersionModal} onCloseVersionModal={() => setShowVersionModal(false)} - releaseInfo={releaseInfo} - currentVersion={currentVersion} - latestVersion={latestVersion} - installMode={installMode} t={t} /> diff --git a/src/components/sidebar/view/subcomponents/SidebarModals.tsx b/src/components/sidebar/view/subcomponents/SidebarModals.tsx index 3f6cd4f8..0757445b 100644 --- a/src/components/sidebar/view/subcomponents/SidebarModals.tsx +++ b/src/components/sidebar/view/subcomponents/SidebarModals.tsx @@ -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, )} - + ); } diff --git a/src/components/version-upgrade/view/VersionUpgradeModal.tsx b/src/components/version-upgrade/view/VersionUpgradeModal.tsx index d4570ee5..16a7fbc2 100644 --- a/src/components/version-upgrade/view/VersionUpgradeModal.tsx +++ b/src/components/version-upgrade/view/VersionUpgradeModal.tsx @@ -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';