1
This commit is contained in:
@@ -6,6 +6,8 @@ import { expect, test } from 'vitest';
|
||||
import type { CustomWorldWorkSummary } from '../../../packages/shared/src/contracts/customWorldAgent';
|
||||
import { CustomWorldCreationHub } from './CustomWorldCreationHub';
|
||||
|
||||
const noopCreateType = () => {};
|
||||
|
||||
const baseDraftItem: CustomWorldWorkSummary = {
|
||||
workId: 'draft:session-1',
|
||||
sourceType: 'agent_session',
|
||||
@@ -32,9 +34,8 @@ test('creation hub reflects updated draft title summary and counts after rerende
|
||||
items={[baseDraftItem]}
|
||||
loading={false}
|
||||
error={null}
|
||||
onBack={() => {}}
|
||||
onRetry={() => {}}
|
||||
onCreateNew={() => {}}
|
||||
onCreateType={noopCreateType}
|
||||
onOpenDraft={() => {}}
|
||||
onEnterPublished={() => {}}
|
||||
/>,
|
||||
@@ -44,6 +45,9 @@ test('creation hub reflects updated draft title summary and counts after rerende
|
||||
expect(screen.getByText('玩家是失职返乡的守灯人。')).toBeTruthy();
|
||||
expect(screen.getByText('角色 3')).toBeTruthy();
|
||||
expect(screen.getByText('地点 4')).toBeTruthy();
|
||||
expect(screen.getByRole('button', { name: /角色扮演 RPG/u })).toBeTruthy();
|
||||
expect(screen.getByRole('button', { name: /大鱼吃小鱼/u })).toBeTruthy();
|
||||
expect(screen.getByRole('button', { name: /拼图玩法/u })).toBeTruthy();
|
||||
|
||||
rerender(
|
||||
<CustomWorldCreationHub
|
||||
@@ -59,9 +63,8 @@ test('creation hub reflects updated draft title summary and counts after rerende
|
||||
]}
|
||||
loading={false}
|
||||
error={null}
|
||||
onBack={() => {}}
|
||||
onRetry={() => {}}
|
||||
onCreateNew={() => {}}
|
||||
onCreateType={noopCreateType}
|
||||
onOpenDraft={() => {}}
|
||||
onEnterPublished={() => {}}
|
||||
/>,
|
||||
@@ -96,9 +99,8 @@ test('creation hub mixes puzzle works into the same grid and uses puzzle tag to
|
||||
]}
|
||||
loading={false}
|
||||
error={null}
|
||||
onBack={() => {}}
|
||||
onRetry={() => {}}
|
||||
onCreateNew={() => {}}
|
||||
onCreateType={noopCreateType}
|
||||
onOpenDraft={() => {}}
|
||||
onEnterPublished={() => {}}
|
||||
onOpenPuzzleDetail={() => {}}
|
||||
@@ -109,5 +111,4 @@ test('creation hub mixes puzzle works into the same grid and uses puzzle tag to
|
||||
expect(screen.getByText('沉钟拼图')).toBeTruthy();
|
||||
expect(screen.getAllByText('拼图').length).toBeGreaterThan(0);
|
||||
expect(screen.queryByText('我的拼图作品')).toBeNull();
|
||||
expect(screen.queryByText('拼图玩法')).toBeNull();
|
||||
});
|
||||
|
||||
@@ -3,6 +3,8 @@ import { expect, test } from 'vitest';
|
||||
|
||||
import { CustomWorldCreationHub } from './CustomWorldCreationHub';
|
||||
|
||||
const noopCreateType = () => {};
|
||||
|
||||
test('creation hub draft card renders compiled work summary fields', () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<CustomWorldCreationHub
|
||||
@@ -30,9 +32,8 @@ test('creation hub draft card renders compiled work summary fields', () => {
|
||||
]}
|
||||
loading={false}
|
||||
error={null}
|
||||
onBack={() => {}}
|
||||
onRetry={() => {}}
|
||||
onCreateNew={() => {}}
|
||||
onCreateType={noopCreateType}
|
||||
onOpenDraft={() => {}}
|
||||
onEnterPublished={() => {}}
|
||||
/>,
|
||||
@@ -41,6 +42,9 @@ test('creation hub draft card renders compiled work summary fields', () => {
|
||||
expect(html).toContain('一个被潮雾切开的列岛世界');
|
||||
expect(html).toContain('玩家是失职返乡的守灯人');
|
||||
expect(html).toContain('守灯会与沉船商盟争夺航道解释权');
|
||||
expect(html).toContain('角色扮演 RPG');
|
||||
expect(html).toContain('大鱼吃小鱼');
|
||||
expect(html).toContain('拼图玩法');
|
||||
});
|
||||
|
||||
test('creation hub renders puzzle works in the same unified list with puzzle tag', () => {
|
||||
@@ -66,9 +70,8 @@ test('creation hub renders puzzle works in the same unified list with puzzle tag
|
||||
]}
|
||||
loading={false}
|
||||
error={null}
|
||||
onBack={() => {}}
|
||||
onRetry={() => {}}
|
||||
onCreateNew={() => {}}
|
||||
onCreateType={noopCreateType}
|
||||
onOpenDraft={() => {}}
|
||||
onEnterPublished={() => {}}
|
||||
onOpenPuzzleDetail={() => {}}
|
||||
@@ -78,5 +81,4 @@ test('creation hub renders puzzle works in the same unified list with puzzle tag
|
||||
expect(html).toContain('潮雾拼图');
|
||||
expect(html).toContain('拼图');
|
||||
expect(html).not.toContain('我的拼图作品');
|
||||
expect(html).not.toContain('拼图玩法');
|
||||
});
|
||||
|
||||
@@ -11,14 +11,16 @@ import {
|
||||
type CustomWorldWorkFilter,
|
||||
CustomWorldWorkTabs,
|
||||
} from './CustomWorldWorkTabs';
|
||||
import type { PlatformCreationTypeId } from '../platform-entry/platformEntryCreationTypes';
|
||||
|
||||
type CustomWorldCreationHubProps = {
|
||||
items: CustomWorldWorkSummary[];
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
onBack: () => void;
|
||||
onRetry: () => void;
|
||||
onCreateNew: () => void;
|
||||
createError?: string | null;
|
||||
createBusy?: boolean;
|
||||
onCreateType: (type: PlatformCreationTypeId) => void;
|
||||
onOpenDraft: (item: CustomWorldWorkSummary) => void;
|
||||
onEnterPublished: (profileId: string) => void;
|
||||
puzzleItems?: PuzzleWorkSummary[];
|
||||
@@ -39,9 +41,10 @@ export function CustomWorldCreationHub({
|
||||
items,
|
||||
loading,
|
||||
error,
|
||||
onBack,
|
||||
onRetry,
|
||||
onCreateNew,
|
||||
createError = null,
|
||||
createBusy = false,
|
||||
onCreateType,
|
||||
onOpenDraft,
|
||||
onEnterPublished,
|
||||
puzzleItems = [],
|
||||
@@ -80,33 +83,12 @@ export function CustomWorldCreationHub({
|
||||
|
||||
return (
|
||||
<div className="platform-page-stage platform-remap-surface space-y-4 px-3 pb-4 pt-3 sm:px-4 sm:pt-4">
|
||||
<div className="pb-1">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onBack}
|
||||
className="platform-button platform-button--ghost min-h-0 rounded-full px-3 py-1.5 text-[11px]"
|
||||
>
|
||||
返回
|
||||
</button>
|
||||
<div className="mt-4 text-[1.8rem] font-black leading-tight text-[var(--platform-text-strong)] sm:text-[2.3rem]">
|
||||
创作中心
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden shrink-0 gap-2 sm:flex">
|
||||
<span className="platform-pill platform-pill--warm px-3 py-1 text-[11px]">
|
||||
草稿 {draftCount}
|
||||
</span>
|
||||
<span className="platform-pill platform-pill--success px-3 py-1 text-[11px]">
|
||||
已发布 {publishedCount}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<CustomWorldCreationStartCard onCreateNew={onCreateNew} />
|
||||
<CustomWorldCreationStartCard
|
||||
busy={createBusy}
|
||||
error={createError}
|
||||
onCreateType={onCreateType}
|
||||
/>
|
||||
|
||||
<CustomWorldWorkTabs
|
||||
activeFilter={activeFilter}
|
||||
|
||||
@@ -1,27 +1,89 @@
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
|
||||
import {
|
||||
PLATFORM_CREATION_TYPES,
|
||||
type PlatformCreationTypeId,
|
||||
} from '../platform-entry/platformEntryCreationTypes';
|
||||
|
||||
type CustomWorldCreationStartCardProps = {
|
||||
onCreateNew: () => void;
|
||||
busy?: boolean;
|
||||
error?: string | null;
|
||||
onCreateType: (type: PlatformCreationTypeId) => void;
|
||||
};
|
||||
|
||||
export function CustomWorldCreationStartCard({
|
||||
onCreateNew,
|
||||
busy = false,
|
||||
error = null,
|
||||
onCreateType,
|
||||
}: CustomWorldCreationStartCardProps) {
|
||||
return (
|
||||
<div className="platform-surface platform-surface--hero relative overflow-hidden px-5 py-5">
|
||||
<div className="absolute inset-0 bg-[var(--platform-hero-overlay-strong)]" />
|
||||
<div className="relative z-10 flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
|
||||
<div className="relative z-10 space-y-4">
|
||||
<div>
|
||||
<div className="text-2xl font-black text-white sm:text-3xl">
|
||||
新建作品
|
||||
</div>
|
||||
<div className="mt-2 text-sm leading-6 text-zinc-200/88">
|
||||
直接选择游戏创作模板,立刻进入对应的共创工作台。
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onCreateNew}
|
||||
className="platform-button platform-button--primary w-full justify-between rounded-[1.1rem] text-left sm:w-auto"
|
||||
>
|
||||
<span className="text-sm font-semibold">新建作品</span>
|
||||
<span aria-hidden="true">→</span>
|
||||
</button>
|
||||
|
||||
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-5">
|
||||
{PLATFORM_CREATION_TYPES.map((item) => {
|
||||
const disabled = item.locked || busy;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={item.id}
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
onClick={() => {
|
||||
onCreateType(item.id);
|
||||
}}
|
||||
className={`platform-interactive-card relative overflow-hidden rounded-[1.5rem] border px-4 py-4 text-left transition ${
|
||||
item.locked
|
||||
? 'cursor-not-allowed border-white/10 bg-white/8 text-zinc-300/70'
|
||||
: 'border-white/18 bg-[radial-gradient(circle_at_top_left,rgba(255,255,255,0.24),transparent_36%),linear-gradient(135deg,rgba(255,255,255,0.18),rgba(255,255,255,0.08))] text-white'
|
||||
} ${busy && !item.locked ? 'opacity-70' : ''}`}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<span
|
||||
className={`platform-pill px-3 ${
|
||||
item.locked
|
||||
? 'platform-pill--neutral text-[var(--platform-text-soft)]'
|
||||
: 'platform-pill--neutral border-white/30 bg-white/18 text-white'
|
||||
}`}
|
||||
>
|
||||
{item.locked ? item.badge : busy ? '正在开启' : item.badge}
|
||||
</span>
|
||||
{item.locked ? (
|
||||
<span className="text-base leading-none text-white/40">·</span>
|
||||
) : (
|
||||
<ArrowRight className="h-4 w-4 text-white/80" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-7 text-lg font-black leading-tight text-inherit">
|
||||
{item.title}
|
||||
</div>
|
||||
<div
|
||||
className={`mt-2 text-sm ${
|
||||
item.locked ? 'text-zinc-400' : 'text-zinc-200/82'
|
||||
}`}
|
||||
>
|
||||
{item.subtitle}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{error ? (
|
||||
<div className="platform-banner platform-banner--danger rounded-[1.25rem] text-sm leading-6">
|
||||
{error}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user