fix(canvas): compact workspace toolbar
This commit is contained in:
@@ -5,7 +5,6 @@ import { DesignPlanHistory } from './DesignPlanHistory';
|
||||
|
||||
export function DesignHistoryRail({ workspace }: { workspace: DesignWorkspace | null }) {
|
||||
const taskCount = workspace?.tasks.length ?? 0;
|
||||
const resultCount = workspace?.assets.filter((asset) => asset.generationTaskId).length ?? 0;
|
||||
const platform = window.electron?.platform;
|
||||
|
||||
return (
|
||||
@@ -17,22 +16,14 @@ export function DesignHistoryRail({ workspace }: { workspace: DesignWorkspace |
|
||||
<header
|
||||
data-testid="image-workspace-history-header"
|
||||
className={cn(
|
||||
'flex h-16 shrink-0 items-center border-b border-border/70 px-4',
|
||||
'flex h-12 shrink-0 items-center border-b border-border/70 px-4',
|
||||
platform === 'darwin' && 'pr-[116px]',
|
||||
platform === 'win32' && 'items-end pb-1.5 pr-[156px]',
|
||||
platform === 'win32' && 'pr-[244px]',
|
||||
)}
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<Images className="h-4 w-4 shrink-0 text-brand" aria-hidden="true" />
|
||||
<h2 className="truncate text-sm font-semibold text-foreground">历史作品</h2>
|
||||
</div>
|
||||
<p className={cn(
|
||||
'mt-1 truncate text-[11px] tabular-nums text-muted-foreground',
|
||||
platform === 'win32' && 'hidden',
|
||||
)}>
|
||||
{taskCount > 0 ? `${taskCount} 次制作 · ${resultCount} 个结果` : '当前设计项目的制作记录'}
|
||||
</p>
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<Images className="h-4 w-4 shrink-0 text-brand" aria-hidden="true" />
|
||||
<h2 className="truncate text-sm font-semibold text-foreground">历史作品</h2>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import {
|
||||
Cloud,
|
||||
CloudOff,
|
||||
PanelLeft,
|
||||
Plus,
|
||||
@@ -23,8 +22,6 @@ import { useImageWorkspaceStore } from '@/stores/image-workspace';
|
||||
import { DesignConversationPane } from './DesignConversationPane';
|
||||
import { DesignHistoryRail } from './DesignHistoryRail';
|
||||
import { DesignWorksRail } from './DesignWorksRail';
|
||||
import { hasActiveCreationPlan } from './plan-lifecycle';
|
||||
import { projectYouthCreationCard } from './youth-form-projection';
|
||||
|
||||
function useDesktopLayout(): boolean {
|
||||
const [desktop, setDesktop] = useState(() => (
|
||||
@@ -49,7 +46,7 @@ function LoadingWorkspace() {
|
||||
return (
|
||||
<div className="flex h-full min-h-0 bg-background" aria-label="正在加载 AI 设计">
|
||||
<div className="flex min-w-0 flex-1 flex-col">
|
||||
<div className="h-16 animate-pulse border-b border-border/60 bg-surface-subtle/60" />
|
||||
<div className="h-12 animate-pulse border-b border-border/60 bg-surface-subtle/60" />
|
||||
<div className="min-h-0 flex-1 space-y-4 bg-surface-subtle/35 p-6">
|
||||
<div className="h-16 animate-pulse rounded-2xl bg-surface-subtle" />
|
||||
<div className="ml-auto h-20 w-4/5 animate-pulse rounded-2xl bg-brand/[0.07]" />
|
||||
@@ -57,7 +54,7 @@ function LoadingWorkspace() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden h-full w-[320px] shrink-0 flex-col border-l border-border/70 bg-background lg:flex xl:w-[340px]">
|
||||
<div className="h-16 animate-pulse border-b border-border/60 bg-surface-subtle/45" />
|
||||
<div className="h-12 animate-pulse border-b border-border/60 bg-surface-subtle/45" />
|
||||
<div className="space-y-3 p-3">
|
||||
{[0, 1, 2].map((item) => (
|
||||
<div key={item} className="h-16 animate-pulse rounded-xl bg-surface-subtle" />
|
||||
@@ -111,7 +108,6 @@ export function ImageCanvas() {
|
||||
const status = useImageWorkspaceStore((state) => state.status);
|
||||
const bootstrap = useImageWorkspaceStore((state) => state.bootstrap);
|
||||
const workspace = useImageWorkspaceStore((state) => state.workspace);
|
||||
const eventState = useImageWorkspaceStore((state) => state.eventState);
|
||||
const error = useImageWorkspaceStore((state) => state.error);
|
||||
const quoteBlockers = useImageWorkspaceStore((state) => state.quoteBlockers);
|
||||
const load = useImageWorkspaceStore((state) => state.load);
|
||||
@@ -150,23 +146,7 @@ export function ImageCanvas() {
|
||||
|
||||
if (!workspace && status !== 'ready') return <LoadingWorkspace />;
|
||||
|
||||
const streamHealthy = eventState === 'connected';
|
||||
const generationAvailable = bootstrap?.capabilities.generation !== false;
|
||||
const readiness = workspace
|
||||
? workspace.form.activeQuotes.some((quote) => quote.status === 'offered')
|
||||
? '制作方案已准备好,确认前仍可修改'
|
||||
: hasActiveCreationPlan(workspace)
|
||||
? '想法已整理清楚,可以检查制作方案'
|
||||
: projectYouthCreationCard(workspace, { quoteBlockers }).readinessMessage
|
||||
: '从一个新作品开始';
|
||||
const updatedAt = workspace
|
||||
? new Date(workspace.workspace.updatedAt).toLocaleString([], {
|
||||
month: 'numeric',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
: null;
|
||||
const platform = window.electron?.platform;
|
||||
const openCreate = () => {
|
||||
if (!desktopLayout) setWorksOpen(true);
|
||||
@@ -179,7 +159,7 @@ export function ImageCanvas() {
|
||||
<div data-testid="image-workspace" className="flex h-full min-h-0 overflow-hidden bg-background font-sans">
|
||||
<div className="flex min-w-0 flex-1 flex-col">
|
||||
<header data-testid="image-workspace-header" className={cn(
|
||||
'flex h-16 shrink-0 items-center gap-4 border-b border-border/70 bg-background py-2',
|
||||
'flex h-12 shrink-0 items-center gap-3 border-b border-border/70 bg-background',
|
||||
platform === 'darwin'
|
||||
? 'pl-4 pr-[116px] sm:pl-5'
|
||||
: platform === 'win32'
|
||||
@@ -193,36 +173,8 @@ export function ImageCanvas() {
|
||||
{workspace?.workspace.title ?? 'Makelore Canvas'}
|
||||
</h1>
|
||||
</div>
|
||||
<p className="mt-1 truncate text-xs text-muted-foreground">
|
||||
{updatedAt ? `${readiness} · 更新于 ${updatedAt}` : readiness}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
'hidden items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-xs sm:flex',
|
||||
streamHealthy ? 'bg-brand/[0.055] text-brand' : 'bg-surface-subtle text-muted-foreground',
|
||||
)}
|
||||
title={streamHealthy ? '已经连接' : '正在重新连接,你的内容不会丢失'}
|
||||
>
|
||||
{streamHealthy ? <Cloud className="h-3.5 w-3.5" /> : <CloudOff className="h-3.5 w-3.5" />}
|
||||
{streamHealthy ? '实时同步' : '恢复连接'}
|
||||
</div>
|
||||
|
||||
{workspace && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-9 w-9 rounded-lg"
|
||||
aria-label="刷新作品"
|
||||
title="刷新"
|
||||
onClick={() => void refreshWorkspace()}
|
||||
>
|
||||
<RefreshCw className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{!desktopLayout && (
|
||||
<Button
|
||||
type="button"
|
||||
|
||||
@@ -356,6 +356,9 @@ test.describe('AI Design V2 workspace', () => {
|
||||
await expect(page.getByTestId('image-workspace-history-rail')).toBeVisible();
|
||||
await expect(page.getByTestId('image-workspace-works-rail')).toHaveCount(0);
|
||||
await expect(page.getByRole('heading', { name: '和 AI 一起完善创作' })).toHaveCount(0);
|
||||
await expect(page.getByText('当前设计项目的制作记录')).toHaveCount(0);
|
||||
await expect(page.getByText('实时同步')).toHaveCount(0);
|
||||
await expect(page.getByRole('button', { name: '刷新作品' })).toHaveCount(0);
|
||||
await expect(page.getByRole('heading', { name: '制作方案' })).toBeVisible();
|
||||
await expect(page.getByRole('textbox', { name: '创作提示词' })).toHaveValue(
|
||||
'把便携咖啡机呈现为城市通勤中的精密工具',
|
||||
@@ -404,8 +407,8 @@ test.describe('AI Design V2 workspace', () => {
|
||||
expect(layout!.historyTopGap).toBeLessThanOrEqual(1);
|
||||
expect(layout!.historyBottomGap).toBeLessThanOrEqual(1);
|
||||
expect(layout!.historyHorizontalGap).toBeLessThanOrEqual(1);
|
||||
expect(layout!.workspaceHeaderHeight).toBe(64);
|
||||
expect(layout!.historyHeaderHeight).toBe(64);
|
||||
expect(layout!.workspaceHeaderHeight).toBe(48);
|
||||
expect(layout!.historyHeaderHeight).toBe(48);
|
||||
expect(layout!.headerTopGap).toBeLessThanOrEqual(1);
|
||||
expect(layout!.headerBottomGap).toBeLessThanOrEqual(1);
|
||||
expect(layout!.conversationWidth).toBeGreaterThan(600);
|
||||
|
||||
@@ -132,22 +132,26 @@ describe('AI Design Canvas page', () => {
|
||||
|
||||
const historyRail = screen.getByTestId('image-workspace-history-rail');
|
||||
expect(historyRail).toHaveClass('h-full', 'border-l');
|
||||
expect(screen.getByTestId('image-workspace-header')).toHaveClass('h-16');
|
||||
expect(screen.getByTestId('image-workspace-history-header')).toHaveClass('h-16');
|
||||
expect(screen.getByTestId('image-workspace-header')).toHaveClass('h-12');
|
||||
expect(screen.getByTestId('image-workspace-history-header')).toHaveClass('h-12');
|
||||
expect(within(historyRail).getByRole('heading', { name: '历史作品' })).toBeInTheDocument();
|
||||
expect(within(historyRail).queryByText('当前设计项目的制作记录')).not.toBeInTheDocument();
|
||||
expect(within(historyRail).getByText('还没有历史作品')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('image-workspace-works-rail')).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: '项目' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('实时同步')).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: '刷新作品' })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('prioritizes an offered current plan over older completed-work status', () => {
|
||||
it('keeps an offered current plan actionable without a second header status row', () => {
|
||||
setViewport(true);
|
||||
prepareWorkspace({
|
||||
form: designFormFixture({ activeQuotes: [designQuoteFixture()] }),
|
||||
});
|
||||
render(<ImageCanvas />);
|
||||
|
||||
expect(screen.getByText(/制作方案已准备好,确认前仍可修改/)).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '确认并开始制作' })).toBeInTheDocument();
|
||||
expect(screen.queryByText(/制作方案已准备好,确认前仍可修改/)).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId('image-workspace-history-rail')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('image-workspace-works-rail')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user