Merge branch 'codex/profile-mobile-ui-reference'

This commit is contained in:
2026-05-25 01:41:05 +08:00
74 changed files with 5512 additions and 1090 deletions

View File

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

View File

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

View File

@@ -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()),

View File

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