refactor(settings): always-docked editor toolbar; reorder Navigation
The 'Always show editor toolbar' preference is gone — the docked toolbar under the file tabs is now the only mode, and the floating hover toolbar branch in the files editor (with its open-state and outside-click machinery) is deleted. The stored preference is dropped by a store migration and removed from desktop settings persistence, settings search and every locale. Navigation section order now reads: file editor keymap, auto-save, terminal shell + login shell, Terminal Quick Keys, and the Session tabs group last.
This commit is contained in:
@@ -154,7 +154,6 @@ const GeneralSectionContent: React.FC = () => {
|
||||
'fileEditorKeymap',
|
||||
...(!isVSCode ? ['sessionTabs' as const] : []),
|
||||
'autoSaveEnabled',
|
||||
'expandedEditorToolbar',
|
||||
...(!isVSCode ? ['terminalQuickKeys' as const] : []),
|
||||
...(!isVSCode ? ['terminalShell' as const] : []),
|
||||
...(!isVSCode ? ['terminalLoginShell' as const] : []),
|
||||
|
||||
@@ -266,7 +266,7 @@ const normalizeUserMessageRenderingMode = (mode: unknown): 'markdown' | 'plain'
|
||||
return mode === 'markdown' ? 'markdown' : 'plain';
|
||||
};
|
||||
|
||||
type VisibleSetting = 'sessionAssist' | 'sessionGoal' | 'theme' | 'windowControlsPosition' | 'pwaInstallName' | 'pwaOrientation' | 'mobileKeyboardMode' | 'timeFormat' | 'weekStart' | 'fontSize' | 'terminalFontSize' | 'terminalShell' | 'terminalLoginShell' | 'editorFontSize' | 'spacing' | 'inputBarOffset' | 'mermaidRendering' | 'userMessageRendering' | 'chatRenderMode' | 'messageTransport' | 'activityRenderMode' | 'collapsibleUserMessages' | 'stickyUserHeader' | 'promptNavigatorEnabled' | 'wideChatLayout' | 'codeBlockLineWrap' | 'splitAssistantMessageActions' | 'subagentReadOnlyBanner' | 'diffLayout' | 'mobileStatusBar' | 'dotfiles' | 'fileViewerPreview' | 'reasoning' | 'showToolFileIcons' | 'showTurnChangedFiles' | 'expandedTools' | 'followUpBehavior' | 'terminalQuickKeys' | 'fileEditorKeymap' | 'persistDraft' | 'inputSpellcheck' | 'reportUsage' | 'expandedEditorToolbar' | 'autoSaveEnabled' | 'sessionTabs';
|
||||
type VisibleSetting = 'sessionAssist' | 'sessionGoal' | 'theme' | 'windowControlsPosition' | 'pwaInstallName' | 'pwaOrientation' | 'mobileKeyboardMode' | 'timeFormat' | 'weekStart' | 'fontSize' | 'terminalFontSize' | 'terminalShell' | 'terminalLoginShell' | 'editorFontSize' | 'spacing' | 'inputBarOffset' | 'mermaidRendering' | 'userMessageRendering' | 'chatRenderMode' | 'messageTransport' | 'activityRenderMode' | 'collapsibleUserMessages' | 'stickyUserHeader' | 'promptNavigatorEnabled' | 'wideChatLayout' | 'codeBlockLineWrap' | 'splitAssistantMessageActions' | 'subagentReadOnlyBanner' | 'diffLayout' | 'mobileStatusBar' | 'dotfiles' | 'fileViewerPreview' | 'reasoning' | 'showToolFileIcons' | 'showTurnChangedFiles' | 'expandedTools' | 'followUpBehavior' | 'terminalQuickKeys' | 'fileEditorKeymap' | 'persistDraft' | 'inputSpellcheck' | 'reportUsage' | 'autoSaveEnabled' | 'sessionTabs';
|
||||
|
||||
const WINDOW_CONTROLS_POSITION_OPTIONS: Array<{ id: DesktopWindowControlsPosition; labelKey: string }> = [
|
||||
{ id: 'left', labelKey: 'settings.openchamber.desktopNetwork.option.windowControlsLeft' },
|
||||
@@ -315,8 +315,6 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
const promptNavigatorEnabled = useUIStore(state => state.promptNavigatorEnabled);
|
||||
const setStickyUserHeader = useUIStore(state => state.setStickyUserHeader);
|
||||
const setPromptNavigatorEnabled = useUIStore(state => state.setPromptNavigatorEnabled);
|
||||
const expandedEditorToolbar = useUIStore(state => state.expandedEditorToolbar);
|
||||
const setExpandedEditorToolbar = useUIStore(state => state.setExpandedEditorToolbar);
|
||||
const autoSaveEnabled = useUIStore(state => state.autoSaveEnabled);
|
||||
const setAutoSaveEnabled = useUIStore(state => state.setAutoSaveEnabled);
|
||||
const wideChatLayoutEnabled = useUIStore(state => state.wideChatLayoutEnabled);
|
||||
@@ -500,11 +498,6 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
void updateDesktopSettings({ draftStartersVisible: enabled });
|
||||
}, [setDraftStartersVisible]);
|
||||
|
||||
const handleExpandedEditorToolbarChange = React.useCallback((enabled: boolean) => {
|
||||
setExpandedEditorToolbar(enabled);
|
||||
void updateDesktopSettings({ expandedEditorToolbar: enabled });
|
||||
}, [setExpandedEditorToolbar]);
|
||||
|
||||
const handleCollapsibleUserMessagesChange = React.useCallback((enabled: boolean) => {
|
||||
setCollapsibleUserMessages(enabled);
|
||||
void updateDesktopSettings({ collapsibleUserMessages: enabled });
|
||||
@@ -623,7 +616,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
? hasLocalizationSettings
|
||||
: (shouldShow('theme') || showWindowControlsPositionSetting || shouldShow('pwaInstallName') || shouldShow('pwaOrientation') || shouldShow('timeFormat') || shouldShow('weekStart'));
|
||||
const hasLayoutSettings = shouldShow('fontSize') || shouldShow('terminalFontSize') || shouldShow('editorFontSize') || shouldShow('spacing') || (shouldShow('inputBarOffset') && isMobile);
|
||||
const hasNavigationSettings = (shouldShow('terminalQuickKeys') && !isMobile) || ((shouldShow('terminalShell') || shouldShow('terminalLoginShell')) && !isVSCode) || shouldShow('fileEditorKeymap') || shouldShow('autoSaveEnabled') || (shouldShow('expandedEditorToolbar') && !isVSCode) || (shouldShow('sessionTabs') && !isVSCode && !isMobile);
|
||||
const hasNavigationSettings = (shouldShow('terminalQuickKeys') && !isMobile) || ((shouldShow('terminalShell') || shouldShow('terminalLoginShell')) && !isVSCode) || shouldShow('fileEditorKeymap') || shouldShow('autoSaveEnabled') || (shouldShow('sessionTabs') && !isVSCode && !isMobile);
|
||||
const hasBehaviorSettings = shouldShow('mermaidRendering')
|
||||
|| (shouldShow('sessionGoal') && !isVSCode)
|
||||
|| shouldShow('userMessageRendering')
|
||||
@@ -1438,20 +1431,6 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
</SettingsRadioGroup>
|
||||
</SettingsControlGroup>
|
||||
)}
|
||||
{shouldShow('sessionTabs') && !isVSCode && !isMobile && (
|
||||
<SettingsControlGroup
|
||||
title={t('settings.openchamber.visual.field.sessionTabsGroup')}
|
||||
settingsItem="appearance.session-tabs"
|
||||
>
|
||||
<SettingsCheckboxRow
|
||||
checked={sessionTabsEnabled}
|
||||
onChange={setSessionTabsEnabled}
|
||||
label={t('settings.openchamber.visual.field.sessionTabs')}
|
||||
ariaLabel={t('settings.openchamber.visual.field.sessionTabsAria')}
|
||||
info={t('settings.openchamber.visual.field.sessionTabsInfo')}
|
||||
/>
|
||||
</SettingsControlGroup>
|
||||
)}
|
||||
<div className={SETTINGS_OPTION_STACK_CLASS}>
|
||||
{shouldShow('autoSaveEnabled') && (
|
||||
<SettingsCheckboxRow
|
||||
@@ -1463,25 +1442,6 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
settingsItem="appearance.auto-save-enabled"
|
||||
/>
|
||||
)}
|
||||
{shouldShow('expandedEditorToolbar') && !isVSCode && (
|
||||
<SettingsCheckboxRow
|
||||
checked={expandedEditorToolbar}
|
||||
onChange={handleExpandedEditorToolbarChange}
|
||||
label={t('settings.openchamber.visual.field.expandedEditorToolbar')}
|
||||
ariaLabel={t('settings.openchamber.visual.field.expandedEditorToolbarAria')}
|
||||
settingsItem="appearance.expanded-editor-toolbar"
|
||||
/>
|
||||
)}
|
||||
{shouldShow('terminalQuickKeys') && !isMobile && (
|
||||
<SettingsCheckboxRow
|
||||
checked={showTerminalQuickKeysOnDesktop}
|
||||
onChange={setShowTerminalQuickKeysOnDesktop}
|
||||
label={t('settings.openchamber.visual.field.terminalQuickKeys')}
|
||||
ariaLabel={t('settings.openchamber.visual.field.terminalQuickKeysAria')}
|
||||
settingsItem="appearance.terminal-quick-keys"
|
||||
info={t('settings.openchamber.visual.field.terminalQuickKeysTooltip')}
|
||||
/>
|
||||
)}
|
||||
{showTerminalShellSetting && (
|
||||
<SettingsStackedField
|
||||
label={t('settings.openchamber.visual.field.terminalShell')}
|
||||
@@ -1511,7 +1471,31 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
settingsItem="appearance.terminal-login-shell"
|
||||
/>
|
||||
)}
|
||||
{shouldShow('terminalQuickKeys') && !isMobile && (
|
||||
<SettingsCheckboxRow
|
||||
checked={showTerminalQuickKeysOnDesktop}
|
||||
onChange={setShowTerminalQuickKeysOnDesktop}
|
||||
label={t('settings.openchamber.visual.field.terminalQuickKeys')}
|
||||
ariaLabel={t('settings.openchamber.visual.field.terminalQuickKeysAria')}
|
||||
settingsItem="appearance.terminal-quick-keys"
|
||||
info={t('settings.openchamber.visual.field.terminalQuickKeysTooltip')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{shouldShow('sessionTabs') && !isVSCode && !isMobile && (
|
||||
<SettingsControlGroup
|
||||
title={t('settings.openchamber.visual.field.sessionTabsGroup')}
|
||||
settingsItem="appearance.session-tabs"
|
||||
>
|
||||
<SettingsCheckboxRow
|
||||
checked={sessionTabsEnabled}
|
||||
onChange={setSessionTabsEnabled}
|
||||
label={t('settings.openchamber.visual.field.sessionTabs')}
|
||||
ariaLabel={t('settings.openchamber.visual.field.sessionTabsAria')}
|
||||
info={t('settings.openchamber.visual.field.sessionTabsInfo')}
|
||||
/>
|
||||
</SettingsControlGroup>
|
||||
)}
|
||||
</SettingsSection>
|
||||
)}
|
||||
|
||||
|
||||
@@ -750,8 +750,6 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
const [wrapLines, setWrapLines] = React.useState(true);
|
||||
const [isFullscreen, setIsFullscreen] = React.useState(false);
|
||||
const [isSearchOpen, setIsSearchOpen] = React.useState(false);
|
||||
const [isFloatingToolbarOpen, setIsFloatingToolbarOpen] = React.useState(false);
|
||||
const floatingToolbarRef = React.useRef<HTMLDivElement | null>(null);
|
||||
const toolbarDropdownOpenCountRef = React.useRef(0);
|
||||
|
||||
const handleToolbarDropdownOpenChange = React.useCallback((open: boolean) => {
|
||||
@@ -761,23 +759,6 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
);
|
||||
}, []);
|
||||
|
||||
const isClickInsidePortalledMenu = React.useCallback((target: EventTarget | null) => {
|
||||
if (!(target instanceof Element)) return false;
|
||||
return target.closest('[data-slot="dropdown-menu-content"], [data-slot="dropdown-menu-item"]') !== null;
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!isFloatingToolbarOpen) return;
|
||||
const handler = (event: MouseEvent) => {
|
||||
if (toolbarDropdownOpenCountRef.current > 0) return;
|
||||
if (isClickInsidePortalledMenu(event.target)) return;
|
||||
if (floatingToolbarRef.current && !floatingToolbarRef.current.contains(event.target as Node)) {
|
||||
setIsFloatingToolbarOpen(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener('mousedown', handler);
|
||||
return () => document.removeEventListener('mousedown', handler);
|
||||
}, [isClickInsidePortalledMenu, isFloatingToolbarOpen]);
|
||||
type TextViewMode = 'view' | 'edit';
|
||||
type PreviewViewMode = 'preview' | 'edit';
|
||||
|
||||
@@ -1036,7 +1017,6 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
const fileEditorKeymap = useUIStore((state) => state.fileEditorKeymap);
|
||||
const settingsDefaultFileViewerPreview = useConfigStore((state) => state.settingsDefaultFileViewerPreview);
|
||||
const showMessageTTSButtons = useConfigStore((state) => state.showMessageTTSButtons);
|
||||
const settingsExpandedEditorToolbar = useUIStore((state) => state.expandedEditorToolbar);
|
||||
|
||||
// Global mouseup to end drag selection
|
||||
React.useEffect(() => {
|
||||
@@ -3741,9 +3721,8 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{/* Row 2: Docked editor toolbar (expanded). Desktop opt-in; ALWAYS on
|
||||
for mobile — floating hover controls don't work with touch. */}
|
||||
{(settingsExpandedEditorToolbar || isMobile) && selectedFile ? (
|
||||
{/* Row 2: Docked editor toolbar. */}
|
||||
{selectedFile ? (
|
||||
<div className="flex min-w-0 items-center gap-3 border-t border-border/40 bg-[var(--surface-subtle)] px-3 py-1">
|
||||
{/* Mobile hosts already show the file name in their own header;
|
||||
a truncated duplicate here just eats toolbar width. */}
|
||||
@@ -3764,69 +3743,6 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-h-0 min-w-0 relative">
|
||||
{selectedFile && !isSearchOpen && !(settingsExpandedEditorToolbar || isMobile) && (
|
||||
<div
|
||||
ref={floatingToolbarRef}
|
||||
className="absolute right-3 top-3 z-30"
|
||||
onMouseLeave={() => {
|
||||
if (toolbarDropdownOpenCountRef.current > 0) return;
|
||||
setIsFloatingToolbarOpen(false);
|
||||
}}
|
||||
>
|
||||
{isFloatingToolbarOpen ? (
|
||||
renderFloatingFileControls()
|
||||
) : (
|
||||
<div className="flex items-center gap-1">
|
||||
{isMarkdown ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="inline-flex">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => saveMdViewMode(getMdViewMode() === 'preview' ? 'edit' : 'preview')}
|
||||
className={cn(
|
||||
'size-8 rounded-lg border border-[var(--interactive-border)] bg-[var(--surface-elevated)] p-0 shadow-sm transition-colors',
|
||||
getMdViewMode() === 'preview'
|
||||
? 'bg-[var(--interactive-selection)] text-[var(--interactive-selection-foreground)] hover:bg-[var(--interactive-selection)]'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
)}
|
||||
aria-label={t(getMdViewMode() === 'preview' ? 'filesView.editor.switchToEditMode' : 'filesView.editor.switchToPreviewMode')}
|
||||
title={t(getMdViewMode() === 'preview' ? 'filesView.editor.switchToEditMode' : 'filesView.editor.switchToPreviewMode')}
|
||||
>
|
||||
<Icon name={getMdViewMode() === 'preview' ? 'eye' : 'eye-off'} className="size-4" />
|
||||
</Button>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{t(getMdViewMode() === 'preview' ? 'filesView.editor.switchToEditMode' : 'filesView.editor.switchToPreviewMode')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span
|
||||
className="inline-flex"
|
||||
onMouseEnter={() => setIsFloatingToolbarOpen(true)}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setIsFloatingToolbarOpen(true)}
|
||||
className="size-8 rounded-lg border border-[var(--interactive-border)] bg-[var(--surface-elevated)] p-0 text-muted-foreground shadow-sm hover:text-foreground"
|
||||
aria-label={t('filesView.editor.showControlsAria')}
|
||||
title={t('filesView.editor.controlsTitle')}
|
||||
>
|
||||
<Icon name="more-2-fill" className="size-4" />
|
||||
</Button>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>{t('filesView.editor.controlsTitle')}</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<ScrollableOverlay outerClassName="h-full min-w-0" className="h-full min-w-0">
|
||||
{!selectedFile ? (
|
||||
<div className="p-3 typography-ui text-muted-foreground">{t('filesView.editor.pickFileFromTree')}</div>
|
||||
|
||||
Reference in New Issue
Block a user