272 lines
7.5 KiB
TypeScript
272 lines
7.5 KiB
TypeScript
import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/bigFishWorkSummary';
|
|
import type { CustomWorldWorkSummary } from '../../../packages/shared/src/contracts/customWorldAgent';
|
|
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
|
|
import type { CustomWorldLibraryEntry } from '../../../packages/shared/src/contracts/runtime';
|
|
import { buildPuzzlePublicWorkCode } from '../../services/publicWorkCode';
|
|
import type { CustomWorldProfile } from '../../types';
|
|
|
|
export type CreationWorkShelfKind = 'rpg' | 'big-fish' | 'puzzle';
|
|
export type CreationWorkShelfStatus = 'draft' | 'published';
|
|
|
|
export type CreationWorkShelfBadgeTone = 'warm' | 'success' | 'neutral';
|
|
|
|
export type CreationWorkShelfBadge = {
|
|
id: string;
|
|
label: string;
|
|
tone: CreationWorkShelfBadgeTone;
|
|
};
|
|
|
|
export type CreationWorkShelfMetric = {
|
|
id: string;
|
|
label: string;
|
|
tone?: CreationWorkShelfBadgeTone;
|
|
};
|
|
|
|
export type CreationWorkShelfSource =
|
|
| {
|
|
kind: 'rpg';
|
|
item: CustomWorldWorkSummary;
|
|
}
|
|
| {
|
|
kind: 'big-fish';
|
|
item: BigFishWorkSummary;
|
|
}
|
|
| {
|
|
kind: 'puzzle';
|
|
item: PuzzleWorkSummary;
|
|
};
|
|
|
|
export type CreationWorkShelfItem = {
|
|
id: string;
|
|
kind: CreationWorkShelfKind;
|
|
status: CreationWorkShelfStatus;
|
|
title: string;
|
|
subtitle: string;
|
|
summary: string;
|
|
updatedAt: string;
|
|
coverImageSrc: string | null;
|
|
coverRenderMode: 'image' | 'scene_with_roles';
|
|
coverCharacterImageSrcs: string[];
|
|
publicWorkCode: string | null;
|
|
typeLabel: string;
|
|
openActionLabel: string;
|
|
canExperience: boolean;
|
|
canDelete: boolean;
|
|
badges: CreationWorkShelfBadge[];
|
|
metrics: CreationWorkShelfMetric[];
|
|
source: CreationWorkShelfSource;
|
|
};
|
|
|
|
export function buildCreationWorkShelfItems(params: {
|
|
rpgItems: CustomWorldWorkSummary[];
|
|
rpgLibraryEntries?: CustomWorldLibraryEntry<CustomWorldProfile>[];
|
|
bigFishItems: BigFishWorkSummary[];
|
|
puzzleItems: PuzzleWorkSummary[];
|
|
canDeleteRpg?: boolean;
|
|
canDeleteBigFish?: boolean;
|
|
canDeletePuzzle?: boolean;
|
|
}) {
|
|
const {
|
|
rpgItems,
|
|
rpgLibraryEntries = [],
|
|
bigFishItems,
|
|
puzzleItems,
|
|
canDeleteRpg = false,
|
|
canDeleteBigFish = false,
|
|
canDeletePuzzle = false,
|
|
} = params;
|
|
|
|
return [
|
|
...rpgItems.map((item) =>
|
|
mapRpgWorkToShelfItem(item, canDeleteRpg, rpgLibraryEntries),
|
|
),
|
|
...bigFishItems.map((item) =>
|
|
mapBigFishWorkToShelfItem(item, canDeleteBigFish),
|
|
),
|
|
...puzzleItems.map((item) =>
|
|
mapPuzzleWorkToShelfItem(item, canDeletePuzzle),
|
|
),
|
|
].sort(
|
|
(left, right) =>
|
|
getShelfItemTime(right.updatedAt) - getShelfItemTime(left.updatedAt),
|
|
);
|
|
}
|
|
|
|
function mapRpgWorkToShelfItem(
|
|
item: CustomWorldWorkSummary,
|
|
canDelete: boolean,
|
|
libraryEntries: CustomWorldLibraryEntry<CustomWorldProfile>[],
|
|
): CreationWorkShelfItem {
|
|
const isDraft = item.status === 'draft';
|
|
const libraryEntry = item.profileId
|
|
? libraryEntries.find((entry) => entry.profileId === item.profileId)
|
|
: null;
|
|
const badges: CreationWorkShelfBadge[] = [
|
|
buildStatusBadge(item.status),
|
|
{ id: 'type', label: 'RPG', tone: 'neutral' },
|
|
];
|
|
if (item.stageLabel) {
|
|
badges.push({ id: 'stage', label: item.stageLabel, tone: 'neutral' });
|
|
}
|
|
|
|
const metrics: CreationWorkShelfMetric[] = [
|
|
{
|
|
id: 'playable-npc-count',
|
|
label: `${isDraft ? '角色' : '可扮演角色'} ${item.playableNpcCount}`,
|
|
},
|
|
{ id: 'landmark-count', label: `地点 ${item.landmarkCount}` },
|
|
];
|
|
if (item.roleVisualReadyCount) {
|
|
metrics.push({
|
|
id: 'role-visual-ready-count',
|
|
label: `主图 ${item.roleVisualReadyCount}`,
|
|
tone: 'warm',
|
|
});
|
|
}
|
|
if (item.roleAnimationReadyCount) {
|
|
metrics.push({
|
|
id: 'role-animation-ready-count',
|
|
label: `动作 ${item.roleAnimationReadyCount}`,
|
|
tone: 'success',
|
|
});
|
|
}
|
|
if (item.roleAssetSummaryLabel) {
|
|
metrics.push({
|
|
id: 'role-asset-summary',
|
|
label: item.roleAssetSummaryLabel,
|
|
});
|
|
}
|
|
|
|
return {
|
|
id: item.workId,
|
|
kind: 'rpg',
|
|
status: item.status,
|
|
title: item.title,
|
|
subtitle: item.subtitle,
|
|
summary: item.summary,
|
|
updatedAt: item.updatedAt,
|
|
coverImageSrc: item.coverImageSrc ?? null,
|
|
coverRenderMode: item.coverRenderMode ?? 'image',
|
|
coverCharacterImageSrcs: item.coverCharacterImageSrcs ?? [],
|
|
publicWorkCode:
|
|
item.status === 'published'
|
|
? (libraryEntry?.publicWorkCode ?? null)
|
|
: null,
|
|
typeLabel: 'RPG',
|
|
openActionLabel: isDraft
|
|
? item.playableNpcCount > 0 || item.landmarkCount > 0
|
|
? '继续完善'
|
|
: '继续创作'
|
|
: '查看详情',
|
|
canExperience: item.status === 'published' && item.canEnterWorld,
|
|
canDelete,
|
|
badges,
|
|
metrics,
|
|
source: { kind: 'rpg', item },
|
|
};
|
|
}
|
|
|
|
function mapBigFishWorkToShelfItem(
|
|
item: BigFishWorkSummary,
|
|
canDelete: boolean,
|
|
): CreationWorkShelfItem {
|
|
return {
|
|
id: item.workId,
|
|
kind: 'big-fish',
|
|
status: item.status,
|
|
title: item.title,
|
|
subtitle: item.subtitle,
|
|
summary: item.summary,
|
|
updatedAt: item.updatedAt,
|
|
coverImageSrc: item.coverImageSrc ?? null,
|
|
coverRenderMode: 'image',
|
|
coverCharacterImageSrcs: [],
|
|
publicWorkCode: null,
|
|
typeLabel: '大鱼',
|
|
openActionLabel: item.status === 'draft' ? '继续创作' : '查看详情',
|
|
canExperience: item.status === 'published',
|
|
canDelete,
|
|
badges: [
|
|
buildStatusBadge(item.status),
|
|
{ id: 'type', label: '大鱼', tone: 'neutral' },
|
|
],
|
|
metrics: [
|
|
{ id: 'level-count', label: `关卡 ${item.levelCount}` },
|
|
{
|
|
id: 'level-main-image-ready-count',
|
|
label: `主图 ${item.levelMainImageReadyCount}`,
|
|
},
|
|
{
|
|
id: 'level-motion-ready-count',
|
|
label: `动作 ${item.levelMotionReadyCount}`,
|
|
},
|
|
...(item.backgroundReady
|
|
? [
|
|
{
|
|
id: 'background-ready',
|
|
label: '背景已就绪',
|
|
tone: 'success' as const,
|
|
},
|
|
]
|
|
: []),
|
|
],
|
|
source: { kind: 'big-fish', item },
|
|
};
|
|
}
|
|
|
|
function mapPuzzleWorkToShelfItem(
|
|
item: PuzzleWorkSummary,
|
|
canDelete: boolean,
|
|
): CreationWorkShelfItem {
|
|
const status = item.publicationStatus;
|
|
|
|
return {
|
|
id: item.workId,
|
|
kind: 'puzzle',
|
|
status,
|
|
title: item.levelName,
|
|
subtitle: item.authorDisplayName,
|
|
summary: item.summary,
|
|
updatedAt: item.updatedAt,
|
|
coverImageSrc: item.coverImageSrc ?? null,
|
|
coverRenderMode: 'image',
|
|
coverCharacterImageSrcs: [],
|
|
publicWorkCode:
|
|
status === 'published' ? buildPuzzlePublicWorkCode(item.profileId) : null,
|
|
typeLabel: '拼图',
|
|
openActionLabel:
|
|
status === 'published' && !item.sourceSessionId ? '查看详情' : '继续创作',
|
|
canExperience: status === 'published',
|
|
canDelete,
|
|
badges: [
|
|
buildStatusBadge(status),
|
|
{ id: 'type', label: '拼图', tone: 'neutral' },
|
|
...item.themeTags.slice(0, 2).map((tag) => ({
|
|
id: `tag:${tag}`,
|
|
label: tag,
|
|
tone: 'neutral' as const,
|
|
})),
|
|
],
|
|
metrics: [
|
|
{ id: 'author', label: `作者 ${item.authorDisplayName}` },
|
|
{ id: 'play-count', label: `游玩 ${item.playCount}` },
|
|
],
|
|
source: { kind: 'puzzle', item },
|
|
};
|
|
}
|
|
|
|
function buildStatusBadge(
|
|
status: CreationWorkShelfStatus,
|
|
): CreationWorkShelfBadge {
|
|
return {
|
|
id: 'status',
|
|
label: status === 'draft' ? '草稿' : '已发布',
|
|
tone: status === 'draft' ? 'warm' : 'success',
|
|
};
|
|
}
|
|
|
|
function getShelfItemTime(value: string) {
|
|
const timestamp = new Date(value).getTime();
|
|
return Number.isNaN(timestamp) ? 0 : timestamp;
|
|
}
|