This commit is contained in:
2026-04-22 22:01:07 +08:00
parent d8716d70b0
commit b317c2a8ea
37 changed files with 1821 additions and 515 deletions

View File

@@ -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();
});

View File

@@ -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('拼图玩法');
});

View File

@@ -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}

View File

@@ -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>
);