合入 master 最新的认证、玩法契约与推荐页改动。 修复拼图草稿生成、推荐页下一关和公开详情访客试玩回归。 修复抓大鹅草稿试玩鉴权与首屏推荐详情测试入口。 补齐相关测试夹具、文档与团队记忆更新。
1202 lines
30 KiB
TypeScript
1202 lines
30 KiB
TypeScript
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 { Match3DWorkSummary } from '../../../packages/shared/src/contracts/match3dWorks';
|
||
import type { PuzzleRunSnapshot } from '../../../packages/shared/src/contracts/puzzleRuntimeSession';
|
||
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
|
||
import type {
|
||
PuzzleClearGalleryCardResponse,
|
||
PuzzleClearWorkProfileResponse,
|
||
} from '../../../packages/shared/src/contracts/puzzleClear';
|
||
import type {
|
||
CustomWorldGalleryCard,
|
||
CustomWorldLibraryEntry,
|
||
} 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 type { CustomWorldProfile } from '../../types';
|
||
import {
|
||
isBarkBattleGalleryEntry,
|
||
isBigFishGalleryEntry,
|
||
isEdutainmentGalleryEntry,
|
||
isJumpHopGalleryEntry,
|
||
isMatch3DGalleryEntry,
|
||
isPuzzleClearGalleryEntry,
|
||
isPuzzleGalleryEntry,
|
||
isSquareHoleGalleryEntry,
|
||
isVisualNovelGalleryEntry,
|
||
isWoodenFishGalleryEntry,
|
||
mapBarkBattleWorkToPlatformGalleryCard,
|
||
mapBigFishWorkToPlatformGalleryCard,
|
||
mapJumpHopWorkToPlatformGalleryCard,
|
||
mapPuzzleClearWorkToPlatformGalleryCard,
|
||
mapPuzzleWorkToPlatformGalleryCard,
|
||
mapSquareHoleWorkToPlatformGalleryCard,
|
||
mapVisualNovelWorkToPlatformGalleryCard,
|
||
mapWoodenFishWorkToPlatformGalleryCard,
|
||
type PlatformPublicGalleryCard,
|
||
} from '../rpg-entry/rpgEntryWorldPresentation';
|
||
import {
|
||
canExposePublicWork,
|
||
EDUTAINMENT_HIDDEN_MESSAGE,
|
||
} from './platformEdutainmentVisibility';
|
||
|
||
export type PlatformPublicWorkDetailKind =
|
||
| 'bark-battle'
|
||
| 'big-fish'
|
||
| 'edutainment'
|
||
| 'jump-hop'
|
||
| 'match3d'
|
||
| 'puzzle'
|
||
| 'puzzle-clear'
|
||
| 'rpg'
|
||
| 'square-hole'
|
||
| 'visual-novel'
|
||
| 'wooden-fish';
|
||
|
||
export type PlatformRpgPublicWorkDetailEntry =
|
||
| CustomWorldGalleryCard
|
||
| CustomWorldLibraryEntry<CustomWorldProfile>;
|
||
|
||
export type PlatformPublicWorkDetailOpenStrategy =
|
||
| {
|
||
type: 'use-entry';
|
||
kind: Exclude<
|
||
PlatformPublicWorkDetailKind,
|
||
'jump-hop' | 'puzzle' | 'rpg' | 'visual-novel' | 'wooden-fish'
|
||
>;
|
||
}
|
||
| {
|
||
type: 'load-puzzle-detail';
|
||
profileId: string;
|
||
}
|
||
| {
|
||
type: 'load-jump-hop-detail';
|
||
profileId: string;
|
||
}
|
||
| {
|
||
type: 'load-wooden-fish-detail';
|
||
profileId: string;
|
||
}
|
||
| {
|
||
type: 'load-visual-novel-detail';
|
||
profileId: string;
|
||
}
|
||
| {
|
||
type: 'load-rpg-detail';
|
||
entry: PlatformRpgPublicWorkDetailEntry;
|
||
};
|
||
|
||
export type PlatformPublicWorkActionMode = 'edit' | 'remix';
|
||
|
||
export type PlatformPublicWorkLikeIntent =
|
||
| {
|
||
type: 'like-big-fish';
|
||
profileId: string;
|
||
}
|
||
| {
|
||
type: 'like-puzzle';
|
||
profileId: string;
|
||
}
|
||
| {
|
||
type: 'like-rpg-gallery';
|
||
ownerUserId: string;
|
||
profileId: string;
|
||
}
|
||
| {
|
||
type: 'unsupported';
|
||
errorMessage: string;
|
||
};
|
||
|
||
export type PlatformPublicWorkRemixIntent =
|
||
| {
|
||
type: 'remix-big-fish';
|
||
profileId: string;
|
||
selectionStage: 'big-fish-result';
|
||
}
|
||
| {
|
||
type: 'remix-puzzle';
|
||
profileId: string;
|
||
selectionStage: 'puzzle-result';
|
||
}
|
||
| {
|
||
type: 'remix-rpg-gallery';
|
||
ownerUserId: string;
|
||
profileId: string;
|
||
}
|
||
| {
|
||
type: 'unsupported';
|
||
errorMessage: string;
|
||
};
|
||
|
||
export type PlatformPublicWorkEditIntent =
|
||
| {
|
||
type: 'blocked';
|
||
errorMessage: string;
|
||
}
|
||
| {
|
||
type: 'edit-big-fish';
|
||
work: BigFishWorkSummary;
|
||
}
|
||
| {
|
||
type: 'edit-puzzle';
|
||
work: PuzzleWorkSummary;
|
||
}
|
||
| {
|
||
type: 'edit-match3d';
|
||
work: Match3DWorkSummary;
|
||
forceDraft: true;
|
||
}
|
||
| {
|
||
type: 'edit-square-hole';
|
||
work: SquareHoleWorkSummary;
|
||
forceDraft: true;
|
||
}
|
||
| {
|
||
type: 'edit-visual-novel';
|
||
work: VisualNovelWorkSummary;
|
||
forceDraft: true;
|
||
}
|
||
| {
|
||
type: 'resolve-edutainment-draft';
|
||
entry: PlatformPublicGalleryCard;
|
||
}
|
||
| {
|
||
type: 'edit-bark-battle';
|
||
work: BarkBattleWorkSummary;
|
||
forceDraft: true;
|
||
}
|
||
| {
|
||
type: 'edit-rpg-gallery';
|
||
entry: CustomWorldLibraryEntry<CustomWorldProfile>;
|
||
};
|
||
|
||
export type PlatformPublicWorkEditIntentDeps = {
|
||
selectedPuzzleDetail?: PuzzleWorkSummary | null;
|
||
selectedRpgDetailEntry?: PlatformRpgPublicWorkDetailEntry | null;
|
||
visualNovelWorks?: readonly VisualNovelWorkSummary[];
|
||
barkBattleGalleryEntries?: readonly BarkBattleWorkSummary[];
|
||
barkBattleWorks?: readonly BarkBattleWorkSummary[];
|
||
mapMatch3DWork: (
|
||
entry: PlatformPublicGalleryCard,
|
||
) => Match3DWorkSummary | null;
|
||
};
|
||
|
||
export type PlatformPublicWorkStartIntent =
|
||
| {
|
||
type: 'blocked';
|
||
errorMessage: string;
|
||
}
|
||
| {
|
||
type: 'start-big-fish';
|
||
work: BigFishWorkSummary;
|
||
returnStage: 'work-detail';
|
||
}
|
||
| {
|
||
type: 'start-puzzle';
|
||
work: PuzzleWorkSummary;
|
||
returnStage: 'work-detail';
|
||
authMode: 'isolated';
|
||
}
|
||
| {
|
||
type: 'start-puzzle-clear';
|
||
profileId: string;
|
||
returnStage: 'work-detail';
|
||
}
|
||
| {
|
||
type: 'start-jump-hop';
|
||
profileId: string;
|
||
returnStage: 'work-detail';
|
||
}
|
||
| {
|
||
type: 'start-wooden-fish';
|
||
profileId: string;
|
||
returnStage: 'work-detail';
|
||
}
|
||
| {
|
||
type: 'start-match3d';
|
||
work: Match3DWorkSummary;
|
||
returnStage: 'work-detail';
|
||
}
|
||
| {
|
||
type: 'start-square-hole';
|
||
work: SquareHoleWorkSummary;
|
||
returnStage: 'work-detail';
|
||
}
|
||
| {
|
||
type: 'start-visual-novel';
|
||
profileId: string;
|
||
returnStage: 'work-detail';
|
||
}
|
||
| {
|
||
type: 'start-bark-battle';
|
||
work: BarkBattleWorkSummary;
|
||
returnStage: 'work-detail';
|
||
}
|
||
| {
|
||
type: 'start-edutainment';
|
||
entry: PlatformPublicGalleryCard;
|
||
returnStage: 'work-detail';
|
||
}
|
||
| {
|
||
type: 'record-rpg-gallery-play';
|
||
entry: PlatformRpgPublicWorkDetailEntry;
|
||
};
|
||
|
||
export type PlatformPublicWorkStartIntentDeps = {
|
||
selectedPuzzleDetail?: PuzzleWorkSummary | null;
|
||
selectedRpgDetailEntry?: PlatformRpgPublicWorkDetailEntry | null;
|
||
barkBattleGalleryEntries?: readonly BarkBattleWorkSummary[];
|
||
barkBattleWorks?: readonly BarkBattleWorkSummary[];
|
||
mapMatch3DWork: (
|
||
entry: PlatformPublicGalleryCard,
|
||
) => Match3DWorkSummary | null;
|
||
};
|
||
|
||
export type PlatformPublicWorkDetailOpenDecision =
|
||
| {
|
||
type: 'blocked';
|
||
selectedDetail: null;
|
||
errorMessage: string;
|
||
selectionStage: 'platform';
|
||
historyPath: null;
|
||
}
|
||
| {
|
||
type: 'open';
|
||
selectedDetail: PlatformPublicGalleryCard;
|
||
errorMessage: null;
|
||
selectionStage: 'work-detail';
|
||
historyPath: string | null;
|
||
};
|
||
|
||
export type PlatformPublicWorkDetailOpenDecisionDeps = {
|
||
canExposeEntry?: (entry: PlatformPublicGalleryCard) => boolean;
|
||
hiddenMessage?: string;
|
||
buildWorkDetailPath?: (publicWorkCode: string) => string;
|
||
};
|
||
|
||
export type ActivePlatformPublicWorkAuthorEntryInput = {
|
||
selectionStage: string;
|
||
selectedPublicWorkDetail: PlatformPublicGalleryCard | null;
|
||
selectedRpgDetailEntry: PlatformRpgPublicWorkDetailEntry | null;
|
||
};
|
||
|
||
export function isRpgPublicWorkDetailEntry(
|
||
entry: PlatformPublicGalleryCard,
|
||
): entry is PlatformRpgPublicWorkDetailEntry {
|
||
return !('sourceType' in entry);
|
||
}
|
||
|
||
export function mapRpgGalleryCardToPublicWorkDetail(
|
||
entry: PlatformRpgPublicWorkDetailEntry,
|
||
): CustomWorldGalleryCard {
|
||
return {
|
||
ownerUserId: entry.ownerUserId,
|
||
profileId: entry.profileId,
|
||
publicWorkCode: entry.publicWorkCode,
|
||
authorPublicUserCode: entry.authorPublicUserCode,
|
||
visibility: entry.visibility,
|
||
publishedAt: entry.publishedAt,
|
||
updatedAt: entry.updatedAt,
|
||
authorDisplayName: entry.authorDisplayName,
|
||
worldName: entry.worldName,
|
||
subtitle: entry.subtitle,
|
||
summaryText: entry.summaryText,
|
||
coverImageSrc: entry.coverImageSrc,
|
||
themeMode: entry.themeMode,
|
||
playableNpcCount: entry.playableNpcCount,
|
||
landmarkCount: entry.landmarkCount,
|
||
playCount: entry.playCount ?? 0,
|
||
remixCount: entry.remixCount ?? 0,
|
||
likeCount: entry.likeCount ?? 0,
|
||
recentPlayCount7d: entry.recentPlayCount7d ?? 0,
|
||
};
|
||
}
|
||
|
||
function isRpgPublicWorkLibraryEntry(
|
||
entry: PlatformRpgPublicWorkDetailEntry | null | undefined,
|
||
): entry is CustomWorldLibraryEntry<CustomWorldProfile> {
|
||
return Boolean(entry && 'profile' in 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 mapPuzzleClearWorkToPublicWorkDetail(
|
||
item: PuzzleClearGalleryCardResponse | PuzzleClearWorkProfileResponse,
|
||
): PlatformPublicGalleryCard {
|
||
return mapPuzzleClearWorkToPlatformGalleryCard(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 resolveVisiblePuzzleDetailCoverCount(
|
||
entry: PlatformPublicGalleryCard | null,
|
||
run: PuzzleRunSnapshot | null,
|
||
) {
|
||
if (!entry || !isPuzzleGalleryEntry(entry)) {
|
||
return 1;
|
||
}
|
||
|
||
if (run?.entryProfileId !== entry.profileId) {
|
||
return 1;
|
||
}
|
||
|
||
// 中文注释:封面首图永远公开,后续封面跟随当前玩家本次 run 的通关进度即时解锁。
|
||
return Math.max(1, run.clearedLevelCount + 1);
|
||
}
|
||
|
||
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 {
|
||
if (isBigFishGalleryEntry(entry)) {
|
||
return 'big-fish';
|
||
}
|
||
|
||
if (isPuzzleGalleryEntry(entry)) {
|
||
return 'puzzle';
|
||
}
|
||
|
||
if (isPuzzleClearGalleryEntry(entry)) {
|
||
return 'puzzle-clear';
|
||
}
|
||
|
||
if (isJumpHopGalleryEntry(entry)) {
|
||
return 'jump-hop';
|
||
}
|
||
|
||
if (isWoodenFishGalleryEntry(entry)) {
|
||
return 'wooden-fish';
|
||
}
|
||
|
||
if (isMatch3DGalleryEntry(entry)) {
|
||
return 'match3d';
|
||
}
|
||
|
||
if (isSquareHoleGalleryEntry(entry)) {
|
||
return 'square-hole';
|
||
}
|
||
|
||
if (isVisualNovelGalleryEntry(entry)) {
|
||
return 'visual-novel';
|
||
}
|
||
|
||
if (isBarkBattleGalleryEntry(entry)) {
|
||
return 'bark-battle';
|
||
}
|
||
|
||
if (isEdutainmentGalleryEntry(entry)) {
|
||
return 'edutainment';
|
||
}
|
||
|
||
return 'rpg';
|
||
}
|
||
|
||
export function resolvePlatformPublicWorkDetailOpenStrategy(
|
||
entry: PlatformPublicGalleryCard,
|
||
): PlatformPublicWorkDetailOpenStrategy {
|
||
if (isBigFishGalleryEntry(entry)) {
|
||
return {
|
||
type: 'use-entry',
|
||
kind: 'big-fish',
|
||
};
|
||
}
|
||
|
||
if (isPuzzleGalleryEntry(entry)) {
|
||
return {
|
||
type: 'load-puzzle-detail',
|
||
profileId: entry.profileId,
|
||
};
|
||
}
|
||
|
||
if (isPuzzleClearGalleryEntry(entry)) {
|
||
return {
|
||
type: 'use-entry',
|
||
kind: 'puzzle-clear',
|
||
};
|
||
}
|
||
|
||
if (isJumpHopGalleryEntry(entry)) {
|
||
return {
|
||
type: 'load-jump-hop-detail',
|
||
profileId: entry.profileId,
|
||
};
|
||
}
|
||
|
||
if (isWoodenFishGalleryEntry(entry)) {
|
||
return {
|
||
type: 'load-wooden-fish-detail',
|
||
profileId: entry.profileId,
|
||
};
|
||
}
|
||
|
||
if (isMatch3DGalleryEntry(entry)) {
|
||
return {
|
||
type: 'use-entry',
|
||
kind: 'match3d',
|
||
};
|
||
}
|
||
|
||
if (isSquareHoleGalleryEntry(entry)) {
|
||
return {
|
||
type: 'use-entry',
|
||
kind: 'square-hole',
|
||
};
|
||
}
|
||
|
||
if (isVisualNovelGalleryEntry(entry)) {
|
||
return {
|
||
type: 'load-visual-novel-detail',
|
||
profileId: entry.profileId,
|
||
};
|
||
}
|
||
|
||
if (isBarkBattleGalleryEntry(entry)) {
|
||
return {
|
||
type: 'use-entry',
|
||
kind: 'bark-battle',
|
||
};
|
||
}
|
||
|
||
if (isEdutainmentGalleryEntry(entry)) {
|
||
return {
|
||
type: 'use-entry',
|
||
kind: 'edutainment',
|
||
};
|
||
}
|
||
|
||
if (isRpgPublicWorkDetailEntry(entry)) {
|
||
return {
|
||
type: 'load-rpg-detail',
|
||
entry,
|
||
};
|
||
}
|
||
|
||
const exhaustive: never = entry;
|
||
return exhaustive;
|
||
}
|
||
|
||
export function resolvePlatformPublicWorkActionMode(
|
||
entry: PlatformPublicGalleryCard,
|
||
viewerUserId: string | null | undefined,
|
||
): PlatformPublicWorkActionMode {
|
||
return viewerUserId?.trim() && entry.ownerUserId === viewerUserId.trim()
|
||
? 'edit'
|
||
: 'remix';
|
||
}
|
||
|
||
export function resolvePlatformPublicWorkLikeIntent(
|
||
entry: PlatformPublicGalleryCard,
|
||
): PlatformPublicWorkLikeIntent {
|
||
if (isBigFishGalleryEntry(entry)) {
|
||
return {
|
||
type: 'like-big-fish',
|
||
profileId: entry.profileId,
|
||
};
|
||
}
|
||
|
||
if (isPuzzleGalleryEntry(entry)) {
|
||
return {
|
||
type: 'like-puzzle',
|
||
profileId: entry.profileId,
|
||
};
|
||
}
|
||
|
||
if (isEdutainmentGalleryEntry(entry)) {
|
||
return {
|
||
type: 'unsupported',
|
||
errorMessage: '宝贝识物点赞将在后续版本开放。',
|
||
};
|
||
}
|
||
|
||
if (isPuzzleClearGalleryEntry(entry)) {
|
||
return {
|
||
type: 'unsupported',
|
||
errorMessage: '拼消消点赞将在后续版本开放。',
|
||
};
|
||
}
|
||
|
||
if (isWoodenFishGalleryEntry(entry)) {
|
||
return {
|
||
type: 'unsupported',
|
||
errorMessage: '作品类型 wooden-fish 暂不支持点赞。',
|
||
};
|
||
}
|
||
|
||
if (isJumpHopGalleryEntry(entry)) {
|
||
return {
|
||
type: 'unsupported',
|
||
errorMessage: '作品类型 jump-hop 暂不支持点赞。',
|
||
};
|
||
}
|
||
|
||
if (isMatch3DGalleryEntry(entry)) {
|
||
return {
|
||
type: 'unsupported',
|
||
errorMessage: '作品类型 match3d 暂不支持点赞。',
|
||
};
|
||
}
|
||
|
||
if (isBarkBattleGalleryEntry(entry)) {
|
||
return {
|
||
type: 'unsupported',
|
||
errorMessage: '汪汪声浪点赞将在后续版本开放。',
|
||
};
|
||
}
|
||
|
||
if (isSquareHoleGalleryEntry(entry)) {
|
||
return {
|
||
type: 'unsupported',
|
||
errorMessage: '方洞挑战点赞将在后续版本开放。',
|
||
};
|
||
}
|
||
|
||
if (isVisualNovelGalleryEntry(entry)) {
|
||
return {
|
||
type: 'unsupported',
|
||
errorMessage: '视觉小说点赞将在后续版本开放。',
|
||
};
|
||
}
|
||
|
||
return {
|
||
type: 'like-rpg-gallery',
|
||
ownerUserId: entry.ownerUserId,
|
||
profileId: entry.profileId,
|
||
};
|
||
}
|
||
|
||
export function resolvePlatformPublicWorkRemixIntent(
|
||
entry: PlatformPublicGalleryCard,
|
||
): PlatformPublicWorkRemixIntent {
|
||
if (isBigFishGalleryEntry(entry)) {
|
||
return {
|
||
type: 'remix-big-fish',
|
||
profileId: entry.profileId,
|
||
selectionStage: 'big-fish-result',
|
||
};
|
||
}
|
||
|
||
if (isPuzzleGalleryEntry(entry)) {
|
||
return {
|
||
type: 'remix-puzzle',
|
||
profileId: entry.profileId,
|
||
selectionStage: 'puzzle-result',
|
||
};
|
||
}
|
||
|
||
if (isPuzzleClearGalleryEntry(entry)) {
|
||
return {
|
||
type: 'unsupported',
|
||
errorMessage: '拼消消作品改造将在后续版本开放。',
|
||
};
|
||
}
|
||
|
||
if (isMatch3DGalleryEntry(entry)) {
|
||
return {
|
||
type: 'unsupported',
|
||
errorMessage: '抓大鹅作品改造将在后续版本开放。',
|
||
};
|
||
}
|
||
|
||
if (isSquareHoleGalleryEntry(entry)) {
|
||
return {
|
||
type: 'unsupported',
|
||
errorMessage: '方洞挑战作品改造将在后续版本开放。',
|
||
};
|
||
}
|
||
|
||
if (isJumpHopGalleryEntry(entry)) {
|
||
return {
|
||
type: 'unsupported',
|
||
errorMessage: '跳一跳作品改造将在后续版本开放。',
|
||
};
|
||
}
|
||
|
||
if (isWoodenFishGalleryEntry(entry)) {
|
||
return {
|
||
type: 'unsupported',
|
||
errorMessage: '敲木鱼作品改造将在后续版本开放。',
|
||
};
|
||
}
|
||
|
||
if (isVisualNovelGalleryEntry(entry)) {
|
||
return {
|
||
type: 'unsupported',
|
||
errorMessage: '视觉小说作品改造将在后续版本开放。',
|
||
};
|
||
}
|
||
|
||
if (isEdutainmentGalleryEntry(entry)) {
|
||
return {
|
||
type: 'unsupported',
|
||
errorMessage: '宝贝识物作品改造将在创作链路接入后开放。',
|
||
};
|
||
}
|
||
|
||
if (isBarkBattleGalleryEntry(entry)) {
|
||
return {
|
||
type: 'unsupported',
|
||
errorMessage: '汪汪声浪作品改造将在后续版本开放。',
|
||
};
|
||
}
|
||
|
||
return {
|
||
type: 'remix-rpg-gallery',
|
||
ownerUserId: entry.ownerUserId,
|
||
profileId: entry.profileId,
|
||
};
|
||
}
|
||
|
||
export function resolvePlatformPublicWorkEditIntent(
|
||
entry: PlatformPublicGalleryCard,
|
||
deps: PlatformPublicWorkEditIntentDeps,
|
||
): PlatformPublicWorkEditIntent {
|
||
if (isBigFishGalleryEntry(entry)) {
|
||
const work = mapPublicWorkDetailToBigFishWork(entry);
|
||
if (!work?.sourceSessionId?.trim()) {
|
||
return {
|
||
type: 'blocked',
|
||
errorMessage: '这份大鱼吃小鱼作品缺少原草稿会话,暂时无法编辑。',
|
||
};
|
||
}
|
||
|
||
return {
|
||
type: 'edit-big-fish',
|
||
work,
|
||
};
|
||
}
|
||
|
||
if (isPuzzleGalleryEntry(entry)) {
|
||
const work =
|
||
deps.selectedPuzzleDetail?.profileId === entry.profileId
|
||
? deps.selectedPuzzleDetail
|
||
: mapPublicWorkDetailToPuzzleWork(entry);
|
||
if (!work?.sourceSessionId?.trim()) {
|
||
return {
|
||
type: 'blocked',
|
||
errorMessage: '这份拼图作品缺少原草稿会话,暂时无法编辑。',
|
||
};
|
||
}
|
||
|
||
return {
|
||
type: 'edit-puzzle',
|
||
work,
|
||
};
|
||
}
|
||
|
||
if (isMatch3DGalleryEntry(entry)) {
|
||
// 中文注释:抓大鹅草稿恢复仍复用 Match3D Module 的 public detail -> work Adapter。
|
||
const work = deps.mapMatch3DWork(entry);
|
||
if (!work?.sourceSessionId?.trim()) {
|
||
return {
|
||
type: 'blocked',
|
||
errorMessage: '这份抓大鹅作品缺少原草稿会话,暂时无法编辑。',
|
||
};
|
||
}
|
||
|
||
return {
|
||
type: 'edit-match3d',
|
||
work,
|
||
forceDraft: true,
|
||
};
|
||
}
|
||
|
||
if (isSquareHoleGalleryEntry(entry)) {
|
||
const work = mapPublicWorkDetailToSquareHoleWork(entry);
|
||
if (!work?.sourceSessionId?.trim()) {
|
||
return {
|
||
type: 'blocked',
|
||
errorMessage: '这份方洞挑战作品缺少原草稿会话,暂时无法编辑。',
|
||
};
|
||
}
|
||
|
||
return {
|
||
type: 'edit-square-hole',
|
||
work,
|
||
forceDraft: true,
|
||
};
|
||
}
|
||
|
||
if (isJumpHopGalleryEntry(entry)) {
|
||
return {
|
||
type: 'blocked',
|
||
errorMessage: '这份跳一跳作品暂时请从作品架编辑。',
|
||
};
|
||
}
|
||
|
||
if (isPuzzleClearGalleryEntry(entry)) {
|
||
return {
|
||
type: 'blocked',
|
||
errorMessage: '这份拼消消作品暂时请从作品架编辑。',
|
||
};
|
||
}
|
||
|
||
if (isWoodenFishGalleryEntry(entry)) {
|
||
return {
|
||
type: 'blocked',
|
||
errorMessage: '这份敲木鱼作品暂时请从作品架编辑。',
|
||
};
|
||
}
|
||
|
||
if (isVisualNovelGalleryEntry(entry)) {
|
||
const work =
|
||
deps.visualNovelWorks?.find((item) => item.profileId === entry.profileId) ??
|
||
null;
|
||
if (!work) {
|
||
return {
|
||
type: 'blocked',
|
||
errorMessage: '这份视觉小说缺少可编辑草稿。',
|
||
};
|
||
}
|
||
|
||
return {
|
||
type: 'edit-visual-novel',
|
||
work,
|
||
forceDraft: true,
|
||
};
|
||
}
|
||
|
||
if (isEdutainmentGalleryEntry(entry)) {
|
||
return {
|
||
type: 'resolve-edutainment-draft',
|
||
entry,
|
||
};
|
||
}
|
||
|
||
if (isBarkBattleGalleryEntry(entry)) {
|
||
const work =
|
||
deps.barkBattleWorks?.find((item) => item.workId === entry.workId) ??
|
||
deps.barkBattleGalleryEntries?.find(
|
||
(item) => item.workId === entry.workId,
|
||
) ??
|
||
mapBarkBattlePublicDetailToWorkSummary(entry);
|
||
if (!work?.draftId?.trim()) {
|
||
return {
|
||
type: 'blocked',
|
||
errorMessage: '这份汪汪声浪缺少可编辑草稿。',
|
||
};
|
||
}
|
||
|
||
return {
|
||
type: 'edit-bark-battle',
|
||
work,
|
||
forceDraft: true,
|
||
};
|
||
}
|
||
|
||
const editEntry =
|
||
deps.selectedRpgDetailEntry?.profileId === entry.profileId &&
|
||
isRpgPublicWorkLibraryEntry(deps.selectedRpgDetailEntry)
|
||
? deps.selectedRpgDetailEntry
|
||
: null;
|
||
if (!editEntry) {
|
||
return {
|
||
type: 'blocked',
|
||
errorMessage: '作品详情尚未读取完成。',
|
||
};
|
||
}
|
||
|
||
return {
|
||
type: 'edit-rpg-gallery',
|
||
entry: editEntry,
|
||
};
|
||
}
|
||
|
||
export function resolvePlatformPublicWorkStartIntent(
|
||
entry: PlatformPublicGalleryCard,
|
||
deps: PlatformPublicWorkStartIntentDeps,
|
||
): PlatformPublicWorkStartIntent {
|
||
if (isBigFishGalleryEntry(entry)) {
|
||
const work = mapPublicWorkDetailToBigFishWork(entry);
|
||
if (!work) {
|
||
return {
|
||
type: 'blocked',
|
||
errorMessage: '当前作品缺少会话信息,暂时无法进入玩法。',
|
||
};
|
||
}
|
||
|
||
return {
|
||
type: 'start-big-fish',
|
||
work,
|
||
returnStage: 'work-detail',
|
||
};
|
||
}
|
||
|
||
if (isPuzzleGalleryEntry(entry)) {
|
||
const work =
|
||
deps.selectedPuzzleDetail?.profileId === entry.profileId
|
||
? deps.selectedPuzzleDetail
|
||
: mapPublicWorkDetailToPuzzleWork(entry);
|
||
if (!work) {
|
||
return {
|
||
type: 'blocked',
|
||
errorMessage: '当前拼图作品信息不完整,暂时无法进入玩法。',
|
||
};
|
||
}
|
||
|
||
return {
|
||
type: 'start-puzzle',
|
||
work,
|
||
returnStage: 'work-detail',
|
||
authMode: 'isolated',
|
||
};
|
||
}
|
||
|
||
if (isPuzzleClearGalleryEntry(entry)) {
|
||
return {
|
||
type: 'start-puzzle-clear',
|
||
profileId: entry.profileId,
|
||
returnStage: 'work-detail',
|
||
};
|
||
}
|
||
|
||
if (isJumpHopGalleryEntry(entry)) {
|
||
return {
|
||
type: 'start-jump-hop',
|
||
profileId: entry.profileId,
|
||
returnStage: 'work-detail',
|
||
};
|
||
}
|
||
|
||
if (isWoodenFishGalleryEntry(entry)) {
|
||
return {
|
||
type: 'start-wooden-fish',
|
||
profileId: entry.profileId,
|
||
returnStage: 'work-detail',
|
||
};
|
||
}
|
||
|
||
if (isMatch3DGalleryEntry(entry)) {
|
||
// 中文注释:抓大鹅运行态素材归一仍归 Match3D Module,公开详情 Flow 只接其 Adapter。
|
||
const work = deps.mapMatch3DWork(entry);
|
||
if (!work) {
|
||
return {
|
||
type: 'blocked',
|
||
errorMessage: '当前抓大鹅作品信息不完整,暂时无法进入玩法。',
|
||
};
|
||
}
|
||
|
||
return {
|
||
type: 'start-match3d',
|
||
work,
|
||
returnStage: 'work-detail',
|
||
};
|
||
}
|
||
|
||
if (isSquareHoleGalleryEntry(entry)) {
|
||
const work = mapPublicWorkDetailToSquareHoleWork(entry);
|
||
if (!work) {
|
||
return {
|
||
type: 'blocked',
|
||
errorMessage: '当前方洞挑战作品信息不完整,暂时无法进入玩法。',
|
||
};
|
||
}
|
||
|
||
return {
|
||
type: 'start-square-hole',
|
||
work,
|
||
returnStage: 'work-detail',
|
||
};
|
||
}
|
||
|
||
if (isVisualNovelGalleryEntry(entry)) {
|
||
return {
|
||
type: 'start-visual-novel',
|
||
profileId: entry.profileId,
|
||
returnStage: 'work-detail',
|
||
};
|
||
}
|
||
|
||
if (isBarkBattleGalleryEntry(entry)) {
|
||
const work =
|
||
deps.barkBattleGalleryEntries?.find(
|
||
(item) => item.workId === entry.workId,
|
||
) ??
|
||
deps.barkBattleWorks?.find((item) => item.workId === entry.workId) ??
|
||
mapBarkBattlePublicDetailToWorkSummary(entry);
|
||
if (!work) {
|
||
return {
|
||
type: 'blocked',
|
||
errorMessage: '当前汪汪声浪作品信息不完整,暂时无法进入玩法。',
|
||
};
|
||
}
|
||
|
||
return {
|
||
type: 'start-bark-battle',
|
||
work,
|
||
returnStage: 'work-detail',
|
||
};
|
||
}
|
||
|
||
if (isEdutainmentGalleryEntry(entry)) {
|
||
return {
|
||
type: 'start-edutainment',
|
||
entry,
|
||
returnStage: 'work-detail',
|
||
};
|
||
}
|
||
|
||
const launchEntry =
|
||
deps.selectedRpgDetailEntry?.profileId === entry.profileId
|
||
? deps.selectedRpgDetailEntry
|
||
: null;
|
||
if (!launchEntry) {
|
||
return {
|
||
type: 'blocked',
|
||
errorMessage: '作品详情尚未读取完成。',
|
||
};
|
||
}
|
||
|
||
return {
|
||
type: 'record-rpg-gallery-play',
|
||
entry: launchEntry,
|
||
};
|
||
}
|
||
|
||
export function resolvePlatformPublicWorkDetailOpenDecision(
|
||
entry: PlatformPublicGalleryCard,
|
||
deps: PlatformPublicWorkDetailOpenDecisionDeps = {},
|
||
): PlatformPublicWorkDetailOpenDecision {
|
||
const canExposeEntry = deps.canExposeEntry ?? canExposePublicWork;
|
||
const hiddenMessage = deps.hiddenMessage ?? EDUTAINMENT_HIDDEN_MESSAGE;
|
||
const buildWorkDetailPath =
|
||
deps.buildWorkDetailPath ??
|
||
((publicWorkCode: string) =>
|
||
buildPublicWorkStagePath('work-detail', publicWorkCode));
|
||
|
||
if (!canExposeEntry(entry)) {
|
||
return {
|
||
type: 'blocked',
|
||
selectedDetail: null,
|
||
errorMessage: hiddenMessage,
|
||
selectionStage: 'platform',
|
||
historyPath: null,
|
||
};
|
||
}
|
||
|
||
const publicWorkCode = entry.publicWorkCode?.trim()
|
||
? entry.publicWorkCode
|
||
: null;
|
||
|
||
return {
|
||
type: 'open',
|
||
selectedDetail: entry,
|
||
errorMessage: null,
|
||
selectionStage: 'work-detail',
|
||
historyPath: publicWorkCode ? buildWorkDetailPath(publicWorkCode) : null,
|
||
};
|
||
}
|
||
|
||
export function resolveActivePlatformPublicWorkAuthorEntry({
|
||
selectionStage,
|
||
selectedPublicWorkDetail,
|
||
selectedRpgDetailEntry,
|
||
}: ActivePlatformPublicWorkAuthorEntryInput): PlatformPublicGalleryCard | null {
|
||
if (selectionStage === 'work-detail') {
|
||
return selectedPublicWorkDetail;
|
||
}
|
||
|
||
if (
|
||
selectionStage === 'detail' &&
|
||
selectedRpgDetailEntry &&
|
||
selectedRpgDetailEntry.visibility !== 'draft'
|
||
) {
|
||
return selectedRpgDetailEntry;
|
||
}
|
||
|
||
return null;
|
||
}
|