refactor: 收口公开作品详情映射
This commit is contained in:
@@ -374,7 +374,6 @@ import {
|
||||
mapBarkBattleWorkToPlatformGalleryCard,
|
||||
mapBigFishWorkToPlatformGalleryCard,
|
||||
mapJumpHopWorkToPlatformGalleryCard,
|
||||
mapMatch3DWorkToPlatformGalleryCard,
|
||||
mapPuzzleWorkToPlatformGalleryCard,
|
||||
mapSquareHoleWorkToPlatformGalleryCard,
|
||||
mapVisualNovelWorkToPlatformGalleryCard,
|
||||
@@ -500,6 +499,7 @@ import {
|
||||
hasMatch3DRuntimeAsset,
|
||||
hasMatch3DRuntimeBackgroundAsset,
|
||||
mapMatch3DWorksForRuntimeUi,
|
||||
mapMatch3DWorkToPublicWorkDetail,
|
||||
mapPublicWorkDetailToMatch3DWork,
|
||||
normalizeMatch3DWorkForRuntimeUi,
|
||||
promoteMatch3DGeneratedBackgroundAsset,
|
||||
@@ -516,6 +516,18 @@ import {
|
||||
type RecommendRuntimeKind,
|
||||
} from './platformPublicGalleryFlow';
|
||||
import {
|
||||
mapBarkBattlePublicDetailToWorkSummary,
|
||||
mapBarkBattleWorkToPublicWorkDetail,
|
||||
mapBigFishWorkToPublicWorkDetail,
|
||||
mapJumpHopWorkToPublicWorkDetail,
|
||||
mapPublicWorkDetailToBigFishWork,
|
||||
mapPublicWorkDetailToPuzzleWork,
|
||||
mapPublicWorkDetailToSquareHoleWork,
|
||||
mapPuzzleWorkToPublicWorkDetail,
|
||||
mapRpgGalleryCardToPublicWorkDetail,
|
||||
mapSquareHoleWorkToPublicWorkDetail,
|
||||
mapVisualNovelWorkToPublicWorkDetail,
|
||||
mapWoodenFishWorkToPublicWorkDetail,
|
||||
resolveActivePlatformPublicWorkAuthorEntry,
|
||||
resolvePlatformPublicWorkActionMode,
|
||||
resolvePlatformPublicWorkDetailOpenDecision,
|
||||
@@ -719,18 +731,6 @@ function isRecommendRuntimeReadyForEntry(
|
||||
return true;
|
||||
}
|
||||
|
||||
function mapRpgGalleryCardToPublicWorkDetail(
|
||||
entry: CustomWorldGalleryCard,
|
||||
): PlatformPublicGalleryCard {
|
||||
return entry;
|
||||
}
|
||||
|
||||
function mapPuzzleWorkToPublicWorkDetail(
|
||||
item: PuzzleWorkSummary,
|
||||
): PlatformPublicGalleryCard {
|
||||
return mapPuzzleWorkToPlatformGalleryCard(item);
|
||||
}
|
||||
|
||||
function resolveVisiblePuzzleDetailCoverCount(
|
||||
entry: PlatformPublicGalleryCard | null,
|
||||
run: PuzzleRunSnapshot | null,
|
||||
@@ -747,44 +747,6 @@ function resolveVisiblePuzzleDetailCoverCount(
|
||||
return Math.max(1, run.clearedLevelCount + 1);
|
||||
}
|
||||
|
||||
function mapMatch3DWorkToPublicWorkDetail(
|
||||
item: Match3DWorkSummary,
|
||||
): PlatformPublicGalleryCard {
|
||||
return mapMatch3DWorkToPlatformGalleryCard(
|
||||
normalizeMatch3DWorkForRuntimeUi(item),
|
||||
);
|
||||
}
|
||||
|
||||
function mapSquareHoleWorkToPublicWorkDetail(
|
||||
item: SquareHoleWorkSummary,
|
||||
): PlatformPublicGalleryCard {
|
||||
return mapSquareHoleWorkToPlatformGalleryCard(item);
|
||||
}
|
||||
|
||||
function mapBigFishWorkToPublicWorkDetail(
|
||||
item: BigFishWorkSummary,
|
||||
): PlatformPublicGalleryCard {
|
||||
return mapBigFishWorkToPlatformGalleryCard(item);
|
||||
}
|
||||
|
||||
function mapVisualNovelWorkToPublicWorkDetail(
|
||||
item: VisualNovelWorkSummary,
|
||||
): PlatformPublicGalleryCard {
|
||||
return mapVisualNovelWorkToPlatformGalleryCard(item);
|
||||
}
|
||||
|
||||
function mapJumpHopWorkToPublicWorkDetail(
|
||||
item: JumpHopGalleryCardResponse | JumpHopWorkProfileResponse,
|
||||
): PlatformPublicGalleryCard {
|
||||
return mapJumpHopWorkToPlatformGalleryCard(item);
|
||||
}
|
||||
|
||||
function mapBarkBattleWorkToPublicWorkDetail(
|
||||
item: BarkBattleWorkSummary,
|
||||
): PlatformPublicGalleryCard {
|
||||
return mapBarkBattleWorkToPlatformGalleryCard(item);
|
||||
}
|
||||
|
||||
function mapBarkBattleWorkToPublishedConfig(
|
||||
work: BarkBattleWorkSummary,
|
||||
): BarkBattlePublishedConfig {
|
||||
@@ -809,44 +771,6 @@ function mapBarkBattleWorkToPublishedConfig(
|
||||
};
|
||||
}
|
||||
|
||||
function mapBarkBattlePublicDetailToWorkSummary(
|
||||
entry: PlatformPublicGalleryCard,
|
||||
): BarkBattleWorkSummary | null {
|
||||
if (!isBarkBattleGalleryEntry(entry)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
workId: entry.workId,
|
||||
draftId: entry.sourceSessionId ?? null,
|
||||
ownerUserId: entry.ownerUserId,
|
||||
authorDisplayName: entry.authorDisplayName,
|
||||
title: entry.worldName,
|
||||
summary: entry.summaryText,
|
||||
themeDescription: entry.themeTags[0] ?? entry.summaryText,
|
||||
playerImageDescription: entry.themeTags[1] ?? entry.summaryText,
|
||||
opponentImageDescription: entry.themeTags[2] ?? entry.summaryText,
|
||||
onomatopoeia: undefined,
|
||||
playerCharacterImageSrc: entry.coverCharacterImageSrcs[0] ?? null,
|
||||
opponentCharacterImageSrc: entry.coverCharacterImageSrcs[1] ?? null,
|
||||
uiBackgroundImageSrc: entry.coverImageSrc,
|
||||
difficultyPreset: 'normal',
|
||||
status: 'published',
|
||||
generationStatus: 'ready',
|
||||
publishReady: true,
|
||||
playCount: entry.playCount ?? 0,
|
||||
recentPlayCount7d: entry.recentPlayCount7d ?? 0,
|
||||
updatedAt: entry.updatedAt,
|
||||
publishedAt: entry.publishedAt,
|
||||
};
|
||||
}
|
||||
|
||||
function mapWoodenFishWorkToPublicWorkDetail(
|
||||
item: WoodenFishGalleryCardResponse | WoodenFishWorkProfileResponse,
|
||||
): PlatformPublicGalleryCard {
|
||||
return mapWoodenFishWorkToPlatformGalleryCard(item);
|
||||
}
|
||||
|
||||
function mapVisualNovelWorkDetailToSession(
|
||||
work: VisualNovelWorkDetail,
|
||||
): VisualNovelAgentSessionSnapshot {
|
||||
@@ -892,122 +816,6 @@ function resolveMatch3DGenerationStateFromAssets(
|
||||
};
|
||||
}
|
||||
|
||||
function mapPublicWorkDetailToPuzzleWork(
|
||||
entry: PlatformPublicGalleryCard,
|
||||
): PuzzleWorkSummary | null {
|
||||
if (!isPuzzleGalleryEntry(entry)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
workId: entry.workId,
|
||||
profileId: entry.profileId,
|
||||
ownerUserId: entry.ownerUserId,
|
||||
sourceSessionId:
|
||||
'sourceSessionId' in entry && typeof entry.sourceSessionId === 'string'
|
||||
? entry.sourceSessionId
|
||||
: null,
|
||||
authorDisplayName: entry.authorDisplayName,
|
||||
levelName: entry.worldName,
|
||||
summary: entry.summaryText,
|
||||
themeTags: entry.themeTags,
|
||||
coverImageSrc: entry.coverImageSrc,
|
||||
publicationStatus: 'published',
|
||||
updatedAt: entry.updatedAt,
|
||||
publishedAt: entry.publishedAt,
|
||||
playCount: entry.playCount ?? 0,
|
||||
remixCount: entry.remixCount ?? 0,
|
||||
likeCount: entry.likeCount ?? 0,
|
||||
pointIncentiveTotalHalfPoints: 0,
|
||||
pointIncentiveClaimedPoints: 0,
|
||||
pointIncentiveTotalPoints: 0,
|
||||
pointIncentiveClaimablePoints: 0,
|
||||
publishReady: true,
|
||||
levels:
|
||||
entry.coverSlides?.map((slide, index) => ({
|
||||
levelId: slide.id || `puzzle-level-${index + 1}`,
|
||||
levelName: slide.label,
|
||||
pictureDescription: entry.summaryText,
|
||||
candidates: [],
|
||||
selectedCandidateId: null,
|
||||
coverImageSrc: slide.imageSrc,
|
||||
coverAssetId: null,
|
||||
generationStatus: 'ready' as const,
|
||||
})) ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
function mapPublicWorkDetailToBigFishWork(
|
||||
entry: PlatformPublicGalleryCard,
|
||||
): BigFishWorkSummary | null {
|
||||
if (!isBigFishGalleryEntry(entry)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const levelCount = Number.parseInt(
|
||||
entry.themeTags.find((tag) => /^\d+级$/u.test(tag))?.replace('级', '') ??
|
||||
'0',
|
||||
10,
|
||||
);
|
||||
|
||||
return {
|
||||
workId: entry.workId,
|
||||
sourceSessionId: entry.profileId,
|
||||
ownerUserId: entry.ownerUserId,
|
||||
authorDisplayName: entry.authorDisplayName,
|
||||
title: entry.worldName,
|
||||
subtitle: entry.subtitle,
|
||||
summary: entry.summaryText,
|
||||
coverImageSrc: entry.coverImageSrc,
|
||||
status: 'published',
|
||||
updatedAt: entry.updatedAt,
|
||||
publishedAt: entry.publishedAt,
|
||||
publishReady: true,
|
||||
levelCount: Number.isNaN(levelCount) ? 0 : levelCount,
|
||||
levelMainImageReadyCount: 0,
|
||||
levelMotionReadyCount: 0,
|
||||
backgroundReady: Boolean(entry.coverImageSrc),
|
||||
playCount: entry.playCount ?? 0,
|
||||
remixCount: entry.remixCount ?? 0,
|
||||
likeCount: entry.likeCount ?? 0,
|
||||
};
|
||||
}
|
||||
|
||||
function mapPublicWorkDetailToSquareHoleWork(
|
||||
entry: PlatformPublicGalleryCard,
|
||||
): SquareHoleWorkSummary | null {
|
||||
if (!isSquareHoleGalleryEntry(entry)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
workId: entry.workId,
|
||||
profileId: entry.profileId,
|
||||
ownerUserId: entry.ownerUserId,
|
||||
sourceSessionId:
|
||||
'sourceSessionId' in entry && typeof entry.sourceSessionId === 'string'
|
||||
? entry.sourceSessionId
|
||||
: null,
|
||||
gameName: entry.worldName,
|
||||
themeText: entry.themeTags[0] ?? '方洞挑战',
|
||||
twistRule: entry.subtitle,
|
||||
summary: entry.summaryText,
|
||||
tags: entry.themeTags,
|
||||
coverImageSrc: entry.coverImageSrc,
|
||||
backgroundPrompt: entry.backgroundPrompt ?? '方洞挑战运行背景',
|
||||
backgroundImageSrc: entry.backgroundImageSrc ?? null,
|
||||
shapeOptions: entry.shapeOptions ?? [],
|
||||
holeOptions: entry.holeOptions ?? [],
|
||||
shapeCount: entry.shapeCount ?? 8,
|
||||
difficulty: entry.difficulty ?? 4,
|
||||
publicationStatus: 'published',
|
||||
playCount: entry.playCount ?? 0,
|
||||
updatedAt: entry.updatedAt,
|
||||
publishedAt: entry.publishedAt,
|
||||
publishReady: true,
|
||||
};
|
||||
}
|
||||
|
||||
function buildSquareHoleProfileFromSession(
|
||||
session: SquareHoleSessionSnapshot | null,
|
||||
): SquareHoleWorkProfile | null {
|
||||
|
||||
@@ -10,6 +10,7 @@ import type {
|
||||
import type { PlatformMatch3DGalleryCard } from '../rpg-entry/rpgEntryWorldPresentation';
|
||||
import {
|
||||
buildMatch3DProfileFromSession,
|
||||
mapMatch3DWorkToPublicWorkDetail,
|
||||
mapPublicWorkDetailToMatch3DWork,
|
||||
resolveActiveMatch3DRuntimeProfile,
|
||||
resolveMatch3DRuntimeBackgroundImageSrc,
|
||||
@@ -142,6 +143,31 @@ test('Match3D runtime profile maps public detail and promotes item background as
|
||||
expect(work?.backgroundImageObjectKey).toBe('oss/background-from-item.png');
|
||||
});
|
||||
|
||||
test('Match3D runtime profile maps work summary to public detail with promoted background asset', () => {
|
||||
const backgroundAsset = buildBackgroundAsset({
|
||||
imageSrc: '/generated/match3d/detail-background.png',
|
||||
});
|
||||
const detail = mapMatch3DWorkToPublicWorkDetail(
|
||||
buildProfile({
|
||||
generatedBackgroundAsset: null,
|
||||
backgroundImageSrc: null,
|
||||
generatedItemAssets: [
|
||||
buildItemAsset({
|
||||
backgroundAsset,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
expect(detail).toMatchObject({
|
||||
sourceType: 'match3d',
|
||||
workId: 'match3d-work-1',
|
||||
profileId: 'match3d-profile-1',
|
||||
backgroundImageSrc: '/generated/match3d/detail-background.png',
|
||||
generatedBackgroundAsset: backgroundAsset,
|
||||
});
|
||||
});
|
||||
|
||||
test('Match3D runtime profile builds draft profile from session snapshot', () => {
|
||||
const backgroundAsset = buildBackgroundAsset({
|
||||
imageSrc: '/generated/match3d/draft-background.png',
|
||||
|
||||
@@ -13,9 +13,18 @@ import {
|
||||
} from '../../services/match3dGeneratedModelCache';
|
||||
import {
|
||||
isMatch3DGalleryEntry,
|
||||
mapMatch3DWorkToPlatformGalleryCard,
|
||||
type PlatformPublicGalleryCard,
|
||||
} from '../rpg-entry/rpgEntryWorldPresentation';
|
||||
|
||||
export function mapMatch3DWorkToPublicWorkDetail(
|
||||
item: Match3DWorkSummary,
|
||||
): PlatformPublicGalleryCard {
|
||||
return mapMatch3DWorkToPlatformGalleryCard(
|
||||
normalizeMatch3DWorkForRuntimeUi(item),
|
||||
);
|
||||
}
|
||||
|
||||
export function mapPublicWorkDetailToMatch3DWork(
|
||||
entry: PlatformPublicGalleryCard,
|
||||
): Match3DWorkSummary | null {
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { expect, test } from 'vitest';
|
||||
|
||||
import type { BarkBattleWorkSummary } from '../../../packages/shared/src/contracts/barkBattle';
|
||||
import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/bigFishWorkSummary';
|
||||
import type { JumpHopGalleryCardResponse } from '../../../packages/shared/src/contracts/jumpHop';
|
||||
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
|
||||
import type { CustomWorldGalleryCard } from '../../../packages/shared/src/contracts/runtime';
|
||||
import type { SquareHoleWorkSummary } from '../../../packages/shared/src/contracts/squareHoleWorks';
|
||||
import type { VisualNovelWorkSummary } from '../../../packages/shared/src/contracts/visualNovel';
|
||||
import type { WoodenFishGalleryCardResponse } from '../../../packages/shared/src/contracts/woodenFish';
|
||||
import {
|
||||
EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_ID,
|
||||
EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_NAME,
|
||||
@@ -8,6 +15,18 @@ import {
|
||||
} from '../rpg-entry/rpgEntryWorldPresentation';
|
||||
import {
|
||||
getPlatformPublicWorkDetailKind,
|
||||
mapBarkBattlePublicDetailToWorkSummary,
|
||||
mapBarkBattleWorkToPublicWorkDetail,
|
||||
mapBigFishWorkToPublicWorkDetail,
|
||||
mapJumpHopWorkToPublicWorkDetail,
|
||||
mapPublicWorkDetailToBigFishWork,
|
||||
mapPublicWorkDetailToPuzzleWork,
|
||||
mapPublicWorkDetailToSquareHoleWork,
|
||||
mapPuzzleWorkToPublicWorkDetail,
|
||||
mapRpgGalleryCardToPublicWorkDetail,
|
||||
mapSquareHoleWorkToPublicWorkDetail,
|
||||
mapVisualNovelWorkToPublicWorkDetail,
|
||||
mapWoodenFishWorkToPublicWorkDetail,
|
||||
type PlatformPublicWorkDetailKind,
|
||||
type PlatformPublicWorkDetailOpenStrategy,
|
||||
resolveActivePlatformPublicWorkAuthorEntry,
|
||||
@@ -21,10 +40,21 @@ type TypedPlatformPublicGalleryCard = Extract<
|
||||
{ sourceType: string }
|
||||
>;
|
||||
type PlatformGallerySourceType = TypedPlatformPublicGalleryCard['sourceType'];
|
||||
type TypedPlatformPublicGalleryCardOverrides = Partial<
|
||||
Omit<TypedPlatformPublicGalleryCard, 'sourceType'>
|
||||
type TypedPlatformPublicGalleryCardOverrides<
|
||||
TSourceType extends PlatformGallerySourceType,
|
||||
> = Partial<
|
||||
Omit<
|
||||
Extract<TypedPlatformPublicGalleryCard, { sourceType: TSourceType }>,
|
||||
'sourceType'
|
||||
>
|
||||
>;
|
||||
|
||||
function narrowTypedEntry<TSourceType extends PlatformGallerySourceType>(
|
||||
entry: TypedPlatformPublicGalleryCard,
|
||||
): Extract<TypedPlatformPublicGalleryCard, { sourceType: TSourceType }> {
|
||||
return entry as Extract<TypedPlatformPublicGalleryCard, { sourceType: TSourceType }>;
|
||||
}
|
||||
|
||||
function buildRpgEntry(
|
||||
overrides: Partial<CustomWorldGalleryCard> = {},
|
||||
): CustomWorldGalleryCard {
|
||||
@@ -48,10 +78,10 @@ function buildRpgEntry(
|
||||
};
|
||||
}
|
||||
|
||||
function buildTypedEntry(
|
||||
sourceType: PlatformGallerySourceType,
|
||||
overrides: TypedPlatformPublicGalleryCardOverrides = {},
|
||||
): PlatformPublicGalleryCard {
|
||||
function buildTypedEntry<TSourceType extends PlatformGallerySourceType>(
|
||||
sourceType: TSourceType,
|
||||
overrides: TypedPlatformPublicGalleryCardOverrides<TSourceType> = {},
|
||||
): Extract<TypedPlatformPublicGalleryCard, { sourceType: TSourceType }> {
|
||||
const common = {
|
||||
workId: `${sourceType}-work`,
|
||||
profileId: `${sourceType}-profile`,
|
||||
@@ -70,31 +100,30 @@ function buildTypedEntry(
|
||||
|
||||
switch (sourceType) {
|
||||
case 'puzzle':
|
||||
return { ...common, ...overrides, sourceType };
|
||||
return narrowTypedEntry<TSourceType>({ ...common, ...overrides, sourceType });
|
||||
case 'big-fish':
|
||||
return { ...common, ...overrides, sourceType };
|
||||
return narrowTypedEntry<TSourceType>({ ...common, ...overrides, sourceType });
|
||||
case 'match3d':
|
||||
return { ...common, ...overrides, sourceType };
|
||||
return narrowTypedEntry<TSourceType>({ ...common, ...overrides, sourceType });
|
||||
case 'square-hole':
|
||||
return { ...common, ...overrides, sourceType };
|
||||
return narrowTypedEntry<TSourceType>({ ...common, ...overrides, sourceType });
|
||||
case 'visual-novel':
|
||||
return { ...common, ...overrides, sourceType };
|
||||
return narrowTypedEntry<TSourceType>({ ...common, ...overrides, sourceType });
|
||||
case 'jump-hop':
|
||||
return { ...common, ...overrides, sourceType };
|
||||
return narrowTypedEntry<TSourceType>({ ...common, ...overrides, sourceType });
|
||||
case 'wooden-fish':
|
||||
return { ...common, ...overrides, sourceType };
|
||||
return narrowTypedEntry<TSourceType>({ ...common, ...overrides, sourceType });
|
||||
case 'edutainment':
|
||||
return {
|
||||
return narrowTypedEntry<TSourceType>({
|
||||
...common,
|
||||
...overrides,
|
||||
sourceType,
|
||||
templateId: EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_ID,
|
||||
templateName: EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_NAME,
|
||||
};
|
||||
case 'bark-battle':
|
||||
return {
|
||||
...common,
|
||||
...overrides,
|
||||
});
|
||||
case 'bark-battle':
|
||||
return narrowTypedEntry<TSourceType>({
|
||||
...common,
|
||||
sourceType,
|
||||
authorPublicUserCode: null,
|
||||
coverRenderMode: 'image',
|
||||
@@ -102,14 +131,190 @@ function buildTypedEntry(
|
||||
themeMode: 'martial',
|
||||
playableNpcCount: 1,
|
||||
landmarkCount: 1,
|
||||
};
|
||||
...overrides,
|
||||
});
|
||||
default: {
|
||||
const exhaustive: never = sourceType;
|
||||
return exhaustive;
|
||||
throw new Error(`Unsupported source type: ${sourceType}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildPuzzleWork(
|
||||
overrides: Partial<PuzzleWorkSummary> = {},
|
||||
): PuzzleWorkSummary {
|
||||
return {
|
||||
workId: 'puzzle-work',
|
||||
profileId: 'puzzle-profile',
|
||||
ownerUserId: 'user-1',
|
||||
sourceSessionId: 'puzzle-session',
|
||||
authorDisplayName: '玩家',
|
||||
workTitle: '拼图作品',
|
||||
workDescription: '拼图描述',
|
||||
levelName: '第一关',
|
||||
summary: '拼图摘要',
|
||||
themeTags: ['拼图'],
|
||||
coverImageSrc: '/puzzle-cover.png',
|
||||
publicationStatus: 'published',
|
||||
updatedAt: '2026-06-01T01:00:00.000Z',
|
||||
publishedAt: '2026-06-01T00:00:00.000Z',
|
||||
playCount: 3,
|
||||
remixCount: 2,
|
||||
likeCount: 1,
|
||||
publishReady: true,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function buildBigFishWork(
|
||||
overrides: Partial<BigFishWorkSummary> = {},
|
||||
): BigFishWorkSummary {
|
||||
return {
|
||||
workId: 'big-fish-work',
|
||||
sourceSessionId: 'big-fish-session',
|
||||
ownerUserId: 'user-1',
|
||||
authorDisplayName: '玩家',
|
||||
title: '大鱼作品',
|
||||
subtitle: '大鱼吃小鱼',
|
||||
summary: '大鱼摘要',
|
||||
coverImageSrc: '/big-fish-cover.png',
|
||||
status: 'published',
|
||||
updatedAt: '2026-06-01T01:00:00.000Z',
|
||||
publishedAt: '2026-06-01T00:00:00.000Z',
|
||||
publishReady: true,
|
||||
levelCount: 12,
|
||||
levelMainImageReadyCount: 0,
|
||||
levelMotionReadyCount: 0,
|
||||
backgroundReady: true,
|
||||
playCount: 4,
|
||||
remixCount: 3,
|
||||
likeCount: 2,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function buildSquareHoleWork(
|
||||
overrides: Partial<SquareHoleWorkSummary> = {},
|
||||
): SquareHoleWorkSummary {
|
||||
return {
|
||||
workId: 'square-hole-work',
|
||||
profileId: 'square-hole-profile',
|
||||
ownerUserId: 'user-1',
|
||||
sourceSessionId: 'square-hole-session',
|
||||
gameName: '方洞作品',
|
||||
themeText: '形状',
|
||||
twistRule: '反直觉',
|
||||
summary: '方洞摘要',
|
||||
tags: ['方洞'],
|
||||
coverImageSrc: '/square-hole-cover.png',
|
||||
backgroundPrompt: '方洞背景',
|
||||
backgroundImageSrc: '/square-hole-bg.png',
|
||||
shapeOptions: [],
|
||||
holeOptions: [],
|
||||
shapeCount: 8,
|
||||
difficulty: 4,
|
||||
publicationStatus: 'published',
|
||||
playCount: 5,
|
||||
updatedAt: '2026-06-01T01:00:00.000Z',
|
||||
publishedAt: '2026-06-01T00:00:00.000Z',
|
||||
publishReady: true,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function buildVisualNovelWork(
|
||||
overrides: Partial<VisualNovelWorkSummary> = {},
|
||||
): VisualNovelWorkSummary {
|
||||
return {
|
||||
runtimeKind: 'visual-novel',
|
||||
profileId: 'visual-novel-profile',
|
||||
ownerUserId: 'user-1',
|
||||
title: '视觉小说作品',
|
||||
description: '视觉小说摘要',
|
||||
coverImageSrc: '/visual-novel-cover.png',
|
||||
tags: ['视觉小说'],
|
||||
publishStatus: 'published',
|
||||
publishReady: true,
|
||||
playCount: 6,
|
||||
updatedAt: '2026-06-01T01:00:00.000Z',
|
||||
publishedAt: '2026-06-01T00:00:00.000Z',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function buildJumpHopGalleryCard(
|
||||
overrides: Partial<JumpHopGalleryCardResponse> = {},
|
||||
): JumpHopGalleryCardResponse {
|
||||
return {
|
||||
publicWorkCode: 'JH-0001',
|
||||
workId: 'jump-hop-work',
|
||||
profileId: 'jump-hop-profile',
|
||||
ownerUserId: 'user-1',
|
||||
authorDisplayName: '玩家',
|
||||
workTitle: '跳一跳作品',
|
||||
workDescription: '跳一跳摘要',
|
||||
coverImageSrc: '/jump-hop-cover.png',
|
||||
themeTags: ['跳一跳'],
|
||||
difficulty: 'standard',
|
||||
stylePreset: 'paper-toy',
|
||||
publicationStatus: 'published',
|
||||
playCount: 7,
|
||||
updatedAt: '2026-06-01T01:00:00.000Z',
|
||||
publishedAt: '2026-06-01T00:00:00.000Z',
|
||||
generationStatus: 'ready',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function buildWoodenFishGalleryCard(
|
||||
overrides: Partial<WoodenFishGalleryCardResponse> = {},
|
||||
): WoodenFishGalleryCardResponse {
|
||||
return {
|
||||
publicWorkCode: 'WF-0001',
|
||||
workId: 'wooden-fish-work',
|
||||
profileId: 'wooden-fish-profile',
|
||||
ownerUserId: 'user-1',
|
||||
authorDisplayName: '玩家',
|
||||
workTitle: '木鱼作品',
|
||||
workDescription: '木鱼摘要',
|
||||
coverImageSrc: '/wooden-fish-cover.png',
|
||||
themeTags: ['敲木鱼'],
|
||||
publicationStatus: 'published',
|
||||
playCount: 8,
|
||||
updatedAt: '2026-06-01T01:00:00.000Z',
|
||||
publishedAt: '2026-06-01T00:00:00.000Z',
|
||||
generationStatus: 'ready',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function buildBarkBattleWork(
|
||||
overrides: Partial<BarkBattleWorkSummary> = {},
|
||||
): BarkBattleWorkSummary {
|
||||
return {
|
||||
workId: 'bark-battle-work',
|
||||
draftId: 'bark-battle-draft',
|
||||
ownerUserId: 'user-1',
|
||||
authorDisplayName: '玩家',
|
||||
title: '汪汪声浪作品',
|
||||
summary: '汪汪摘要',
|
||||
themeDescription: '森林擂台',
|
||||
playerImageDescription: '小狗',
|
||||
opponentImageDescription: '对手',
|
||||
playerCharacterImageSrc: '/player.png',
|
||||
opponentCharacterImageSrc: '/opponent.png',
|
||||
uiBackgroundImageSrc: '/bark-bg.png',
|
||||
difficultyPreset: 'normal',
|
||||
status: 'published',
|
||||
generationStatus: 'ready',
|
||||
publishReady: true,
|
||||
playCount: 9,
|
||||
recentPlayCount7d: 2,
|
||||
updatedAt: '2026-06-01T01:00:00.000Z',
|
||||
publishedAt: '2026-06-01T00:00:00.000Z',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
test('platform public work detail flow resolves detail kind for every play kind', () => {
|
||||
const cases: Array<
|
||||
[sourceType: PlatformGallerySourceType, kind: PlatformPublicWorkDetailKind]
|
||||
@@ -221,6 +426,173 @@ test('platform public work detail flow resolves open strategy', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('platform public work detail flow maps work summaries to detail entries', () => {
|
||||
const rpgEntry = buildRpgEntry();
|
||||
|
||||
expect(mapRpgGalleryCardToPublicWorkDetail(rpgEntry)).toBe(rpgEntry);
|
||||
expect(mapPuzzleWorkToPublicWorkDetail(buildPuzzleWork())).toMatchObject({
|
||||
sourceType: 'puzzle',
|
||||
workId: 'puzzle-work',
|
||||
profileId: 'puzzle-profile',
|
||||
playCount: 3,
|
||||
remixCount: 2,
|
||||
likeCount: 1,
|
||||
});
|
||||
expect(mapBigFishWorkToPublicWorkDetail(buildBigFishWork())).toMatchObject({
|
||||
sourceType: 'big-fish',
|
||||
workId: 'big-fish-work',
|
||||
profileId: 'big-fish-session',
|
||||
playCount: 4,
|
||||
});
|
||||
expect(
|
||||
mapSquareHoleWorkToPublicWorkDetail(buildSquareHoleWork()),
|
||||
).toMatchObject({
|
||||
sourceType: 'square-hole',
|
||||
workId: 'square-hole-work',
|
||||
profileId: 'square-hole-profile',
|
||||
backgroundPrompt: '方洞背景',
|
||||
});
|
||||
expect(
|
||||
mapVisualNovelWorkToPublicWorkDetail(buildVisualNovelWork()),
|
||||
).toMatchObject({
|
||||
sourceType: 'visual-novel',
|
||||
workId: 'visual-novel-profile',
|
||||
profileId: 'visual-novel-profile',
|
||||
playCount: 6,
|
||||
});
|
||||
expect(
|
||||
mapJumpHopWorkToPublicWorkDetail(buildJumpHopGalleryCard()),
|
||||
).toMatchObject({
|
||||
sourceType: 'jump-hop',
|
||||
workId: 'jump-hop-work',
|
||||
profileId: 'jump-hop-profile',
|
||||
publicWorkCode: 'JH-0001',
|
||||
});
|
||||
expect(
|
||||
mapWoodenFishWorkToPublicWorkDetail(buildWoodenFishGalleryCard()),
|
||||
).toMatchObject({
|
||||
sourceType: 'wooden-fish',
|
||||
workId: 'wooden-fish-work',
|
||||
profileId: 'wooden-fish-profile',
|
||||
publicWorkCode: 'WF-0001',
|
||||
});
|
||||
expect(
|
||||
mapBarkBattleWorkToPublicWorkDetail(buildBarkBattleWork()),
|
||||
).toMatchObject({
|
||||
sourceType: 'bark-battle',
|
||||
workId: 'bark-battle-work',
|
||||
sourceSessionId: 'bark-battle-draft',
|
||||
coverRenderMode: 'scene_with_roles',
|
||||
coverCharacterImageSrcs: ['/player.png', '/opponent.png'],
|
||||
});
|
||||
});
|
||||
|
||||
test('platform public work detail flow maps detail entries back to work summaries', () => {
|
||||
expect(
|
||||
mapPublicWorkDetailToPuzzleWork({
|
||||
...buildTypedEntry('puzzle', {
|
||||
coverSlides: [
|
||||
{
|
||||
id: 'level-1',
|
||||
imageSrc: '/level-1.png',
|
||||
label: '第一关',
|
||||
},
|
||||
],
|
||||
playCount: 10,
|
||||
remixCount: 4,
|
||||
likeCount: 3,
|
||||
}),
|
||||
sourceSessionId: 'puzzle-session',
|
||||
}),
|
||||
).toMatchObject({
|
||||
workId: 'puzzle-work',
|
||||
profileId: 'puzzle-profile',
|
||||
sourceSessionId: 'puzzle-session',
|
||||
playCount: 10,
|
||||
remixCount: 4,
|
||||
likeCount: 3,
|
||||
pointIncentiveTotalPoints: 0,
|
||||
levels: [
|
||||
{
|
||||
levelId: 'level-1',
|
||||
levelName: '第一关',
|
||||
coverImageSrc: '/level-1.png',
|
||||
generationStatus: 'ready',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(
|
||||
mapPublicWorkDetailToBigFishWork(
|
||||
buildTypedEntry('big-fish', {
|
||||
themeTags: ['大鱼', '12级'],
|
||||
coverImageSrc: '/big-fish-cover.png',
|
||||
}),
|
||||
),
|
||||
).toMatchObject({
|
||||
workId: 'big-fish-work',
|
||||
sourceSessionId: 'big-fish-profile',
|
||||
levelCount: 12,
|
||||
backgroundReady: true,
|
||||
});
|
||||
expect(
|
||||
mapPublicWorkDetailToBigFishWork(
|
||||
buildTypedEntry('big-fish', { themeTags: ['大鱼'] }),
|
||||
)?.levelCount,
|
||||
).toBe(0);
|
||||
|
||||
expect(
|
||||
mapPublicWorkDetailToSquareHoleWork(
|
||||
buildTypedEntry('square-hole', { themeTags: [] }),
|
||||
),
|
||||
).toMatchObject({
|
||||
workId: 'square-hole-work',
|
||||
profileId: 'square-hole-profile',
|
||||
themeText: '方洞挑战',
|
||||
backgroundPrompt: '方洞挑战运行背景',
|
||||
shapeOptions: [],
|
||||
holeOptions: [],
|
||||
shapeCount: 8,
|
||||
difficulty: 4,
|
||||
});
|
||||
|
||||
expect(
|
||||
mapBarkBattlePublicDetailToWorkSummary(
|
||||
{
|
||||
...buildTypedEntry('bark-battle', {
|
||||
themeTags: ['森林', '小狗', '对手'],
|
||||
coverImageSrc: '/bark-bg.png',
|
||||
coverCharacterImageSrcs: ['/player.png', '/opponent.png'],
|
||||
playCount: 11,
|
||||
recentPlayCount7d: 5,
|
||||
}),
|
||||
sourceSessionId: 'bark-draft',
|
||||
},
|
||||
),
|
||||
).toMatchObject({
|
||||
workId: 'bark-battle-work',
|
||||
draftId: 'bark-draft',
|
||||
themeDescription: '森林',
|
||||
playerImageDescription: '小狗',
|
||||
opponentImageDescription: '对手',
|
||||
playerCharacterImageSrc: '/player.png',
|
||||
opponentCharacterImageSrc: '/opponent.png',
|
||||
uiBackgroundImageSrc: '/bark-bg.png',
|
||||
difficultyPreset: 'normal',
|
||||
playCount: 11,
|
||||
recentPlayCount7d: 5,
|
||||
});
|
||||
|
||||
expect(mapPublicWorkDetailToPuzzleWork(buildTypedEntry('big-fish'))).toBeNull();
|
||||
expect(mapPublicWorkDetailToBigFishWork(buildTypedEntry('puzzle'))).toBeNull();
|
||||
expect(
|
||||
mapPublicWorkDetailToSquareHoleWork(buildTypedEntry('puzzle')),
|
||||
).toBeNull();
|
||||
expect(
|
||||
mapBarkBattlePublicDetailToWorkSummary(buildTypedEntry('puzzle')),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
test('platform public work detail flow resolves edit mode only for owned works', () => {
|
||||
const entry = buildTypedEntry('puzzle');
|
||||
|
||||
|
||||
@@ -1,4 +1,17 @@
|
||||
import type { BarkBattleWorkSummary } from '../../../packages/shared/src/contracts/barkBattle';
|
||||
import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/bigFishWorkSummary';
|
||||
import type {
|
||||
JumpHopGalleryCardResponse,
|
||||
JumpHopWorkProfileResponse,
|
||||
} from '../../../packages/shared/src/contracts/jumpHop';
|
||||
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
|
||||
import type { CustomWorldGalleryCard } from '../../../packages/shared/src/contracts/runtime';
|
||||
import type { SquareHoleWorkSummary } from '../../../packages/shared/src/contracts/squareHoleWorks';
|
||||
import type { VisualNovelWorkSummary } from '../../../packages/shared/src/contracts/visualNovel';
|
||||
import type {
|
||||
WoodenFishGalleryCardResponse,
|
||||
WoodenFishWorkProfileResponse,
|
||||
} from '../../../packages/shared/src/contracts/woodenFish';
|
||||
import { buildPublicWorkStagePath } from '../../routing/appPageRoutes';
|
||||
import {
|
||||
isBarkBattleGalleryEntry,
|
||||
@@ -10,6 +23,13 @@ import {
|
||||
isSquareHoleGalleryEntry,
|
||||
isVisualNovelGalleryEntry,
|
||||
isWoodenFishGalleryEntry,
|
||||
mapBarkBattleWorkToPlatformGalleryCard,
|
||||
mapBigFishWorkToPlatformGalleryCard,
|
||||
mapJumpHopWorkToPlatformGalleryCard,
|
||||
mapPuzzleWorkToPlatformGalleryCard,
|
||||
mapSquareHoleWorkToPlatformGalleryCard,
|
||||
mapVisualNovelWorkToPlatformGalleryCard,
|
||||
mapWoodenFishWorkToPlatformGalleryCard,
|
||||
type PlatformPublicGalleryCard,
|
||||
} from '../rpg-entry/rpgEntryWorldPresentation';
|
||||
import {
|
||||
@@ -94,6 +114,202 @@ export function isRpgPublicWorkDetailEntry(
|
||||
return !('sourceType' in entry);
|
||||
}
|
||||
|
||||
export function mapRpgGalleryCardToPublicWorkDetail(
|
||||
entry: CustomWorldGalleryCard,
|
||||
): PlatformPublicGalleryCard {
|
||||
return entry;
|
||||
}
|
||||
|
||||
export function mapPuzzleWorkToPublicWorkDetail(
|
||||
item: PuzzleWorkSummary,
|
||||
): PlatformPublicGalleryCard {
|
||||
return mapPuzzleWorkToPlatformGalleryCard(item);
|
||||
}
|
||||
|
||||
export function mapSquareHoleWorkToPublicWorkDetail(
|
||||
item: SquareHoleWorkSummary,
|
||||
): PlatformPublicGalleryCard {
|
||||
return mapSquareHoleWorkToPlatformGalleryCard(item);
|
||||
}
|
||||
|
||||
export function mapBigFishWorkToPublicWorkDetail(
|
||||
item: BigFishWorkSummary,
|
||||
): PlatformPublicGalleryCard {
|
||||
return mapBigFishWorkToPlatformGalleryCard(item);
|
||||
}
|
||||
|
||||
export function mapVisualNovelWorkToPublicWorkDetail(
|
||||
item: VisualNovelWorkSummary,
|
||||
): PlatformPublicGalleryCard {
|
||||
return mapVisualNovelWorkToPlatformGalleryCard(item);
|
||||
}
|
||||
|
||||
export function mapJumpHopWorkToPublicWorkDetail(
|
||||
item: JumpHopGalleryCardResponse | JumpHopWorkProfileResponse,
|
||||
): PlatformPublicGalleryCard {
|
||||
return mapJumpHopWorkToPlatformGalleryCard(item);
|
||||
}
|
||||
|
||||
export function mapBarkBattleWorkToPublicWorkDetail(
|
||||
item: BarkBattleWorkSummary,
|
||||
): PlatformPublicGalleryCard {
|
||||
return mapBarkBattleWorkToPlatformGalleryCard(item);
|
||||
}
|
||||
|
||||
export function mapWoodenFishWorkToPublicWorkDetail(
|
||||
item: WoodenFishGalleryCardResponse | WoodenFishWorkProfileResponse,
|
||||
): PlatformPublicGalleryCard {
|
||||
return mapWoodenFishWorkToPlatformGalleryCard(item);
|
||||
}
|
||||
|
||||
export function mapBarkBattlePublicDetailToWorkSummary(
|
||||
entry: PlatformPublicGalleryCard,
|
||||
): BarkBattleWorkSummary | null {
|
||||
if (!isBarkBattleGalleryEntry(entry)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
workId: entry.workId,
|
||||
draftId: entry.sourceSessionId ?? null,
|
||||
ownerUserId: entry.ownerUserId,
|
||||
authorDisplayName: entry.authorDisplayName,
|
||||
title: entry.worldName,
|
||||
summary: entry.summaryText,
|
||||
themeDescription: entry.themeTags[0] ?? entry.summaryText,
|
||||
playerImageDescription: entry.themeTags[1] ?? entry.summaryText,
|
||||
opponentImageDescription: entry.themeTags[2] ?? entry.summaryText,
|
||||
onomatopoeia: undefined,
|
||||
playerCharacterImageSrc: entry.coverCharacterImageSrcs[0] ?? null,
|
||||
opponentCharacterImageSrc: entry.coverCharacterImageSrcs[1] ?? null,
|
||||
uiBackgroundImageSrc: entry.coverImageSrc,
|
||||
difficultyPreset: 'normal',
|
||||
status: 'published',
|
||||
generationStatus: 'ready',
|
||||
publishReady: true,
|
||||
playCount: entry.playCount ?? 0,
|
||||
recentPlayCount7d: entry.recentPlayCount7d ?? 0,
|
||||
updatedAt: entry.updatedAt,
|
||||
publishedAt: entry.publishedAt,
|
||||
};
|
||||
}
|
||||
|
||||
export function mapPublicWorkDetailToPuzzleWork(
|
||||
entry: PlatformPublicGalleryCard,
|
||||
): PuzzleWorkSummary | null {
|
||||
if (!isPuzzleGalleryEntry(entry)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
workId: entry.workId,
|
||||
profileId: entry.profileId,
|
||||
ownerUserId: entry.ownerUserId,
|
||||
sourceSessionId:
|
||||
'sourceSessionId' in entry && typeof entry.sourceSessionId === 'string'
|
||||
? entry.sourceSessionId
|
||||
: null,
|
||||
authorDisplayName: entry.authorDisplayName,
|
||||
levelName: entry.worldName,
|
||||
summary: entry.summaryText,
|
||||
themeTags: entry.themeTags,
|
||||
coverImageSrc: entry.coverImageSrc,
|
||||
publicationStatus: 'published',
|
||||
updatedAt: entry.updatedAt,
|
||||
publishedAt: entry.publishedAt,
|
||||
playCount: entry.playCount ?? 0,
|
||||
remixCount: entry.remixCount ?? 0,
|
||||
likeCount: entry.likeCount ?? 0,
|
||||
pointIncentiveTotalHalfPoints: 0,
|
||||
pointIncentiveClaimedPoints: 0,
|
||||
pointIncentiveTotalPoints: 0,
|
||||
pointIncentiveClaimablePoints: 0,
|
||||
publishReady: true,
|
||||
levels:
|
||||
entry.coverSlides?.map((slide, index) => ({
|
||||
levelId: slide.id || `puzzle-level-${index + 1}`,
|
||||
levelName: slide.label,
|
||||
pictureDescription: entry.summaryText,
|
||||
candidates: [],
|
||||
selectedCandidateId: null,
|
||||
coverImageSrc: slide.imageSrc,
|
||||
coverAssetId: null,
|
||||
generationStatus: 'ready' as const,
|
||||
})) ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
export function mapPublicWorkDetailToBigFishWork(
|
||||
entry: PlatformPublicGalleryCard,
|
||||
): BigFishWorkSummary | null {
|
||||
if (!isBigFishGalleryEntry(entry)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const levelCount = Number.parseInt(
|
||||
entry.themeTags.find((tag) => /^\d+级$/u.test(tag))?.replace('级', '') ??
|
||||
'0',
|
||||
10,
|
||||
);
|
||||
|
||||
return {
|
||||
workId: entry.workId,
|
||||
sourceSessionId: entry.profileId,
|
||||
ownerUserId: entry.ownerUserId,
|
||||
authorDisplayName: entry.authorDisplayName,
|
||||
title: entry.worldName,
|
||||
subtitle: entry.subtitle,
|
||||
summary: entry.summaryText,
|
||||
coverImageSrc: entry.coverImageSrc,
|
||||
status: 'published',
|
||||
updatedAt: entry.updatedAt,
|
||||
publishedAt: entry.publishedAt,
|
||||
publishReady: true,
|
||||
levelCount: Number.isNaN(levelCount) ? 0 : levelCount,
|
||||
levelMainImageReadyCount: 0,
|
||||
levelMotionReadyCount: 0,
|
||||
backgroundReady: Boolean(entry.coverImageSrc),
|
||||
playCount: entry.playCount ?? 0,
|
||||
remixCount: entry.remixCount ?? 0,
|
||||
likeCount: entry.likeCount ?? 0,
|
||||
};
|
||||
}
|
||||
|
||||
export function mapPublicWorkDetailToSquareHoleWork(
|
||||
entry: PlatformPublicGalleryCard,
|
||||
): SquareHoleWorkSummary | null {
|
||||
if (!isSquareHoleGalleryEntry(entry)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
workId: entry.workId,
|
||||
profileId: entry.profileId,
|
||||
ownerUserId: entry.ownerUserId,
|
||||
sourceSessionId:
|
||||
'sourceSessionId' in entry && typeof entry.sourceSessionId === 'string'
|
||||
? entry.sourceSessionId
|
||||
: null,
|
||||
gameName: entry.worldName,
|
||||
themeText: entry.themeTags[0] ?? '方洞挑战',
|
||||
twistRule: entry.subtitle,
|
||||
summary: entry.summaryText,
|
||||
tags: entry.themeTags,
|
||||
coverImageSrc: entry.coverImageSrc,
|
||||
backgroundPrompt: entry.backgroundPrompt ?? '方洞挑战运行背景',
|
||||
backgroundImageSrc: entry.backgroundImageSrc ?? null,
|
||||
shapeOptions: entry.shapeOptions ?? [],
|
||||
holeOptions: entry.holeOptions ?? [],
|
||||
shapeCount: entry.shapeCount ?? 8,
|
||||
difficulty: entry.difficulty ?? 4,
|
||||
publicationStatus: 'published',
|
||||
playCount: entry.playCount ?? 0,
|
||||
updatedAt: entry.updatedAt,
|
||||
publishedAt: entry.publishedAt,
|
||||
publishReady: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function getPlatformPublicWorkDetailKind(
|
||||
entry: PlatformPublicGalleryCard,
|
||||
): PlatformPublicWorkDetailKind {
|
||||
|
||||
Reference in New Issue
Block a user