feat: update Makelore modules and conversations
This commit is contained in:
@@ -1,20 +1,30 @@
|
||||
/**
|
||||
* TitleBar Component
|
||||
* macOS: empty drag region (native traffic lights handled by hiddenInset).
|
||||
* macOS: light application toolbar with native traffic lights handled by hiddenInset.
|
||||
* Windows: drag region with custom minimize/maximize/close controls.
|
||||
* Linux: use native window chrome (no custom title bar).
|
||||
*/
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Minus, Square, X, Copy } from 'lucide-react';
|
||||
import { Copy, FolderOpen, Minus, PanelLeft, PanelLeftClose, Square, X } from 'lucide-react';
|
||||
import { invokeIpc } from '@/lib/api-client';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
import { useOpencodeStore } from '@/stores/opencode';
|
||||
import logoWordmarkSource from '@/assets/makelore-wordmark-source.png';
|
||||
|
||||
export function TitleBar({ integrated = false }: { integrated?: boolean }) {
|
||||
type SidebarPeekChange = (open: boolean) => void;
|
||||
|
||||
type TitleBarProps = {
|
||||
integrated?: boolean;
|
||||
sidebarPeekOpen?: boolean;
|
||||
onSidebarPeekChange?: SidebarPeekChange;
|
||||
};
|
||||
|
||||
export function TitleBar({ integrated = false, sidebarPeekOpen = false, onSidebarPeekChange }: TitleBarProps) {
|
||||
const platform = window.electron?.platform;
|
||||
|
||||
if (platform === 'darwin') {
|
||||
return <ProductTitleBar integrated={integrated} />;
|
||||
return <ProductTitleBar integrated={integrated} nativeTrafficLights sidebarPeekOpen={sidebarPeekOpen} onSidebarPeekChange={onSidebarPeekChange} />;
|
||||
}
|
||||
|
||||
// Linux keeps the native frame/title bar for better IME compatibility.
|
||||
@@ -22,37 +32,122 @@ export function TitleBar({ integrated = false }: { integrated?: boolean }) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <WindowsTitleBar integrated={integrated} />;
|
||||
return <WindowsTitleBar integrated={integrated} sidebarPeekOpen={sidebarPeekOpen} onSidebarPeekChange={onSidebarPeekChange} />;
|
||||
}
|
||||
|
||||
function ProductTitleBar({ integrated, children }: { integrated: boolean; children?: React.ReactNode }) {
|
||||
function ProductTitleBar({
|
||||
integrated,
|
||||
children,
|
||||
nativeTrafficLights = false,
|
||||
sidebarPeekOpen = false,
|
||||
onSidebarPeekChange,
|
||||
}: {
|
||||
integrated: boolean;
|
||||
children?: React.ReactNode;
|
||||
nativeTrafficLights?: boolean;
|
||||
sidebarPeekOpen?: boolean;
|
||||
onSidebarPeekChange?: SidebarPeekChange;
|
||||
}) {
|
||||
const sidebarCollapsed = useSettingsStore((state) => state.sidebarCollapsed);
|
||||
const setSidebarCollapsed = useSettingsStore((state) => state.setSidebarCollapsed);
|
||||
const activeProject = useOpencodeStore((state) => state.activeProject);
|
||||
const toolbarButtonClass = 'no-drag motion-press flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-surface-subtle hover:text-foreground';
|
||||
const toggleSidebar = () => setSidebarCollapsed(!sidebarCollapsed);
|
||||
const handleSidebarPeekEnter = () => {
|
||||
if (sidebarCollapsed) onSidebarPeekChange?.(true);
|
||||
};
|
||||
const handleSidebarPeekLeave = () => {
|
||||
if (sidebarCollapsed) onSidebarPeekChange?.(false);
|
||||
};
|
||||
const sidebarHidden = sidebarCollapsed && !sidebarPeekOpen;
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="product-titlebar"
|
||||
className={cn(
|
||||
'drag-region flex h-10 shrink-0 bg-[#FFFFFF] text-[#26384D]',
|
||||
integrated && 'bg-[#FFFFFF]',
|
||||
'glass-surface drag-region relative z-30 flex h-10 shrink-0 text-foreground',
|
||||
integrated && (sidebarHidden ? 'bg-background' : 'bg-background/70'),
|
||||
integrated && sidebarHidden && 'border-b border-border/80',
|
||||
)}
|
||||
>
|
||||
{integrated ? (
|
||||
<div
|
||||
data-testid="titlebar-sidebar-surface"
|
||||
className={cn(
|
||||
'h-full shrink-0 border-r-4 border-[#26384D] bg-[#F6F8FA] transition-[width] duration-300',
|
||||
sidebarCollapsed ? 'w-16' : 'w-72',
|
||||
'flex h-full shrink-0 items-center gap-1 px-2 transition-[width] duration-300 ease-spring',
|
||||
sidebarHidden
|
||||
? nativeTrafficLights
|
||||
? 'w-[132px]'
|
||||
: 'w-[52px]'
|
||||
: 'w-64',
|
||||
sidebarHidden
|
||||
? 'border-r-0 bg-background'
|
||||
: 'border-r border-border/80 bg-surface-sidebar',
|
||||
nativeTrafficLights && 'pl-[88px]',
|
||||
)}
|
||||
/>
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="titlebar-sidebar-toggle"
|
||||
aria-label={sidebarCollapsed ? (sidebarPeekOpen ? '固定侧栏' : '展开侧栏') : '折叠侧栏'}
|
||||
aria-expanded={!sidebarCollapsed || sidebarPeekOpen}
|
||||
title={sidebarCollapsed ? (sidebarPeekOpen ? '固定侧栏' : '展开侧栏') : '折叠侧栏'}
|
||||
className={cn(toolbarButtonClass, nativeTrafficLights && 'translate-y-[3px]')}
|
||||
onClick={toggleSidebar}
|
||||
onPointerEnter={handleSidebarPeekEnter}
|
||||
onPointerLeave={handleSidebarPeekLeave}
|
||||
onFocus={handleSidebarPeekEnter}
|
||||
onBlur={handleSidebarPeekLeave}
|
||||
>
|
||||
{sidebarCollapsed ? <PanelLeft className="h-4 w-4" /> : <PanelLeftClose className="h-4 w-4" />}
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="flex min-w-0 flex-1 items-center justify-end bg-[#FFFFFF]">
|
||||
{children}
|
||||
</div>
|
||||
{integrated ? (
|
||||
<div
|
||||
data-testid="titlebar-main-surface"
|
||||
className={cn(
|
||||
'flex min-w-0 flex-1',
|
||||
sidebarHidden ? 'border-b-0' : 'border-b border-border/80',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
data-testid="titlebar-project-context"
|
||||
className={cn(
|
||||
'flex min-w-0 flex-1 items-center gap-2 px-3',
|
||||
nativeTrafficLights && 'translate-y-[3px]',
|
||||
)}
|
||||
>
|
||||
<FolderOpen className="h-4 w-4 shrink-0 text-muted-foreground" aria-hidden="true" />
|
||||
<span className="truncate text-sm font-semibold">{activeProject?.name ?? 'Makelore 工作台'}</span>
|
||||
</div>
|
||||
<div className={cn(
|
||||
'flex min-w-0 items-center justify-end gap-1 px-2',
|
||||
nativeTrafficLights && 'translate-y-[3px]',
|
||||
)}>
|
||||
<div
|
||||
data-testid="titlebar-logo"
|
||||
className="relative mr-1 h-6 w-24 shrink-0 overflow-hidden"
|
||||
>
|
||||
<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>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex min-w-0 flex-1 items-center justify-end gap-1 px-2">{children}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function WindowsTitleBar({ integrated }: { integrated: boolean }) {
|
||||
function WindowsTitleBar({ integrated, sidebarPeekOpen, onSidebarPeekChange }: Omit<TitleBarProps, 'integrated'> & { integrated: boolean }) {
|
||||
const [maximized, setMaximized] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -79,25 +174,25 @@ function WindowsTitleBar({ integrated }: { integrated: boolean }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<ProductTitleBar integrated={integrated}>
|
||||
<ProductTitleBar integrated={integrated} sidebarPeekOpen={sidebarPeekOpen} onSidebarPeekChange={onSidebarPeekChange}>
|
||||
<div className="no-drag flex h-full">
|
||||
<button
|
||||
onClick={handleMinimize}
|
||||
className="flex h-full w-11 items-center justify-center text-muted-foreground hover:bg-accent transition-colors"
|
||||
className="motion-press flex h-full w-11 items-center justify-center text-muted-foreground hover:bg-surface-subtle"
|
||||
title="Minimize"
|
||||
>
|
||||
<Minus className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={handleMaximize}
|
||||
className="flex h-full w-11 items-center justify-center text-muted-foreground hover:bg-accent transition-colors"
|
||||
className="motion-press flex h-full w-11 items-center justify-center text-muted-foreground hover:bg-surface-subtle"
|
||||
title={maximized ? 'Restore' : 'Maximize'}
|
||||
>
|
||||
{maximized ? <Copy className="h-3.5 w-3.5" /> : <Square className="h-3.5 w-3.5" />}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="flex h-full w-11 items-center justify-center text-muted-foreground hover:bg-red-500 hover:text-white transition-colors"
|
||||
className="motion-press flex h-full w-11 items-center justify-center text-muted-foreground hover:bg-accent hover:text-primary-foreground"
|
||||
title="Close"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
|
||||
Reference in New Issue
Block a user