feature: header layout changes (#195)
* fix(ui): remove fixed sessions button and mac titlebar coupling Remove fixed sessions button from header on desktop Mac Eliminate Mac titlebar spacer and drag-to-dock logic in sidebar Update layout to rely on standard header/sidebar without mac-specific tweaks * feat: improve mobile header with session toggle and back button Add back button in mobile header to exit session switcher Show Sessions label when session switcher is open in mobile header Toggle between opening sessions and back navigation based on session state
This commit is contained in:
committed by
GitHub
parent
05caf4cc58
commit
2dd4a7a3a6
@@ -5,7 +5,7 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from '@/components/ui/tooltip';
|
} from '@/components/ui/tooltip';
|
||||||
|
|
||||||
import { RiChat4Line, RiCodeLine, RiCommandLine, RiFolder6Line, RiGitBranchLine, RiLayoutLeftLine, RiPlayListAddLine, RiQuestionLine, RiSettings3Line, RiTerminalBoxLine, type RemixiconComponentType } from '@remixicon/react';
|
import { RiArrowLeftSLine, RiChat4Line, RiCodeLine, RiCommandLine, RiFolder6Line, RiGitBranchLine, RiLayoutLeftLine, RiPlayListAddLine, RiQuestionLine, RiSettings3Line, RiTerminalBoxLine, type RemixiconComponentType } from '@remixicon/react';
|
||||||
import { useUIStore, type MainTab } from '@/stores/useUIStore';
|
import { useUIStore, type MainTab } from '@/stores/useUIStore';
|
||||||
import { useUpdateStore } from '@/stores/useUpdateStore';
|
import { useUpdateStore } from '@/stores/useUpdateStore';
|
||||||
import { useConfigStore } from '@/stores/useConfigStore';
|
import { useConfigStore } from '@/stores/useConfigStore';
|
||||||
@@ -24,71 +24,12 @@ interface TabConfig {
|
|||||||
showDot?: boolean;
|
showDot?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FixedSessionsButton: React.FC = () => {
|
|
||||||
const setSessionSwitcherOpen = useUIStore((state) => state.setSessionSwitcherOpen);
|
|
||||||
const toggleSidebar = useUIStore((state) => state.toggleSidebar);
|
|
||||||
const { isMobile } = useDeviceInfo();
|
|
||||||
|
|
||||||
const [isDesktopApp, setIsDesktopApp] = React.useState<boolean>(() => {
|
|
||||||
if (typeof window === 'undefined') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return typeof (window as typeof window & { opencodeDesktop?: unknown }).opencodeDesktop !== 'undefined';
|
|
||||||
});
|
|
||||||
|
|
||||||
const isMacPlatform = React.useMemo(() => {
|
|
||||||
if (typeof navigator === 'undefined') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return /Macintosh|Mac OS X/.test(navigator.userAgent || '');
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (typeof window === 'undefined') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const detected = typeof (window as typeof window & { opencodeDesktop?: unknown }).opencodeDesktop !== 'undefined';
|
|
||||||
setIsDesktopApp(detected);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleOpenSessionSwitcher = React.useCallback(() => {
|
|
||||||
if (isMobile) {
|
|
||||||
setSessionSwitcherOpen(true);
|
|
||||||
} else {
|
|
||||||
toggleSidebar();
|
|
||||||
}
|
|
||||||
}, [isMobile, setSessionSwitcherOpen, toggleSidebar]);
|
|
||||||
|
|
||||||
const headerIconButtonClass = 'app-region-no-drag inline-flex h-9 w-9 items-center justify-center rounded-md gap-2 p-2 typography-ui-label font-medium text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50 hover:text-foreground hover:bg-secondary/50 transition-colors';
|
|
||||||
|
|
||||||
if (isMobile || !isDesktopApp || !isMacPlatform) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="fixed top-[0.375rem] left-[5.25rem] z-[9999]"
|
|
||||||
style={{ pointerEvents: 'auto', ['--padding-scale' as string]: '1' } as React.CSSProperties}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={handleOpenSessionSwitcher}
|
|
||||||
aria-label="Open sessions"
|
|
||||||
className={headerIconButtonClass}
|
|
||||||
>
|
|
||||||
<RiLayoutLeftLine className="h-5 w-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Header: React.FC = () => {
|
export const Header: React.FC = () => {
|
||||||
const setSessionSwitcherOpen = useUIStore((state) => state.setSessionSwitcherOpen);
|
const setSessionSwitcherOpen = useUIStore((state) => state.setSessionSwitcherOpen);
|
||||||
const toggleSidebar = useUIStore((state) => state.toggleSidebar);
|
const toggleSidebar = useUIStore((state) => state.toggleSidebar);
|
||||||
const setSettingsDialogOpen = useUIStore((state) => state.setSettingsDialogOpen);
|
const setSettingsDialogOpen = useUIStore((state) => state.setSettingsDialogOpen);
|
||||||
const toggleCommandPalette = useUIStore((state) => state.toggleCommandPalette);
|
const toggleCommandPalette = useUIStore((state) => state.toggleCommandPalette);
|
||||||
const toggleHelpDialog = useUIStore((state) => state.toggleHelpDialog);
|
const toggleHelpDialog = useUIStore((state) => state.toggleHelpDialog);
|
||||||
const isSidebarOpen = useUIStore((state) => state.isSidebarOpen);
|
|
||||||
const activeMainTab = useUIStore((state) => state.activeMainTab);
|
const activeMainTab = useUIStore((state) => state.activeMainTab);
|
||||||
const setActiveMainTab = useUIStore((state) => state.setActiveMainTab);
|
const setActiveMainTab = useUIStore((state) => state.setActiveMainTab);
|
||||||
|
|
||||||
@@ -171,10 +112,11 @@ export const Header: React.FC = () => {
|
|||||||
|
|
||||||
const desktopPaddingClass = React.useMemo(() => {
|
const desktopPaddingClass = React.useMemo(() => {
|
||||||
if (isDesktopApp && isMacPlatform) {
|
if (isDesktopApp && isMacPlatform) {
|
||||||
return isSidebarOpen ? 'pl-2' : 'pl-[8.0rem]';
|
// Always reserve space for Mac traffic lights since header is always on top
|
||||||
|
return 'pl-[5.75rem]';
|
||||||
}
|
}
|
||||||
return 'pl-3';
|
return 'pl-3';
|
||||||
}, [isDesktopApp, isMacPlatform, isSidebarOpen]);
|
}, [isDesktopApp, isMacPlatform]);
|
||||||
|
|
||||||
const updateHeaderHeight = React.useCallback(() => {
|
const updateHeaderHeight = React.useCallback(() => {
|
||||||
if (typeof document === 'undefined') {
|
if (typeof document === 'undefined') {
|
||||||
@@ -344,18 +286,14 @@ export const Header: React.FC = () => {
|
|||||||
role="tablist"
|
role="tablist"
|
||||||
aria-label="Main navigation"
|
aria-label="Main navigation"
|
||||||
>
|
>
|
||||||
{!(isDesktopApp && isMacPlatform) && (
|
<button
|
||||||
<>
|
type="button"
|
||||||
<button
|
onClick={handleOpenSessionSwitcher}
|
||||||
type="button"
|
aria-label="Open sessions"
|
||||||
onClick={handleOpenSessionSwitcher}
|
className={`${headerIconButtonClass} mr-2`}
|
||||||
aria-label="Open sessions"
|
>
|
||||||
className={`${headerIconButtonClass} mr-2`}
|
<RiLayoutLeftLine className="h-5 w-5" />
|
||||||
>
|
</button>
|
||||||
<RiLayoutLeftLine className="h-5 w-5" />
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex items-center gap-1 p-1 bg-background/50 rounded-lg">
|
<div className="flex items-center gap-1 p-1 bg-background/50 rounded-lg">
|
||||||
{tabs.map((tab) => renderTab(tab))}
|
{tabs.map((tab) => renderTab(tab))}
|
||||||
@@ -404,14 +342,25 @@ export const Header: React.FC = () => {
|
|||||||
const renderMobile = () => (
|
const renderMobile = () => (
|
||||||
<div className="app-region-drag relative flex items-center justify-between gap-2 px-3 py-2 select-none">
|
<div className="app-region-drag relative flex items-center justify-between gap-2 px-3 py-2 select-none">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
{/* Show back button when sessions sidebar is open, otherwise show sessions toggle */}
|
||||||
onClick={handleOpenSessionSwitcher}
|
{isSessionSwitcherOpen ? (
|
||||||
className="app-region-no-drag h-9 w-9 p-2 text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary rounded-md active:bg-secondary"
|
<button
|
||||||
aria-label="Open sessions"
|
onClick={() => setSessionSwitcherOpen(false)}
|
||||||
>
|
className="app-region-no-drag h-9 w-9 p-2 text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary rounded-md active:bg-secondary"
|
||||||
<RiPlayListAddLine className="h-5 w-5" />
|
aria-label="Back"
|
||||||
</button>
|
>
|
||||||
{contextUsage && contextUsage.totalTokens > 0 && activeMainTab === 'chat' && (
|
<RiArrowLeftSLine className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
onClick={handleOpenSessionSwitcher}
|
||||||
|
className="app-region-no-drag h-9 w-9 p-2 text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary rounded-md active:bg-secondary"
|
||||||
|
aria-label="Open sessions"
|
||||||
|
>
|
||||||
|
<RiPlayListAddLine className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{!isSessionSwitcherOpen && contextUsage && contextUsage.totalTokens > 0 && activeMainTab === 'chat' && (
|
||||||
<ContextUsageDisplay
|
<ContextUsageDisplay
|
||||||
totalTokens={contextUsage.totalTokens}
|
totalTokens={contextUsage.totalTokens}
|
||||||
percentage={contextUsage.percentage}
|
percentage={contextUsage.percentage}
|
||||||
@@ -421,81 +370,85 @@ export const Header: React.FC = () => {
|
|||||||
isMobile={true}
|
isMobile={true}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{isSessionSwitcherOpen && (
|
||||||
|
<span className="typography-ui-label font-semibold text-foreground">Sessions</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="app-region-no-drag flex items-center gap-1">
|
{/* Hide tabs and right-side buttons when sessions sidebar is open */}
|
||||||
|
{!isSessionSwitcherOpen && (
|
||||||
|
<div className="app-region-no-drag flex items-center gap-1">
|
||||||
|
<div className="flex items-center gap-0.5" role="tablist" aria-label="Main navigation">
|
||||||
|
{tabs.map((tab) => {
|
||||||
|
const isActive = activeMainTab === tab.id;
|
||||||
|
const Icon = tab.icon;
|
||||||
|
return (
|
||||||
|
<Tooltip key={tab.id} delayDuration={500}>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
if (isMobile) {
|
||||||
|
blurActiveElement();
|
||||||
|
}
|
||||||
|
setActiveMainTab(tab.id);
|
||||||
|
}}
|
||||||
|
aria-label={tab.label}
|
||||||
|
aria-selected={isActive}
|
||||||
|
role="tab"
|
||||||
|
className={cn(
|
||||||
|
headerIconButtonClass,
|
||||||
|
'relative',
|
||||||
|
isActive && 'text-foreground bg-secondary'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon className="h-5 w-5" />
|
||||||
|
{tab.badge !== undefined && tab.badge > 0 && (
|
||||||
|
<span className="absolute -top-1 -right-1 text-[10px] font-semibold text-primary">
|
||||||
|
{tab.badge}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{tab.showDot && (
|
||||||
|
<span
|
||||||
|
className="absolute top-1.5 right-1.5 h-2 w-2 rounded-full bg-primary"
|
||||||
|
aria-label="Changes available"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>{tab.label}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-0.5" role="tablist" aria-label="Main navigation">
|
<McpDropdown headerIconButtonClass={headerIconButtonClass} />
|
||||||
|
|
||||||
{tabs.map((tab) => {
|
<Tooltip delayDuration={500}>
|
||||||
const isActive = activeMainTab === tab.id;
|
<TooltipTrigger asChild>
|
||||||
const Icon = tab.icon;
|
<button
|
||||||
return (
|
type="button"
|
||||||
<Tooltip key={tab.id} delayDuration={500}>
|
onClick={handleOpenSettings}
|
||||||
<TooltipTrigger asChild>
|
aria-label="Open settings"
|
||||||
<button
|
className={cn(headerIconButtonClass, 'relative')}
|
||||||
type="button"
|
>
|
||||||
onClick={() => {
|
<RiSettings3Line className="h-5 w-5" />
|
||||||
if (isMobile) {
|
{updateAvailable && (
|
||||||
blurActiveElement();
|
<span
|
||||||
}
|
className="absolute top-1.5 right-1.5 h-2 w-2 rounded-full bg-primary"
|
||||||
setActiveMainTab(tab.id);
|
aria-label="Update available"
|
||||||
}}
|
/>
|
||||||
aria-label={tab.label}
|
)}
|
||||||
aria-selected={isActive}
|
</button>
|
||||||
role="tab"
|
</TooltipTrigger>
|
||||||
className={cn(
|
<TooltipContent>
|
||||||
headerIconButtonClass,
|
<p>{updateAvailable ? 'Settings (Update available)' : 'Settings'}</p>
|
||||||
'relative',
|
</TooltipContent>
|
||||||
isActive && 'text-foreground bg-secondary'
|
</Tooltip>
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Icon className="h-5 w-5" />
|
|
||||||
{tab.badge !== undefined && tab.badge > 0 && (
|
|
||||||
<span className="absolute -top-1 -right-1 text-[10px] font-semibold text-primary">
|
|
||||||
{tab.badge}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{tab.showDot && (
|
|
||||||
<span
|
|
||||||
className="absolute top-1.5 right-1.5 h-2 w-2 rounded-full bg-primary"
|
|
||||||
aria-label="Changes available"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>{tab.label}</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
<McpDropdown headerIconButtonClass={headerIconButtonClass} />
|
|
||||||
|
|
||||||
<Tooltip delayDuration={500}>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={handleOpenSettings}
|
|
||||||
aria-label="Open settings"
|
|
||||||
className={cn(headerIconButtonClass, 'relative')}
|
|
||||||
>
|
|
||||||
<RiSettings3Line className="h-5 w-5" />
|
|
||||||
{updateAvailable && (
|
|
||||||
<span
|
|
||||||
className="absolute top-1.5 right-1.5 h-2 w-2 rounded-full bg-primary"
|
|
||||||
aria-label="Update available"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>{updateAvailable ? 'Settings (Update available)' : 'Settings'}</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Header, FixedSessionsButton } from './Header';
|
import { Header } from './Header';
|
||||||
import { Sidebar } from './Sidebar';
|
import { Sidebar } from './Sidebar';
|
||||||
import { ErrorBoundary } from '../ui/ErrorBoundary';
|
import { ErrorBoundary } from '../ui/ErrorBoundary';
|
||||||
import { CommandPalette } from '../ui/CommandPalette';
|
import { CommandPalette } from '../ui/CommandPalette';
|
||||||
import { HelpDialog } from '../ui/HelpDialog';
|
import { HelpDialog } from '../ui/HelpDialog';
|
||||||
import { SessionSidebar } from '@/components/session/SessionSidebar';
|
import { SessionSidebar } from '@/components/session/SessionSidebar';
|
||||||
import { SessionDialogs } from '@/components/session/SessionDialogs';
|
import { SessionDialogs } from '@/components/session/SessionDialogs';
|
||||||
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
|
|
||||||
import { DiffWorkerProvider } from '@/contexts/DiffWorkerProvider';
|
import { DiffWorkerProvider } from '@/contexts/DiffWorkerProvider';
|
||||||
import { MultiRunLauncher } from '@/components/multirun';
|
import { MultiRunLauncher } from '@/components/multirun';
|
||||||
|
|
||||||
@@ -24,7 +23,6 @@ export const MainLayout: React.FC = () => {
|
|||||||
activeMainTab,
|
activeMainTab,
|
||||||
setIsMobile,
|
setIsMobile,
|
||||||
isSessionSwitcherOpen,
|
isSessionSwitcherOpen,
|
||||||
setSessionSwitcherOpen,
|
|
||||||
isSettingsDialogOpen,
|
isSettingsDialogOpen,
|
||||||
setSettingsDialogOpen,
|
setSettingsDialogOpen,
|
||||||
isMultiRunLauncherOpen,
|
isMultiRunLauncherOpen,
|
||||||
@@ -331,35 +329,34 @@ export const MainLayout: React.FC = () => {
|
|||||||
|
|
||||||
{isMobile ? (
|
{isMobile ? (
|
||||||
<>
|
<>
|
||||||
{/* Mobile: Header + content + overlays */}
|
{/* Mobile: Header + content with drill-down pattern */}
|
||||||
{!(isSettingsDialogOpen || isMultiRunLauncherOpen) && <Header />}
|
{!(isSettingsDialogOpen || isMultiRunLauncherOpen) && <Header />}
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex flex-1 overflow-hidden bg-background',
|
'flex flex-1 overflow-hidden',
|
||||||
(isSettingsDialogOpen || isMultiRunLauncherOpen) && 'hidden'
|
(isSettingsDialogOpen || isMultiRunLauncherOpen) && 'hidden'
|
||||||
)}
|
)}
|
||||||
style={{ paddingTop: 'var(--oc-header-height, 56px)' }}
|
style={{ paddingTop: 'var(--oc-header-height, 56px)' }}
|
||||||
>
|
>
|
||||||
<main className="flex-1 overflow-hidden bg-background relative">
|
{/* Mobile drill-down: show sessions sidebar OR main content */}
|
||||||
<div className={cn('absolute inset-0', !isChatActive && 'invisible')}>
|
{isSessionSwitcherOpen ? (
|
||||||
<ErrorBoundary><ChatView /></ErrorBoundary>
|
<div className="flex-1 overflow-hidden bg-sidebar">
|
||||||
|
<ErrorBoundary><SessionSidebar mobileVariant /></ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
{secondaryView && (
|
) : (
|
||||||
<div className="absolute inset-0">
|
<main className="flex-1 overflow-hidden bg-background relative">
|
||||||
<ErrorBoundary>{secondaryView}</ErrorBoundary>
|
<div className={cn('absolute inset-0', !isChatActive && 'invisible')}>
|
||||||
|
<ErrorBoundary><ChatView /></ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
)}
|
{secondaryView && (
|
||||||
</main>
|
<div className="absolute inset-0">
|
||||||
|
<ErrorBoundary>{secondaryView}</ErrorBoundary>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</main>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MobileOverlayPanel
|
|
||||||
open={isSessionSwitcherOpen}
|
|
||||||
onClose={() => setSessionSwitcherOpen(false)}
|
|
||||||
title="Sessions"
|
|
||||||
>
|
|
||||||
<SessionSidebar mobileVariant />
|
|
||||||
</MobileOverlayPanel>
|
|
||||||
|
|
||||||
{/* Mobile multi-run launcher: full screen */}
|
{/* Mobile multi-run launcher: full screen */}
|
||||||
{isMultiRunLauncherOpen && (
|
{isMultiRunLauncherOpen && (
|
||||||
<div className="absolute inset-0 z-10 bg-background header-safe-area">
|
<div className="absolute inset-0 z-10 bg-background header-safe-area">
|
||||||
@@ -382,18 +379,15 @@ export const MainLayout: React.FC = () => {
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{!isSettingsActive && (
|
{/* Desktop: Header always on top, then Sidebar + Content below */}
|
||||||
<Sidebar isOpen={isSidebarOpen} isMobile={isMobile}>
|
|
||||||
<SessionSidebar />
|
|
||||||
</Sidebar>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Main content area */}
|
|
||||||
<div className="flex flex-1 flex-col overflow-hidden relative">
|
<div className="flex flex-1 flex-col overflow-hidden relative">
|
||||||
{/* Normal view: Header + content */}
|
{/* Normal view: Header above Sidebar + content (like SettingsView) */}
|
||||||
<div className={cn('absolute inset-0 flex flex-col', (isSettingsActive || isMultiRunLauncherOpen) && 'invisible')}>
|
<div className={cn('absolute inset-0 flex flex-col', (isSettingsActive || isMultiRunLauncherOpen) && 'invisible')}>
|
||||||
<Header />
|
<Header />
|
||||||
<div className="flex flex-1 overflow-hidden bg-background">
|
<div className="flex flex-1 overflow-hidden">
|
||||||
|
<Sidebar isOpen={isSidebarOpen} isMobile={isMobile}>
|
||||||
|
<SessionSidebar />
|
||||||
|
</Sidebar>
|
||||||
<main className="flex-1 overflow-hidden bg-background relative">
|
<main className="flex-1 overflow-hidden bg-background relative">
|
||||||
<div className={cn('absolute inset-0', !isChatActive && 'invisible')}>
|
<div className={cn('absolute inset-0', !isChatActive && 'invisible')}>
|
||||||
<ErrorBoundary><ChatView /></ErrorBoundary>
|
<ErrorBoundary><ChatView /></ErrorBoundary>
|
||||||
@@ -430,8 +424,6 @@ export const MainLayout: React.FC = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Hide fixed sessions button when settings is open */}
|
|
||||||
{!isSettingsActive && <FixedSessionsButton />}
|
|
||||||
</div>
|
</div>
|
||||||
</DiffWorkerProvider>
|
</DiffWorkerProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
|
|||||||
export const SIDEBAR_CONTENT_WIDTH = 264;
|
export const SIDEBAR_CONTENT_WIDTH = 264;
|
||||||
const SIDEBAR_MIN_WIDTH = 200;
|
const SIDEBAR_MIN_WIDTH = 200;
|
||||||
const SIDEBAR_MAX_WIDTH = 500;
|
const SIDEBAR_MAX_WIDTH = 500;
|
||||||
const MAC_TITLEBAR_SAFE_AREA = 40;
|
|
||||||
const CHECK_FOR_UPDATES_EVENT = 'openchamber:check-for-updates';
|
const CHECK_FOR_UPDATES_EVENT = 'openchamber:check-for-updates';
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
@@ -40,12 +39,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ isOpen, isMobile, children })
|
|||||||
return typeof (window as typeof window & { opencodeDesktop?: unknown }).opencodeDesktop !== 'undefined';
|
return typeof (window as typeof window & { opencodeDesktop?: unknown }).opencodeDesktop !== 'undefined';
|
||||||
});
|
});
|
||||||
|
|
||||||
const isMacPlatform = React.useMemo(() => {
|
|
||||||
if (typeof navigator === 'undefined') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return /Macintosh|Mac OS X/.test(navigator.userAgent || '');
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
@@ -127,23 +121,6 @@ export const Sidebar: React.FC<SidebarProps> = ({ isOpen, isMobile, children })
|
|||||||
}
|
}
|
||||||
}, [isMobile, isResizing]);
|
}, [isMobile, isResizing]);
|
||||||
|
|
||||||
const handleTitlebarDragStart = React.useCallback(async (e: React.MouseEvent) => {
|
|
||||||
|
|
||||||
if (e.button !== 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDesktopApp) {
|
|
||||||
try {
|
|
||||||
const { getCurrentWindow } = await import('@tauri-apps/api/window');
|
|
||||||
const window = getCurrentWindow();
|
|
||||||
await window.startDragging();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to start window dragging from sidebar:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [isDesktopApp]);
|
|
||||||
|
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -153,7 +130,6 @@ export const Sidebar: React.FC<SidebarProps> = ({ isOpen, isMobile, children })
|
|||||||
SIDEBAR_MAX_WIDTH,
|
SIDEBAR_MAX_WIDTH,
|
||||||
Math.max(SIDEBAR_MIN_WIDTH, sidebarWidth || SIDEBAR_CONTENT_WIDTH)
|
Math.max(SIDEBAR_MIN_WIDTH, sidebarWidth || SIDEBAR_CONTENT_WIDTH)
|
||||||
) : 0;
|
) : 0;
|
||||||
const shouldRenderTitlebarSpacer = isDesktopApp && isMacPlatform;
|
|
||||||
|
|
||||||
const handlePointerDown = (event: React.PointerEvent) => {
|
const handlePointerDown = (event: React.PointerEvent) => {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
@@ -203,14 +179,6 @@ export const Sidebar: React.FC<SidebarProps> = ({ isOpen, isMobile, children })
|
|||||||
style={{ width: `${appliedWidth}px`, overflowX: 'hidden' }}
|
style={{ width: `${appliedWidth}px`, overflowX: 'hidden' }}
|
||||||
aria-hidden={!isOpen}
|
aria-hidden={!isOpen}
|
||||||
>
|
>
|
||||||
{shouldRenderTitlebarSpacer && (
|
|
||||||
<div
|
|
||||||
className="flex-shrink-0 select-none"
|
|
||||||
style={{ height: `${MAC_TITLEBAR_SAFE_AREA}px` }}
|
|
||||||
onMouseDown={handleTitlebarDragStart}
|
|
||||||
aria-hidden
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div className="flex-1 overflow-hidden">
|
<div className="flex-1 overflow-hidden">
|
||||||
<ErrorBoundary>{children}</ErrorBoundary>
|
<ErrorBoundary>{children}</ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user