收口 Makelore 客户端变更
Some checks failed
Electron E2E / Electron E2E (macos-latest) (push) Has been cancelled
Electron E2E / Electron E2E (ubuntu-latest) (push) Has been cancelled
Electron E2E / Electron E2E (windows-latest) (push) Has been cancelled

This commit is contained in:
inman
2026-08-12 22:35:06 +08:00
parent 253bad8b40
commit f4113a872f
232 changed files with 4617 additions and 20121 deletions

View File

@@ -12,6 +12,10 @@ import { getAiModuleForPath } from '@/lib/ai-modules';
import { useOpencodeStore } from '@/stores/opencode';
import { useProjectConfigStore } from '@/stores/project-config';
import { useSettingsStore } from '@/stores/settings';
import { cn } from '@/lib/utils';
import type { SidebarPeekSource } from './sidebar-peek';
const SIDEBAR_PEEK_CLOSE_DELAY_MS = 180;
export function MainLayout() {
const activeProject = useOpencodeStore((state) => state.activeProject);
@@ -22,29 +26,48 @@ export function MainLayout() {
const navigate = useNavigate();
const [sidebarPeekOpen, setSidebarPeekOpen] = useState(false);
const sidebarPeekCloseTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const sidebarPeekSourcesRef = useRef<Record<SidebarPeekSource, boolean>>({
toggle: false,
focus: false,
sidebar: false,
titlebar: false,
});
const isPaintingModule = getAiModuleForPath(location.pathname) === 'painting';
const isChatWorkspace = location.pathname === '/opencode-chat';
const isInitializationSafeRoute = location.pathname === '/project-config' || isPaintingModule;
const handleSidebarPeekChange = useCallback((open: boolean) => {
const handleSidebarPeekChange = useCallback((open: boolean, source: SidebarPeekSource) => {
if (!sidebarCollapsed) return;
const sources = sidebarPeekSourcesRef.current;
if (sources[source] === open) return;
if (sidebarPeekCloseTimerRef.current) {
clearTimeout(sidebarPeekCloseTimerRef.current);
sidebarPeekCloseTimerRef.current = null;
}
sources[source] = open;
if (open) {
setSidebarPeekOpen(true);
return;
}
if (Object.values(sources).some(Boolean)) return;
sidebarPeekCloseTimerRef.current = setTimeout(() => {
if (Object.values(sidebarPeekSourcesRef.current).some(Boolean)) return;
setSidebarPeekOpen(false);
sidebarPeekCloseTimerRef.current = null;
}, 120);
}, SIDEBAR_PEEK_CLOSE_DELAY_MS);
}, [sidebarCollapsed]);
useEffect(() => {
sidebarPeekSourcesRef.current = { toggle: false, focus: false, sidebar: false, titlebar: false };
// This transient state mirrors the external persisted sidebar preference;
// reset it when that preference changes so a stale preview cannot reopen.
// eslint-disable-next-line react-hooks/set-state-in-effect
setSidebarPeekOpen(false);
if (sidebarPeekCloseTimerRef.current) {
clearTimeout(sidebarPeekCloseTimerRef.current);
@@ -62,17 +85,32 @@ export function MainLayout() {
const initializationBlocked = Boolean(activeProject && (!config || !config.initialized) && !isInitializationSafeRoute);
return (
<div data-testid="main-layout" className="flex h-screen flex-col overflow-hidden bg-background">
<div data-testid="main-layout" className="flex h-screen flex-col overflow-hidden bg-transparent">
{/* Title bar: drag region on macOS, icon + controls on Windows */}
<TitleBar integrated sidebarPeekOpen={sidebarPeekOpen} onSidebarPeekChange={handleSidebarPeekChange} />
<TitleBar
integrated
workspaceLayout={isChatWorkspace}
sidebarPeekOpen={sidebarPeekOpen}
onSidebarPeekChange={handleSidebarPeekChange}
/>
{/* Below the title bar: sidebar + content */}
<div className="relative flex min-h-0 flex-1 overflow-hidden">
<Sidebar sidebarPeekOpen={sidebarPeekOpen} onSidebarPeekChange={handleSidebarPeekChange} />
<main data-testid="main-content" className="relative min-h-0 flex-1 overflow-auto bg-background p-5 sm:p-6">
<Sidebar
workspaceLayout={isChatWorkspace}
sidebarPeekOpen={sidebarPeekOpen}
onSidebarPeekChange={handleSidebarPeekChange}
/>
<main
data-testid="main-content"
className={cn(
'relative min-h-0 min-w-0 flex-1 overflow-auto bg-background p-5 sm:p-6',
isChatWorkspace && 'basis-0 overflow-hidden p-0',
)}
>
<Outlet />
{initializationBlocked ? (
<div data-testid="project-initialization-gate" className="glass-surface absolute inset-0 z-50 flex items-center justify-center bg-background/90 p-6">
<div data-testid="project-initialization-gate" className="glass-surface absolute inset-0 z-[100] flex items-center justify-center bg-background/90 p-6">
<div className="surface-card max-w-md rounded-2xl border border-border/80 bg-background p-8 text-center shadow-float">
<LockKeyhole className="mx-auto h-10 w-10" />
<h1 className="mt-4 text-2xl font-semibold">项目尚未初始化</h1>