387a1c26e3
清理已跟踪原始日志与个人本地配置 修复前后端有效门禁、测试和构建问题 补齐生产 Jenkins、SpacetimeDB 本地命令与文档约束 收紧图片编辑器状态、附件与媒体引用测试 排除已下线旧创作入口测试并清理 warning
1267 lines
32 KiB
TypeScript
1267 lines
32 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 {
|
|
PuzzleClearGalleryCardResponse,
|
|
PuzzleClearWorkProfileResponse,
|
|
} from '../../../packages/shared/src/contracts/puzzleClear';
|
|
import type { PuzzleRunSnapshot } from '../../../packages/shared/src/contracts/puzzleRuntimeSession';
|
|
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
|
|
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 PlatformPublicWorkInteractionConfig = {
|
|
sourceType: string;
|
|
likeEnabled: boolean;
|
|
remixEnabled: boolean;
|
|
likeDisabledMessage: string;
|
|
remixDisabledMessage: string;
|
|
};
|
|
|
|
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 getPlatformPublicWorkInteractionSourceType(
|
|
entry: PlatformPublicGalleryCard,
|
|
) {
|
|
return 'sourceType' in entry ? entry.sourceType : 'custom-world';
|
|
}
|
|
|
|
function resolveConfiguredPublicWorkInteractionBlock(
|
|
entry: PlatformPublicGalleryCard,
|
|
configs: readonly PlatformPublicWorkInteractionConfig[] | null | undefined,
|
|
action: 'like' | 'remix',
|
|
): PlatformPublicWorkLikeIntent | PlatformPublicWorkRemixIntent | null {
|
|
const sourceType = getPlatformPublicWorkInteractionSourceType(entry);
|
|
const config = configs?.find((item) => item.sourceType === sourceType);
|
|
if (!config) {
|
|
return null;
|
|
}
|
|
|
|
if (action === 'like' && !config.likeEnabled) {
|
|
return {
|
|
type: 'unsupported',
|
|
errorMessage:
|
|
config.likeDisabledMessage.trim() || '该作品类型暂不支持点赞。',
|
|
};
|
|
}
|
|
|
|
if (action === 'remix' && !config.remixEnabled) {
|
|
return {
|
|
type: 'unsupported',
|
|
errorMessage:
|
|
config.remixDisabledMessage.trim() || '该作品类型暂不支持改造。',
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
export function resolvePlatformPublicWorkLikeIntent(
|
|
entry: PlatformPublicGalleryCard,
|
|
configs?: readonly PlatformPublicWorkInteractionConfig[] | null,
|
|
): PlatformPublicWorkLikeIntent {
|
|
const configuredBlock = resolveConfiguredPublicWorkInteractionBlock(
|
|
entry,
|
|
configs,
|
|
'like',
|
|
);
|
|
if (configuredBlock) {
|
|
return configuredBlock as 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,
|
|
configs?: readonly PlatformPublicWorkInteractionConfig[] | null,
|
|
): PlatformPublicWorkRemixIntent {
|
|
const configuredBlock = resolveConfiguredPublicWorkInteractionBlock(
|
|
entry,
|
|
configs,
|
|
'remix',
|
|
);
|
|
if (configuredBlock) {
|
|
return configuredBlock as 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;
|
|
}
|