完善客户端模块与工作区能力
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* TitleBar Component
|
||||
* macOS: light application toolbar with native traffic lights handled by hiddenInset.
|
||||
* macOS: light application toolbar with native traffic lights overlaid by the
|
||||
* full-size hidden title bar.
|
||||
* Windows: drag region with custom minimize/maximize/close controls.
|
||||
* Linux: use native window chrome (no custom title bar).
|
||||
*/
|
||||
@@ -19,15 +20,16 @@ type SidebarPeekChange = (open: boolean, source: SidebarPeekSource) => void;
|
||||
type TitleBarProps = {
|
||||
integrated?: boolean;
|
||||
workspaceLayout?: boolean;
|
||||
overlay?: boolean;
|
||||
sidebarPeekOpen?: boolean;
|
||||
onSidebarPeekChange?: SidebarPeekChange;
|
||||
};
|
||||
|
||||
export function TitleBar({ integrated = false, workspaceLayout = false, sidebarPeekOpen = false, onSidebarPeekChange }: TitleBarProps) {
|
||||
export function TitleBar({ integrated = false, workspaceLayout = false, overlay = false, sidebarPeekOpen = false, onSidebarPeekChange }: TitleBarProps) {
|
||||
const platform = window.electron?.platform;
|
||||
|
||||
if (platform === 'darwin') {
|
||||
return <ProductTitleBar integrated={integrated} workspaceLayout={workspaceLayout} nativeTrafficLights sidebarPeekOpen={sidebarPeekOpen} onSidebarPeekChange={onSidebarPeekChange} />;
|
||||
return <ProductTitleBar integrated={integrated} workspaceLayout={workspaceLayout} overlay={overlay} nativeTrafficLights sidebarPeekOpen={sidebarPeekOpen} onSidebarPeekChange={onSidebarPeekChange} />;
|
||||
}
|
||||
|
||||
// Linux keeps the native frame/title bar for better IME compatibility.
|
||||
@@ -35,12 +37,13 @@ export function TitleBar({ integrated = false, workspaceLayout = false, sidebarP
|
||||
return null;
|
||||
}
|
||||
|
||||
return <WindowsTitleBar integrated={integrated} workspaceLayout={workspaceLayout} sidebarPeekOpen={sidebarPeekOpen} onSidebarPeekChange={onSidebarPeekChange} />;
|
||||
return <WindowsTitleBar integrated={integrated} workspaceLayout={workspaceLayout} overlay={overlay} sidebarPeekOpen={sidebarPeekOpen} onSidebarPeekChange={onSidebarPeekChange} />;
|
||||
}
|
||||
|
||||
function ProductTitleBar({
|
||||
integrated,
|
||||
workspaceLayout = false,
|
||||
overlay = false,
|
||||
children,
|
||||
nativeTrafficLights = false,
|
||||
sidebarPeekOpen = false,
|
||||
@@ -48,6 +51,7 @@ function ProductTitleBar({
|
||||
}: {
|
||||
integrated: boolean;
|
||||
workspaceLayout?: boolean;
|
||||
overlay?: boolean;
|
||||
children?: React.ReactNode;
|
||||
nativeTrafficLights?: boolean;
|
||||
sidebarPeekOpen?: boolean;
|
||||
@@ -67,7 +71,7 @@ function ProductTitleBar({
|
||||
// The project sidebar preview is rendered below the title bar, so mirror
|
||||
// its full width in the title bar for ordinary pages. The chat workspace
|
||||
// has an independent conversation rail and must keep its pinned columns.
|
||||
const sidebarPreviewTitlebarVisible = sidebarCollapsed && sidebarPeekOpen && !workspaceLayout;
|
||||
const sidebarPreviewTitlebarVisible = sidebarCollapsed && sidebarPeekOpen && !workspaceLayout && !overlay;
|
||||
const sidebarTitlebarHidden = sidebarCollapsed && !sidebarPreviewTitlebarVisible;
|
||||
const conversationSurfaceOffset = sidebarTitlebarHidden
|
||||
? (nativeTrafficLights ? '-132px' : '-52px')
|
||||
@@ -104,16 +108,38 @@ function ProductTitleBar({
|
||||
{sidebarToggle}
|
||||
</div>
|
||||
);
|
||||
const titlebarLogo = (
|
||||
<div
|
||||
data-testid="titlebar-logo"
|
||||
className={cn(
|
||||
'pointer-events-none absolute top-1/2 z-10 h-6 w-24 -translate-y-1/2 overflow-hidden transition-[left,right] ease-out motion-reduce:transition-none',
|
||||
overlay ? 'bg-transparent' : 'bg-background',
|
||||
)}
|
||||
style={{
|
||||
right: overlay ? '0px' : 'var(--agent-browser-titlebar-logo-gap, 48px)',
|
||||
transitionDuration: 'var(--agent-browser-panel-transition-duration, 180ms)',
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={logoWordmarkSource}
|
||||
alt="Makelore logo"
|
||||
data-logo-variant="wordmark"
|
||||
data-logo-source="original-wordmark"
|
||||
className="h-full w-full object-cover object-center mix-blend-multiply"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="product-titlebar"
|
||||
className={cn(
|
||||
'relative z-30 flex h-10 shrink-0 text-foreground',
|
||||
overlay && 'pointer-events-none absolute inset-x-0 top-0 z-[300] bg-transparent',
|
||||
(!nativeTrafficLights || !integrated) && 'drag-region',
|
||||
!workspaceLayout && 'glass-surface',
|
||||
integrated && (workspaceLayout ? 'bg-transparent' : sidebarTitlebarHidden ? 'bg-background' : 'bg-background/70'),
|
||||
integrated && sidebarTitlebarHidden && !workspaceLayout && 'border-b border-border/80',
|
||||
!workspaceLayout && !overlay && 'glass-surface',
|
||||
integrated && !overlay && (workspaceLayout ? 'bg-transparent' : sidebarTitlebarHidden ? 'bg-background' : 'bg-background/70'),
|
||||
integrated && !overlay && sidebarTitlebarHidden && !workspaceLayout && 'border-b border-border/80',
|
||||
)}
|
||||
>
|
||||
{integrated ? (
|
||||
@@ -135,6 +161,7 @@ function ProductTitleBar({
|
||||
sidebarPreviewTitlebarVisible ? 'sidebar-peek-surface' : 'sidebar-glass-surface',
|
||||
'border-r border-border/80',
|
||||
),
|
||||
overlay && 'pointer-events-none border-r-0 bg-transparent',
|
||||
nativeTrafficLights && 'pl-[88px]',
|
||||
)}
|
||||
onPointerEnter={sidebarPreviewTitlebarVisible ? () => handleSidebarPeekEnter('titlebar') : undefined}
|
||||
@@ -148,8 +175,9 @@ function ProductTitleBar({
|
||||
data-testid="titlebar-main-surface"
|
||||
className={cn(
|
||||
'relative flex min-w-0 flex-1 bg-background transition-[margin-right] ease-out motion-reduce:transition-none',
|
||||
overlay && 'pointer-events-none border-b-0 bg-transparent',
|
||||
nativeTrafficLights && 'drag-region',
|
||||
workspaceLayout || sidebarTitlebarHidden ? 'border-b-0' : 'border-b border-border/80',
|
||||
!overlay && (workspaceLayout || sidebarTitlebarHidden ? 'border-b-0' : 'border-b border-border/80'),
|
||||
)}
|
||||
style={{
|
||||
marginRight: 'var(--agent-browser-panel-width, 0px)',
|
||||
@@ -163,7 +191,7 @@ function ProductTitleBar({
|
||||
style={{ left: conversationSurfaceOffset }}
|
||||
/>
|
||||
) : null}
|
||||
{!workspaceLayout ? (
|
||||
{!workspaceLayout && !overlay ? (
|
||||
<div
|
||||
data-testid="titlebar-project-context"
|
||||
className={cn(
|
||||
@@ -174,25 +202,11 @@ function ProductTitleBar({
|
||||
<span className="truncate text-sm font-semibold">{activeProject?.name ?? 'Makelore 工作台'}</span>
|
||||
</div>
|
||||
) : null}
|
||||
<div
|
||||
data-testid="titlebar-logo"
|
||||
className="pointer-events-none absolute top-1/2 z-10 h-6 w-24 -translate-y-1/2 overflow-hidden bg-background transition-[right] ease-out motion-reduce:transition-none"
|
||||
style={{
|
||||
right: 'var(--agent-browser-titlebar-logo-gap, 48px)',
|
||||
transitionDuration: 'var(--agent-browser-panel-transition-duration, 180ms)',
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={logoWordmarkSource}
|
||||
alt="Makelore logo"
|
||||
data-logo-variant="wordmark"
|
||||
data-logo-source="original-wordmark"
|
||||
className="h-full w-full object-cover object-center"
|
||||
/>
|
||||
</div>
|
||||
{!overlay ? titlebarLogo : null}
|
||||
<div
|
||||
className={cn(
|
||||
'no-drag absolute inset-y-0 right-0 z-10 flex items-center justify-end gap-1 px-2',
|
||||
overlay && 'pointer-events-auto',
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
@@ -201,11 +215,12 @@ function ProductTitleBar({
|
||||
) : (
|
||||
<div className="flex min-w-0 flex-1 items-center justify-end gap-1 px-2">{children}</div>
|
||||
)}
|
||||
{integrated && overlay ? titlebarLogo : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function WindowsTitleBar({ integrated, workspaceLayout, sidebarPeekOpen, onSidebarPeekChange }: Omit<TitleBarProps, 'integrated'> & { integrated: boolean }) {
|
||||
function WindowsTitleBar({ integrated, workspaceLayout, overlay, sidebarPeekOpen, onSidebarPeekChange }: Omit<TitleBarProps, 'integrated'> & { integrated: boolean }) {
|
||||
const [maximized, setMaximized] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -232,8 +247,8 @@ function WindowsTitleBar({ integrated, workspaceLayout, sidebarPeekOpen, onSideb
|
||||
};
|
||||
|
||||
return (
|
||||
<ProductTitleBar integrated={integrated} workspaceLayout={workspaceLayout} sidebarPeekOpen={sidebarPeekOpen} onSidebarPeekChange={onSidebarPeekChange}>
|
||||
<div className="no-drag flex h-full">
|
||||
<ProductTitleBar integrated={integrated} workspaceLayout={workspaceLayout} overlay={overlay} sidebarPeekOpen={sidebarPeekOpen} onSidebarPeekChange={onSidebarPeekChange}>
|
||||
<div className="no-drag pointer-events-auto flex h-full">
|
||||
<button
|
||||
onClick={handleMinimize}
|
||||
className="motion-press flex h-full w-11 items-center justify-center text-muted-foreground hover:bg-surface-subtle"
|
||||
|
||||
Reference in New Issue
Block a user