Refine creation tab UX, generation flow, and bindings
Large changes across frontend, backend and docs to align creation-tab and generation-page behavior with new product UI/UX and Spacetime bindings. Updated hermes decision-log and pitfalls with concrete rules (banner carousel, font sizing, unread-dot tokens, template-card layout, direct card->entry routing, separation of account balance vs prize pools, removal of global page card shell, generation progress milestones and unified circular progress, and background video handling). Added GenerationProgressHero component and media assets, plus generation-related UI/tests updates (CustomWorldGenerationView, BarkBattleGeneratingView, creation hub/cards, platform entry routing, index tests). Backend and contract updates include new category fields in admin API types and admin UI form/list, spacetime-client/module/migration changes and generated bindings script. Misc: many tests adjusted, new docs and plan files added, and several server-rs crate changes to support the updated creation/ generation workflows.
This commit is contained in:
@@ -18,6 +18,14 @@ const entryConfig = {
|
||||
title: '选择创作类型',
|
||||
description: '',
|
||||
},
|
||||
eventBanner: {
|
||||
title: '泥点挑战',
|
||||
description: '创作活动测试横幅。',
|
||||
coverImageSrc: '/creation-type-references/puzzle.webp',
|
||||
prizePoolMudPoints: 1000,
|
||||
startsAtText: '2026-05-01',
|
||||
endsAtText: '2026-05-31',
|
||||
},
|
||||
creationTypes: [
|
||||
{
|
||||
id: 'wooden-fish',
|
||||
@@ -28,6 +36,9 @@ const entryConfig = {
|
||||
visible: true,
|
||||
open: true,
|
||||
sortOrder: 10,
|
||||
categoryId: 'recent',
|
||||
categoryLabel: '最近创作',
|
||||
categorySortOrder: 10,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
],
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@ import { afterEach, expect, test, vi } from 'vitest';
|
||||
|
||||
import {
|
||||
derivePlatformCreationTypes,
|
||||
groupVisiblePlatformCreationTypes,
|
||||
getVisiblePlatformCreationTypes,
|
||||
isPlatformCreationTypeOpen,
|
||||
isPlatformCreationTypeVisible,
|
||||
@@ -22,6 +23,9 @@ test('database entry config controls visibility open state and display order', (
|
||||
visible: true,
|
||||
open: false,
|
||||
sortOrder: 30,
|
||||
categoryId: 'recommended',
|
||||
categoryLabel: '热门推荐',
|
||||
categorySortOrder: 20,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
{
|
||||
@@ -33,6 +37,9 @@ test('database entry config controls visibility open state and display order', (
|
||||
visible: true,
|
||||
open: true,
|
||||
sortOrder: 20,
|
||||
categoryId: 'recent',
|
||||
categoryLabel: '最近创作',
|
||||
categorySortOrder: 10,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
{
|
||||
@@ -44,6 +51,9 @@ test('database entry config controls visibility open state and display order', (
|
||||
visible: false,
|
||||
open: true,
|
||||
sortOrder: 10,
|
||||
categoryId: 'festival',
|
||||
categoryLabel: '节日主题',
|
||||
categorySortOrder: 30,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
]);
|
||||
@@ -79,6 +89,9 @@ test('visible platform creation types hide invisible cards and put locked cards
|
||||
visible: false,
|
||||
open: true,
|
||||
sortOrder: 1,
|
||||
categoryId: 'hidden',
|
||||
categoryLabel: '隐藏',
|
||||
categorySortOrder: 99,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
{
|
||||
@@ -90,6 +103,9 @@ test('visible platform creation types hide invisible cards and put locked cards
|
||||
visible: true,
|
||||
open: false,
|
||||
sortOrder: 2,
|
||||
categoryId: 'recommended',
|
||||
categoryLabel: '热门推荐',
|
||||
categorySortOrder: 20,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
{
|
||||
@@ -101,6 +117,9 @@ test('visible platform creation types hide invisible cards and put locked cards
|
||||
visible: true,
|
||||
open: true,
|
||||
sortOrder: 3,
|
||||
categoryId: 'recent',
|
||||
categoryLabel: '最近创作',
|
||||
categorySortOrder: 10,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
]);
|
||||
@@ -131,6 +150,9 @@ test('edutainment switch hides baby object match creation entry from database co
|
||||
visible: true,
|
||||
open: true,
|
||||
sortOrder: 1,
|
||||
categoryId: 'character',
|
||||
categoryLabel: '角色创作',
|
||||
categorySortOrder: 40,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
{
|
||||
@@ -142,6 +164,9 @@ test('edutainment switch hides baby object match creation entry from database co
|
||||
visible: true,
|
||||
open: true,
|
||||
sortOrder: 2,
|
||||
categoryId: 'recent',
|
||||
categoryLabel: '最近创作',
|
||||
categorySortOrder: 10,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
]);
|
||||
@@ -160,6 +185,9 @@ test('edutainment switch hides baby object match creation entry from database co
|
||||
visible: true,
|
||||
open: true,
|
||||
sortOrder: 1,
|
||||
categoryId: 'character',
|
||||
categoryLabel: '角色创作',
|
||||
categorySortOrder: 40,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
{
|
||||
@@ -171,6 +199,9 @@ test('edutainment switch hides baby object match creation entry from database co
|
||||
visible: true,
|
||||
open: true,
|
||||
sortOrder: 2,
|
||||
categoryId: 'recent',
|
||||
categoryLabel: '最近创作',
|
||||
categorySortOrder: 10,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
]);
|
||||
@@ -194,6 +225,9 @@ test('baby object match entry is visible and open when database marks it creatab
|
||||
visible: true,
|
||||
open: true,
|
||||
sortOrder: 90,
|
||||
categoryId: 'character',
|
||||
categoryLabel: '角色创作',
|
||||
categorySortOrder: 40,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
]);
|
||||
@@ -208,3 +242,76 @@ test('baby object match entry is visible and open when database marks it creatab
|
||||
expect(isPlatformCreationTypeVisible(cards, 'baby-object-match')).toBe(true);
|
||||
expect(isPlatformCreationTypeOpen(cards, 'baby-object-match')).toBe(true);
|
||||
});
|
||||
|
||||
test('groups visible platform creation types by backend category metadata', () => {
|
||||
const cards = derivePlatformCreationTypes([
|
||||
{
|
||||
id: 'puzzle',
|
||||
title: '秋日暖阳',
|
||||
subtitle: '记录秋日的温暖时光',
|
||||
badge: '热门',
|
||||
imageSrc: '/creation-type-references/puzzle.webp',
|
||||
visible: true,
|
||||
open: true,
|
||||
sortOrder: 30,
|
||||
categoryId: 'recent',
|
||||
categoryLabel: '最近创作',
|
||||
categorySortOrder: 10,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
{
|
||||
id: 'match3d',
|
||||
title: '秋日小屋',
|
||||
subtitle: '打造专属的秋日小屋',
|
||||
badge: '精选',
|
||||
imageSrc: '/creation-type-references/match3d.webp',
|
||||
visible: true,
|
||||
open: true,
|
||||
sortOrder: 40,
|
||||
categoryId: 'recent',
|
||||
categoryLabel: '最近创作',
|
||||
categorySortOrder: 10,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
{
|
||||
id: 'visual-novel',
|
||||
title: '视觉小说',
|
||||
subtitle: '分支叙事体验',
|
||||
badge: '敬请期待',
|
||||
imageSrc: '/creation-type-references/visual-novel.webp',
|
||||
visible: true,
|
||||
open: false,
|
||||
sortOrder: 60,
|
||||
categoryId: 'festival',
|
||||
categoryLabel: '节日主题',
|
||||
categorySortOrder: 30,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
{
|
||||
id: 'hidden',
|
||||
title: '隐藏入口',
|
||||
subtitle: '隐藏',
|
||||
badge: '隐藏',
|
||||
imageSrc: '/creation-type-references/hidden.webp',
|
||||
visible: false,
|
||||
open: true,
|
||||
sortOrder: 10,
|
||||
categoryId: 'recent',
|
||||
categoryLabel: '最近创作',
|
||||
categorySortOrder: 10,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
]);
|
||||
|
||||
const groups = groupVisiblePlatformCreationTypes(cards);
|
||||
|
||||
expect(groups.map((group) => group.label)).toEqual([
|
||||
'最近创作',
|
||||
'节日主题',
|
||||
]);
|
||||
expect(groups[0]?.items.map((item) => item.id)).toEqual([
|
||||
'puzzle',
|
||||
'match3d',
|
||||
]);
|
||||
expect(groups[1]?.items.map((item) => item.id)).toEqual(['visual-novel']);
|
||||
});
|
||||
|
||||
@@ -10,9 +10,23 @@ export type PlatformCreationTypeCard = {
|
||||
badge: string;
|
||||
imageSrc: string;
|
||||
locked: boolean;
|
||||
categoryId: string;
|
||||
categoryLabel: string;
|
||||
categorySortOrder: number;
|
||||
sortOrder: number;
|
||||
hidden?: boolean;
|
||||
};
|
||||
|
||||
export type PlatformCreationTypeGroup = {
|
||||
id: string;
|
||||
label: string;
|
||||
sortOrder: number;
|
||||
items: PlatformCreationTypeCard[];
|
||||
};
|
||||
|
||||
const FALLBACK_CREATION_CATEGORY_ID = 'recent';
|
||||
const FALLBACK_CREATION_CATEGORY_LABEL = '最近创作';
|
||||
|
||||
export function getVisiblePlatformCreationTypes(
|
||||
creationTypes: readonly PlatformCreationTypeCard[],
|
||||
) {
|
||||
@@ -41,6 +55,50 @@ export function isPlatformCreationTypeOpen(
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeCategoryId(value: string) {
|
||||
const normalized = value.trim();
|
||||
return normalized || FALLBACK_CREATION_CATEGORY_ID;
|
||||
}
|
||||
|
||||
function normalizeCategoryLabel(value: string) {
|
||||
const normalized = value.trim();
|
||||
return normalized || FALLBACK_CREATION_CATEGORY_LABEL;
|
||||
}
|
||||
|
||||
export function groupVisiblePlatformCreationTypes(
|
||||
creationTypes: readonly PlatformCreationTypeCard[],
|
||||
): PlatformCreationTypeGroup[] {
|
||||
const groups = new Map<string, PlatformCreationTypeGroup>();
|
||||
|
||||
for (const item of getVisiblePlatformCreationTypes(creationTypes)) {
|
||||
const categoryId = normalizeCategoryId(item.categoryId);
|
||||
const categoryLabel = normalizeCategoryLabel(item.categoryLabel);
|
||||
const existing = groups.get(categoryId);
|
||||
|
||||
if (existing) {
|
||||
existing.items.push(item);
|
||||
if (item.categorySortOrder < existing.sortOrder) {
|
||||
existing.sortOrder = item.categorySortOrder;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
groups.set(categoryId, {
|
||||
id: categoryId,
|
||||
label: categoryLabel,
|
||||
sortOrder: item.categorySortOrder,
|
||||
items: [item],
|
||||
});
|
||||
}
|
||||
|
||||
return [...groups.values()].sort((left, right) => {
|
||||
if (left.sortOrder !== right.sortOrder) {
|
||||
return left.sortOrder - right.sortOrder;
|
||||
}
|
||||
return left.label.localeCompare(right.label, 'zh-Hans-CN');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创作入口卡片只做展示派生;配置事实源来自后端 API / SpacetimeDB,前端不再保留入口默认配置。
|
||||
*/
|
||||
@@ -56,6 +114,10 @@ export function derivePlatformCreationTypes(
|
||||
badge: item.badge,
|
||||
imageSrc: item.imageSrc,
|
||||
locked: !item.open,
|
||||
categoryId: normalizeCategoryId(item.categoryId),
|
||||
categoryLabel: normalizeCategoryLabel(item.categoryLabel),
|
||||
categorySortOrder: item.categorySortOrder,
|
||||
sortOrder: item.sortOrder,
|
||||
hidden:
|
||||
!item.visible ||
|
||||
(item.id === 'baby-object-match' && !isEdutainmentEntryEnabled()),
|
||||
|
||||
@@ -36,6 +36,7 @@ export type SelectionStage =
|
||||
| 'jump-hop-result'
|
||||
| 'jump-hop-runtime'
|
||||
| 'jump-hop-gallery-detail'
|
||||
| 'bark-battle-workspace'
|
||||
| 'bark-battle-generating'
|
||||
| 'bark-battle-result'
|
||||
| 'bark-battle-runtime'
|
||||
|
||||
Reference in New Issue
Block a user