156 lines
3.9 KiB
TypeScript
156 lines
3.9 KiB
TypeScript
import type {
|
|
CustomWorldGalleryCard,
|
|
CustomWorldLibraryEntry,
|
|
} from '../../../packages/shared/src/contracts/runtime';
|
|
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
|
|
import { buildCustomWorldPlayableCharacters } from '../../data/characterPresets';
|
|
import { resolveCustomWorldCampSceneImage } from '../../data/customWorldVisuals';
|
|
import { buildPuzzlePublicWorkCode } from '../../services/publicWorkCode';
|
|
import type { CustomWorldProfile } from '../../types';
|
|
|
|
export type PlatformWorldCardLike =
|
|
| CustomWorldGalleryCard
|
|
| CustomWorldLibraryEntry<CustomWorldProfile>
|
|
| PlatformPuzzleGalleryCard;
|
|
|
|
export type PlatformPuzzleGalleryCard = {
|
|
sourceType: 'puzzle';
|
|
workId: string;
|
|
profileId: string;
|
|
publicWorkCode: string;
|
|
ownerUserId: string;
|
|
authorDisplayName: string;
|
|
worldName: string;
|
|
subtitle: string;
|
|
summaryText: string;
|
|
coverImageSrc: string | null;
|
|
themeTags: string[];
|
|
visibility: 'published';
|
|
publishedAt: string | null;
|
|
updatedAt: string;
|
|
};
|
|
|
|
export type PlatformPublicGalleryCard =
|
|
| CustomWorldGalleryCard
|
|
| PlatformPuzzleGalleryCard;
|
|
|
|
export function isLibraryWorldEntry(
|
|
entry: PlatformWorldCardLike,
|
|
): entry is CustomWorldLibraryEntry<CustomWorldProfile> {
|
|
return 'profile' in entry;
|
|
}
|
|
|
|
export function isPuzzleGalleryEntry(
|
|
entry: PlatformWorldCardLike,
|
|
): entry is PlatformPuzzleGalleryCard {
|
|
return 'sourceType' in entry && entry.sourceType === 'puzzle';
|
|
}
|
|
|
|
export function mapPuzzleWorkToPlatformGalleryCard(
|
|
work: PuzzleWorkSummary,
|
|
): PlatformPuzzleGalleryCard {
|
|
return {
|
|
sourceType: 'puzzle',
|
|
workId: work.workId,
|
|
profileId: work.profileId,
|
|
publicWorkCode: buildPuzzlePublicWorkCode(work.profileId),
|
|
ownerUserId: work.ownerUserId,
|
|
authorDisplayName: work.authorDisplayName,
|
|
worldName: work.levelName,
|
|
subtitle: '拼图关卡',
|
|
summaryText: work.summary,
|
|
coverImageSrc: work.coverImageSrc,
|
|
themeTags: work.themeTags,
|
|
visibility: 'published',
|
|
publishedAt: work.publishedAt,
|
|
updatedAt: work.updatedAt,
|
|
};
|
|
}
|
|
|
|
export function resolvePlatformWorldCoverImage(entry: PlatformWorldCardLike) {
|
|
if (entry.coverImageSrc) {
|
|
return entry.coverImageSrc;
|
|
}
|
|
|
|
if (isLibraryWorldEntry(entry)) {
|
|
return resolveCustomWorldCampSceneImage(entry.profile) ?? '';
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
export function resolvePlatformWorldLeadPortrait(entry: PlatformWorldCardLike) {
|
|
if (!isLibraryWorldEntry(entry)) {
|
|
return '';
|
|
}
|
|
|
|
return buildCustomWorldPlayableCharacters(entry.profile)[0]?.portrait ?? '';
|
|
}
|
|
|
|
export function buildPlatformWorldTags(entry: PlatformWorldCardLike) {
|
|
if (isPuzzleGalleryEntry(entry)) {
|
|
return entry.themeTags.length > 0 ? entry.themeTags.slice(0, 3) : ['拼图'];
|
|
}
|
|
|
|
if (!isLibraryWorldEntry(entry)) {
|
|
return [
|
|
describePlatformThemeLabel(entry.themeMode),
|
|
`${entry.playableNpcCount} 角色`,
|
|
`${entry.landmarkCount} 地标`,
|
|
];
|
|
}
|
|
|
|
return [
|
|
...entry.profile.majorFactions.slice(0, 2),
|
|
...entry.profile.coreConflicts.slice(0, 1),
|
|
]
|
|
.map((value) => value.trim())
|
|
.filter(Boolean)
|
|
.slice(0, 3);
|
|
}
|
|
|
|
export function formatPlatformWorldTime(value: string | null) {
|
|
if (!value) {
|
|
return '未发布';
|
|
}
|
|
|
|
const date = new Date(value);
|
|
if (Number.isNaN(date.getTime())) {
|
|
return value;
|
|
}
|
|
|
|
return date.toLocaleDateString('zh-CN', {
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
});
|
|
}
|
|
|
|
export function resolvePlatformPublicWorkCode(
|
|
entry: PlatformWorldCardLike,
|
|
): string | null {
|
|
if (isPuzzleGalleryEntry(entry)) {
|
|
return entry.publicWorkCode;
|
|
}
|
|
|
|
return entry.publicWorkCode;
|
|
}
|
|
|
|
export function describePlatformThemeLabel(
|
|
themeMode: CustomWorldGalleryCard['themeMode'],
|
|
) {
|
|
switch (themeMode) {
|
|
case 'martial':
|
|
return '江湖';
|
|
case 'arcane':
|
|
return '灵脉';
|
|
case 'machina':
|
|
return '机巧';
|
|
case 'tide':
|
|
return '潮痕';
|
|
case 'rift':
|
|
return '裂界';
|
|
default:
|
|
return '回响';
|
|
}
|
|
}
|