1
This commit is contained in:
@@ -54,7 +54,10 @@ import {
|
||||
getBigFishCreationSession,
|
||||
streamBigFishCreationMessage,
|
||||
} from '../../services/big-fish-creation';
|
||||
import { listBigFishGallery } from '../../services/big-fish-gallery';
|
||||
import {
|
||||
listBigFishGallery,
|
||||
remixBigFishGalleryWork,
|
||||
} from '../../services/big-fish-gallery';
|
||||
import {
|
||||
advanceLocalBigFishRuntimeRun,
|
||||
recordBigFishPlay,
|
||||
@@ -91,6 +94,7 @@ import {
|
||||
import {
|
||||
getPuzzleGalleryDetail,
|
||||
listPuzzleGallery,
|
||||
remixPuzzleGalleryWork,
|
||||
} from '../../services/puzzle-gallery';
|
||||
import {
|
||||
advanceLocalPuzzleNextLevel,
|
||||
@@ -110,6 +114,8 @@ import { rpgCreationPreviewAdapter } from '../../services/rpg-creation/rpgCreati
|
||||
import {
|
||||
deleteRpgEntryWorldProfile,
|
||||
getRpgEntryWorldGalleryDetailByCode,
|
||||
remixRpgEntryWorldGallery,
|
||||
recordRpgEntryWorldGalleryPlay,
|
||||
} from '../../services/rpg-entry/rpgEntryLibraryClient';
|
||||
import { getRpgProfilePlayStats } from '../../services/rpg-entry/rpgProfileClient';
|
||||
import type { CustomWorldProfile } from '../../types';
|
||||
@@ -138,6 +144,7 @@ import {
|
||||
} from './platformEntryShared';
|
||||
import type { PlatformEntryFlowShellProps } from './platformEntryTypes';
|
||||
import { PlatformEntryWorldDetailView } from './PlatformEntryWorldDetailView';
|
||||
import { PlatformWorkDetailView } from './PlatformWorkDetailView';
|
||||
import { usePlatformCreationAgentFlowController } from './usePlatformCreationAgentFlowController';
|
||||
import { usePlatformEntryBootstrap } from './usePlatformEntryBootstrap';
|
||||
import { usePlatformEntryLibraryDetail } from './usePlatformEntryLibraryDetail';
|
||||
@@ -152,15 +159,19 @@ type PuzzleDetailReturnTarget = {
|
||||
tab: PlatformHomeTab;
|
||||
};
|
||||
|
||||
type PuzzleRuntimeReturnStage = 'puzzle-result' | 'puzzle-gallery-detail';
|
||||
type PuzzleRuntimeReturnStage =
|
||||
| 'puzzle-result'
|
||||
| 'puzzle-gallery-detail'
|
||||
| 'work-detail'
|
||||
| 'platform';
|
||||
|
||||
type BigFishRuntimeReturnStage = 'big-fish-result' | 'work-detail' | 'platform';
|
||||
|
||||
type AgentResultBlockerView = {
|
||||
code?: string;
|
||||
message: string;
|
||||
};
|
||||
|
||||
type BigFishRuntimeSessionSource = 'draft' | 'work' | null;
|
||||
|
||||
const AGENT_RESULT_STRUCTURAL_BLOCKER_CODES = new Set([
|
||||
'publish_missing_world_hook',
|
||||
'publish_missing_player_premise',
|
||||
@@ -201,6 +212,59 @@ function mergePlatformPublicGalleryEntries(
|
||||
);
|
||||
}
|
||||
|
||||
function mapRpgGalleryCardToPublicWorkDetail(
|
||||
entry: CustomWorldGalleryCard,
|
||||
): PlatformPublicGalleryCard {
|
||||
return entry;
|
||||
}
|
||||
|
||||
function mapPuzzleWorkToPublicWorkDetail(
|
||||
item: PuzzleWorkSummary,
|
||||
): PlatformPublicGalleryCard {
|
||||
return mapPuzzleWorkToPlatformGalleryCard(item);
|
||||
}
|
||||
|
||||
function mapBigFishWorkToPublicWorkDetail(
|
||||
item: BigFishWorkSummary,
|
||||
): PlatformPublicGalleryCard {
|
||||
return mapBigFishWorkToPlatformGalleryCard(item);
|
||||
}
|
||||
|
||||
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,
|
||||
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 readProfileTextField(
|
||||
profile: CustomWorldProfile | null,
|
||||
paths: string[],
|
||||
@@ -439,6 +503,12 @@ export function PlatformEntryFlowShellImpl({
|
||||
const [showCreationTypeModal, setShowCreationTypeModal] = useState(false);
|
||||
const [selectedDetailEntry, setSelectedDetailEntry] =
|
||||
useState<CustomWorldLibraryEntry<CustomWorldProfile> | null>(null);
|
||||
const [selectedPublicWorkDetail, setSelectedPublicWorkDetail] =
|
||||
useState<PlatformPublicGalleryCard | null>(null);
|
||||
const [publicWorkDetailError, setPublicWorkDetailError] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
const [isPublicWorkDetailBusy, setIsPublicWorkDetailBusy] = useState(false);
|
||||
const [bigFishWorks, setBigFishWorks] = useState<BigFishWorkSummary[]>([]);
|
||||
const [bigFishGalleryEntries, setBigFishGalleryEntries] = useState<
|
||||
BigFishWorkSummary[]
|
||||
@@ -454,8 +524,8 @@ export function PlatformEntryFlowShellImpl({
|
||||
const [bigFishRuntimeStartedAt, setBigFishRuntimeStartedAt] = useState<
|
||||
number | null
|
||||
>(null);
|
||||
const [bigFishRuntimeSessionSource, setBigFishRuntimeSessionSource] =
|
||||
useState<BigFishRuntimeSessionSource>(null);
|
||||
const [bigFishRuntimeReturnStage, setBigFishRuntimeReturnStage] =
|
||||
useState<BigFishRuntimeReturnStage>('platform');
|
||||
const [isBigFishLoadingLibrary, setIsBigFishLoadingLibrary] = useState(false);
|
||||
const [bigFishGenerationState, setBigFishGenerationState] =
|
||||
useState<MiniGameDraftGenerationState | null>(null);
|
||||
@@ -980,10 +1050,13 @@ export function PlatformEntryFlowShellImpl({
|
||||
response.session.publishedProfileId,
|
||||
);
|
||||
setSelectedPuzzleDetail(galleryDetail.item);
|
||||
setSelectionStage('puzzle-gallery-detail');
|
||||
const detailEntry = mapPuzzleWorkToPublicWorkDetail(galleryDetail.item);
|
||||
setSelectedPublicWorkDetail(detailEntry);
|
||||
setPublicWorkDetailError(null);
|
||||
setSelectionStage('work-detail');
|
||||
pushAppHistoryPath(
|
||||
buildPublicWorkStagePath(
|
||||
'puzzle-gallery-detail',
|
||||
'work-detail',
|
||||
buildPuzzlePublicWorkCode(galleryDetail.item.profileId),
|
||||
),
|
||||
);
|
||||
@@ -1060,12 +1133,15 @@ export function PlatformEntryFlowShellImpl({
|
||||
// 一旦退出登录或鉴权上下文被收回,三类作品缓存必须同步清空,不能等刷新页面。
|
||||
setShowCreationTypeModal(false);
|
||||
setSelectedDetailEntry(null);
|
||||
setSelectedPublicWorkDetail(null);
|
||||
setPublicWorkDetailError(null);
|
||||
setIsPublicWorkDetailBusy(false);
|
||||
setBigFishWorks([]);
|
||||
setBigFishRun(null);
|
||||
setBigFishRuntimeShare(null);
|
||||
setBigFishRuntimeWork(null);
|
||||
setBigFishRuntimeStartedAt(null);
|
||||
setBigFishRuntimeSessionSource(null);
|
||||
setBigFishRuntimeReturnStage('platform');
|
||||
setBigFishGenerationState(null);
|
||||
setBigFishError(null);
|
||||
setPuzzleOperation(null);
|
||||
@@ -1088,6 +1164,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
|
||||
if (
|
||||
selectionStage !== 'platform' &&
|
||||
selectionStage !== 'work-detail' &&
|
||||
selectionStage !== 'detail' &&
|
||||
selectionStage !== 'puzzle-gallery-detail'
|
||||
) {
|
||||
@@ -1150,7 +1227,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
setBigFishRun(null);
|
||||
setBigFishRuntimeWork(null);
|
||||
setBigFishRuntimeStartedAt(null);
|
||||
setBigFishRuntimeSessionSource(null);
|
||||
setBigFishRuntimeReturnStage('platform');
|
||||
setBigFishGenerationState(null);
|
||||
bigFishFlow.leaveFlow();
|
||||
}, [bigFishFlow]);
|
||||
@@ -1192,7 +1269,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
setBigFishRuntimeShare(null);
|
||||
setBigFishRuntimeWork(null);
|
||||
setBigFishRuntimeStartedAt(Date.now());
|
||||
setBigFishRuntimeSessionSource('draft');
|
||||
setBigFishRuntimeReturnStage('big-fish-result');
|
||||
setBigFishRun(startLocalBigFishRuntimeRun({ session: bigFishSession }));
|
||||
setSelectionStage('big-fish-runtime');
|
||||
void recordBigFishPlay(sessionId, { elapsedMs: 0 }).catch((error) => {
|
||||
@@ -1221,9 +1298,9 @@ export function PlatformEntryFlowShellImpl({
|
||||
setBigFishError(null);
|
||||
if (bigFishSession) {
|
||||
setBigFishRuntimeShare(null);
|
||||
setBigFishRuntimeReturnStage('big-fish-result');
|
||||
}
|
||||
setBigFishRuntimeStartedAt(Date.now());
|
||||
setBigFishRuntimeSessionSource(bigFishSession ? 'draft' : 'work');
|
||||
setBigFishRun(
|
||||
startLocalBigFishRuntimeRun({
|
||||
session: bigFishSession,
|
||||
@@ -1245,7 +1322,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
]);
|
||||
|
||||
const startPuzzleRunFromProfile = useCallback(
|
||||
async (profileId: string) => {
|
||||
async (
|
||||
profileId: string,
|
||||
returnStage: PuzzleRuntimeReturnStage = 'work-detail',
|
||||
) => {
|
||||
if (isPuzzleBusy) {
|
||||
return;
|
||||
}
|
||||
@@ -1258,7 +1338,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
const { run } = await startPuzzleRun({ profileId: item.profileId });
|
||||
setSelectedPuzzleDetail(item);
|
||||
setPuzzleRun(run);
|
||||
setPuzzleRuntimeReturnStage('puzzle-gallery-detail');
|
||||
setPuzzleRuntimeReturnStage(returnStage);
|
||||
setSelectionStage('puzzle-runtime');
|
||||
pushAppHistoryPath(
|
||||
buildPublicWorkStagePath(
|
||||
@@ -1297,6 +1377,8 @@ export function PlatformEntryFlowShellImpl({
|
||||
updatedAt: now,
|
||||
publishedAt: null,
|
||||
playCount: 0,
|
||||
remixCount: 0,
|
||||
likeCount: 0,
|
||||
publishReady: Boolean(puzzleSession?.resultPreview?.publishReady),
|
||||
} satisfies PuzzleWorkSummary;
|
||||
},
|
||||
@@ -1733,6 +1815,85 @@ export function PlatformEntryFlowShellImpl({
|
||||
],
|
||||
);
|
||||
|
||||
const openPublicWorkDetail = useCallback(
|
||||
(entry: PlatformPublicGalleryCard) => {
|
||||
setSelectedPublicWorkDetail(entry);
|
||||
setPublicWorkDetailError(null);
|
||||
setSelectionStage('work-detail');
|
||||
if (entry.publicWorkCode?.trim()) {
|
||||
pushAppHistoryPath(
|
||||
buildPublicWorkStagePath('work-detail', entry.publicWorkCode),
|
||||
);
|
||||
}
|
||||
},
|
||||
[setSelectionStage],
|
||||
);
|
||||
|
||||
const openRpgPublicWorkDetail = useCallback(
|
||||
async (entry: CustomWorldGalleryCard) => {
|
||||
setIsPublicWorkDetailBusy(true);
|
||||
setPublicWorkDetailError(null);
|
||||
setSelectionStage('work-detail');
|
||||
|
||||
try {
|
||||
const detailEntry =
|
||||
await detailNavigation.loadGalleryDetailEntry(entry);
|
||||
setSelectedDetailEntry(detailEntry);
|
||||
setSelectedPublicWorkDetail(
|
||||
mapRpgGalleryCardToPublicWorkDetail(detailEntry),
|
||||
);
|
||||
if (detailEntry.publicWorkCode?.trim()) {
|
||||
pushAppHistoryPath(
|
||||
buildPublicWorkStagePath('work-detail', detailEntry.publicWorkCode),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
setSelectedPublicWorkDetail(entry);
|
||||
setPublicWorkDetailError(
|
||||
resolveRpgCreationErrorMessage(error, '读取作品详情失败。'),
|
||||
);
|
||||
} finally {
|
||||
setIsPublicWorkDetailBusy(false);
|
||||
}
|
||||
},
|
||||
[detailNavigation, setSelectedDetailEntry, setSelectionStage],
|
||||
);
|
||||
|
||||
const openPuzzlePublicWorkDetail = useCallback(
|
||||
async (
|
||||
profileId: string,
|
||||
returnTarget: PuzzleDetailReturnTarget = {
|
||||
tab: platformBootstrap.platformTab,
|
||||
},
|
||||
) => {
|
||||
setIsPuzzleBusy(true);
|
||||
setIsPublicWorkDetailBusy(true);
|
||||
setPuzzleError(null);
|
||||
setPublicWorkDetailError(null);
|
||||
setSelectionStage('work-detail');
|
||||
|
||||
try {
|
||||
const { item } = await getPuzzleGalleryDetail(profileId);
|
||||
setSelectedPuzzleDetail(item);
|
||||
setPuzzleDetailReturnTarget(returnTarget);
|
||||
openPublicWorkDetail(mapPuzzleWorkToPublicWorkDetail(item));
|
||||
} catch (error) {
|
||||
setPublicWorkDetailError(
|
||||
resolvePuzzleErrorMessage(error, '读取拼图详情失败。'),
|
||||
);
|
||||
} finally {
|
||||
setIsPuzzleBusy(false);
|
||||
setIsPublicWorkDetailBusy(false);
|
||||
}
|
||||
},
|
||||
[
|
||||
openPublicWorkDetail,
|
||||
platformBootstrap.platformTab,
|
||||
resolvePuzzleErrorMessage,
|
||||
setPuzzleError,
|
||||
],
|
||||
);
|
||||
|
||||
const openPuzzleDetail = useCallback(
|
||||
async (
|
||||
profileId: string,
|
||||
@@ -1793,7 +1954,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
);
|
||||
|
||||
const startBigFishRunFromWork = useCallback(
|
||||
(item: BigFishWorkSummary) => {
|
||||
(
|
||||
item: BigFishWorkSummary,
|
||||
returnStage: BigFishRuntimeReturnStage = 'work-detail',
|
||||
) => {
|
||||
const sessionId = item.sourceSessionId?.trim();
|
||||
if (!sessionId) {
|
||||
setBigFishError('当前作品缺少会话信息,暂时无法进入玩法。');
|
||||
@@ -1809,7 +1973,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
publicWorkCode,
|
||||
});
|
||||
setBigFishRuntimeStartedAt(Date.now());
|
||||
setBigFishRuntimeSessionSource('work');
|
||||
setBigFishRuntimeReturnStage(returnStage);
|
||||
setBigFishRun(startLocalBigFishRuntimeRun({ work: item }));
|
||||
setSelectionStage('big-fish-runtime');
|
||||
pushAppHistoryPath(
|
||||
@@ -1824,6 +1988,157 @@ export function PlatformEntryFlowShellImpl({
|
||||
[bigFishFlow, resolveBigFishErrorMessage, setSelectionStage],
|
||||
);
|
||||
|
||||
const startSelectedPublicWork = useCallback(() => {
|
||||
if (!selectedPublicWorkDetail || isPublicWorkDetailBusy) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isBigFishGalleryEntry(selectedPublicWorkDetail)) {
|
||||
const work = mapPublicWorkDetailToBigFishWork(selectedPublicWorkDetail);
|
||||
if (!work) {
|
||||
setPublicWorkDetailError('当前作品缺少会话信息,暂时无法进入玩法。');
|
||||
return;
|
||||
}
|
||||
startBigFishRunFromWork(work);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPuzzleGalleryEntry(selectedPublicWorkDetail)) {
|
||||
void startPuzzleRunFromProfile(selectedPublicWorkDetail.profileId);
|
||||
return;
|
||||
}
|
||||
|
||||
const launchEntry =
|
||||
selectedDetailEntry?.profileId === selectedPublicWorkDetail.profileId
|
||||
? selectedDetailEntry
|
||||
: null;
|
||||
if (!launchEntry) {
|
||||
setPublicWorkDetailError('作品详情尚未读取完成。');
|
||||
return;
|
||||
}
|
||||
|
||||
runProtectedAction(() => {
|
||||
setIsPublicWorkDetailBusy(true);
|
||||
void recordRpgEntryWorldGalleryPlay(
|
||||
launchEntry.ownerUserId,
|
||||
launchEntry.profileId,
|
||||
)
|
||||
.then((updatedEntry) => {
|
||||
setSelectedDetailEntry(updatedEntry);
|
||||
setSelectedPublicWorkDetail(
|
||||
mapRpgGalleryCardToPublicWorkDetail(updatedEntry),
|
||||
);
|
||||
handleCustomWorldSelect(updatedEntry.profile);
|
||||
})
|
||||
.catch((error) => {
|
||||
setPublicWorkDetailError(
|
||||
resolveRpgCreationErrorMessage(error, '记录作品游玩失败。'),
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsPublicWorkDetailBusy(false);
|
||||
});
|
||||
});
|
||||
}, [
|
||||
handleCustomWorldSelect,
|
||||
isPublicWorkDetailBusy,
|
||||
runProtectedAction,
|
||||
selectedDetailEntry,
|
||||
selectedPublicWorkDetail,
|
||||
startBigFishRunFromWork,
|
||||
startPuzzleRunFromProfile,
|
||||
]);
|
||||
|
||||
const remixPublicWork = useCallback(
|
||||
(entry: PlatformPublicGalleryCard) => {
|
||||
if (isPublicWorkDetailBusy) {
|
||||
return;
|
||||
}
|
||||
|
||||
runProtectedAction(() => {
|
||||
setIsPublicWorkDetailBusy(true);
|
||||
setPublicWorkDetailError(null);
|
||||
|
||||
if (isBigFishGalleryEntry(entry)) {
|
||||
void remixBigFishGalleryWork(entry.profileId)
|
||||
.then((response) => {
|
||||
bigFishFlow.setSession(response.session);
|
||||
enterCreateTab();
|
||||
setSelectionStage('big-fish-result');
|
||||
})
|
||||
.catch((error) => {
|
||||
setPublicWorkDetailError(
|
||||
resolveBigFishErrorMessage(error, 'Remix 大鱼吃小鱼作品失败。'),
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsPublicWorkDetailBusy(false);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPuzzleGalleryEntry(entry)) {
|
||||
void remixPuzzleGalleryWork(entry.profileId)
|
||||
.then((response) => {
|
||||
puzzleFlow.setSession(response.session);
|
||||
setPuzzleOperation(null);
|
||||
enterCreateTab();
|
||||
setSelectionStage('puzzle-result');
|
||||
})
|
||||
.catch((error) => {
|
||||
setPublicWorkDetailError(
|
||||
resolvePuzzleErrorMessage(error, 'Remix 拼图作品失败。'),
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsPublicWorkDetailBusy(false);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
void remixRpgEntryWorldGallery(entry.ownerUserId, entry.profileId)
|
||||
.then((response) => {
|
||||
const nextEntry = response.entry;
|
||||
setSelectedDetailEntry(nextEntry);
|
||||
platformBootstrap.setSavedCustomWorldEntries([
|
||||
nextEntry,
|
||||
...platformBootstrap.savedCustomWorldEntries.filter(
|
||||
(entry) => entry.profileId !== nextEntry.profileId,
|
||||
),
|
||||
]);
|
||||
detailNavigation.openSavedCustomWorldEditor(nextEntry);
|
||||
})
|
||||
.catch((error) => {
|
||||
setPublicWorkDetailError(
|
||||
resolveRpgCreationErrorMessage(error, 'Remix RPG 作品失败。'),
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsPublicWorkDetailBusy(false);
|
||||
});
|
||||
});
|
||||
},
|
||||
[
|
||||
bigFishFlow,
|
||||
detailNavigation,
|
||||
enterCreateTab,
|
||||
isPublicWorkDetailBusy,
|
||||
platformBootstrap,
|
||||
puzzleFlow,
|
||||
resolveBigFishErrorMessage,
|
||||
resolvePuzzleErrorMessage,
|
||||
runProtectedAction,
|
||||
setSelectionStage,
|
||||
],
|
||||
);
|
||||
|
||||
const remixSelectedPublicWork = useCallback(() => {
|
||||
if (!selectedPublicWorkDetail) {
|
||||
return;
|
||||
}
|
||||
remixPublicWork(selectedPublicWorkDetail);
|
||||
}, [remixPublicWork, selectedPublicWorkDetail]);
|
||||
|
||||
const handlePublicCodeSearch = useCallback(
|
||||
async (keyword: string) => {
|
||||
const normalizedKeyword = keyword.trim();
|
||||
@@ -1856,7 +2171,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
const tryOpenGalleryEntry = async () => {
|
||||
const entry =
|
||||
await getRpgEntryWorldGalleryDetailByCode(normalizedKeyword);
|
||||
await detailNavigation.openGalleryDetail({
|
||||
const card = {
|
||||
ownerUserId: entry.ownerUserId,
|
||||
profileId: entry.profileId,
|
||||
publicWorkCode: entry.publicWorkCode,
|
||||
@@ -1872,7 +2187,12 @@ export function PlatformEntryFlowShellImpl({
|
||||
themeMode: entry.themeMode,
|
||||
playableNpcCount: entry.playableNpcCount,
|
||||
landmarkCount: entry.landmarkCount,
|
||||
} satisfies CustomWorldGalleryCard);
|
||||
playCount: entry.playCount ?? 0,
|
||||
remixCount: entry.remixCount ?? 0,
|
||||
likeCount: entry.likeCount ?? 0,
|
||||
} satisfies CustomWorldGalleryCard;
|
||||
setSelectedDetailEntry(entry);
|
||||
openPublicWorkDetail(card);
|
||||
};
|
||||
const tryOpenPuzzleGalleryEntry = async () => {
|
||||
const entries =
|
||||
@@ -1887,7 +2207,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
throw new Error('未找到拼图作品。');
|
||||
}
|
||||
|
||||
await openPuzzleDetail(matchedEntry.profileId, {
|
||||
await openPuzzlePublicWorkDetail(matchedEntry.profileId, {
|
||||
tab: platformBootstrap.platformTab,
|
||||
});
|
||||
};
|
||||
@@ -1904,7 +2224,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
throw new Error('未找到大鱼吃小鱼作品。');
|
||||
}
|
||||
|
||||
await startBigFishRunFromWork(matchedEntry);
|
||||
openPublicWorkDetail(mapBigFishWorkToPublicWorkDetail(matchedEntry));
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -1959,14 +2279,13 @@ export function PlatformEntryFlowShellImpl({
|
||||
}
|
||||
},
|
||||
[
|
||||
detailNavigation,
|
||||
bigFishGalleryEntries,
|
||||
openPuzzleDetail,
|
||||
openPuzzlePublicWorkDetail,
|
||||
openPublicWorkDetail,
|
||||
platformBootstrap.platformTab,
|
||||
puzzleGalleryEntries,
|
||||
refreshBigFishGallery,
|
||||
refreshPuzzleGallery,
|
||||
startBigFishRunFromWork,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -1997,7 +2316,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
const profileId =
|
||||
work.profileId ?? work.worldKey.replace(/^puzzle:/u, '');
|
||||
if (profileId) {
|
||||
void openPuzzleDetail(profileId, { tab: 'profile' });
|
||||
void openPuzzlePublicWorkDetail(profileId, { tab: 'profile' });
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -2018,25 +2337,29 @@ export function PlatformEntryFlowShellImpl({
|
||||
(entry) => entry.sourceSessionId === sessionId,
|
||||
);
|
||||
if (matchedEntry) {
|
||||
startBigFishRunFromWork(matchedEntry);
|
||||
openPublicWorkDetail(
|
||||
mapBigFishWorkToPublicWorkDetail(matchedEntry),
|
||||
);
|
||||
return;
|
||||
}
|
||||
startBigFishRunFromWork({
|
||||
workId: `big-fish:${sessionId}`,
|
||||
sourceSessionId: sessionId,
|
||||
ownerUserId: work.ownerUserId ?? '',
|
||||
title: work.worldTitle,
|
||||
subtitle: work.worldSubtitle,
|
||||
summary: work.worldSubtitle,
|
||||
coverImageSrc: null,
|
||||
status: 'published',
|
||||
updatedAt: work.lastPlayedAt,
|
||||
publishReady: true,
|
||||
levelCount: 0,
|
||||
levelMainImageReadyCount: 0,
|
||||
levelMotionReadyCount: 0,
|
||||
backgroundReady: false,
|
||||
});
|
||||
openPublicWorkDetail(
|
||||
mapBigFishWorkToPublicWorkDetail({
|
||||
workId: `big-fish:${sessionId}`,
|
||||
sourceSessionId: sessionId,
|
||||
ownerUserId: work.ownerUserId ?? '',
|
||||
title: work.worldTitle,
|
||||
subtitle: work.worldSubtitle,
|
||||
summary: work.worldSubtitle,
|
||||
coverImageSrc: null,
|
||||
status: 'published',
|
||||
updatedAt: work.lastPlayedAt,
|
||||
publishReady: true,
|
||||
levelCount: 0,
|
||||
levelMainImageReadyCount: 0,
|
||||
levelMotionReadyCount: 0,
|
||||
backgroundReady: false,
|
||||
}),
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
setBigFishError(
|
||||
@@ -2052,33 +2375,33 @@ export function PlatformEntryFlowShellImpl({
|
||||
return;
|
||||
}
|
||||
|
||||
runProtectedAction(() => {
|
||||
void detailNavigation.openGalleryDetail({
|
||||
ownerUserId,
|
||||
profileId,
|
||||
publicWorkCode: null,
|
||||
authorPublicUserCode: null,
|
||||
visibility: 'published',
|
||||
publishedAt: work.firstPlayedAt,
|
||||
updatedAt: work.lastPlayedAt,
|
||||
authorDisplayName: work.worldSubtitle,
|
||||
worldName: work.worldTitle,
|
||||
subtitle: work.worldSubtitle,
|
||||
summaryText: '',
|
||||
coverImageSrc: null,
|
||||
themeMode: 'martial',
|
||||
playableNpcCount: 0,
|
||||
landmarkCount: 0,
|
||||
});
|
||||
void openRpgPublicWorkDetail({
|
||||
ownerUserId,
|
||||
profileId,
|
||||
publicWorkCode: null,
|
||||
authorPublicUserCode: null,
|
||||
visibility: 'published',
|
||||
publishedAt: work.firstPlayedAt,
|
||||
updatedAt: work.lastPlayedAt,
|
||||
authorDisplayName: work.worldSubtitle,
|
||||
worldName: work.worldTitle,
|
||||
subtitle: work.worldSubtitle,
|
||||
summaryText: '',
|
||||
coverImageSrc: null,
|
||||
themeMode: 'martial',
|
||||
playableNpcCount: 0,
|
||||
landmarkCount: 0,
|
||||
playCount: 0,
|
||||
remixCount: 0,
|
||||
likeCount: 0,
|
||||
});
|
||||
},
|
||||
[
|
||||
detailNavigation,
|
||||
openPuzzleDetail,
|
||||
openPuzzlePublicWorkDetail,
|
||||
openPublicWorkDetail,
|
||||
openRpgPublicWorkDetail,
|
||||
refreshBigFishGallery,
|
||||
resolveBigFishErrorMessage,
|
||||
runProtectedAction,
|
||||
startBigFishRunFromWork,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -2234,7 +2557,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
isBigFishCreationVisible
|
||||
? (item) => {
|
||||
runProtectedAction(() => {
|
||||
void startBigFishRunFromWork(item);
|
||||
void startBigFishRunFromWork(item, 'platform');
|
||||
});
|
||||
}
|
||||
: null
|
||||
@@ -2254,7 +2577,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
}}
|
||||
onExperiencePuzzle={(profileId) => {
|
||||
runProtectedAction(() => {
|
||||
void startPuzzleRunFromProfile(profileId);
|
||||
void startPuzzleRunFromProfile(profileId, 'platform');
|
||||
});
|
||||
}}
|
||||
onDeletePuzzle={(item) => {
|
||||
@@ -2310,42 +2633,18 @@ export function PlatformEntryFlowShellImpl({
|
||||
onOpenCreateTypePicker={openCreationTypePicker}
|
||||
onOpenGalleryDetail={(entry) => {
|
||||
if (isBigFishGalleryEntry(entry)) {
|
||||
runProtectedAction(() => {
|
||||
void startBigFishRunFromWork({
|
||||
workId: entry.workId,
|
||||
sourceSessionId: entry.profileId,
|
||||
ownerUserId: entry.ownerUserId,
|
||||
title: entry.worldName,
|
||||
subtitle: entry.subtitle,
|
||||
summary: entry.summaryText,
|
||||
coverImageSrc: entry.coverImageSrc,
|
||||
status: 'published',
|
||||
updatedAt: entry.updatedAt,
|
||||
publishReady: true,
|
||||
levelCount: Number.parseInt(
|
||||
entry.themeTags
|
||||
.find((tag) => /^\d+级$/u.test(tag))
|
||||
?.replace('级', '') ?? '0',
|
||||
10,
|
||||
),
|
||||
levelMainImageReadyCount: 0,
|
||||
levelMotionReadyCount: 0,
|
||||
backgroundReady: Boolean(entry.coverImageSrc),
|
||||
});
|
||||
});
|
||||
openPublicWorkDetail(entry);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPuzzleGalleryEntry(entry)) {
|
||||
void openPuzzleDetail(entry.profileId, {
|
||||
void openPuzzlePublicWorkDetail(entry.profileId, {
|
||||
tab: platformBootstrap.platformTab,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
runProtectedAction(() => {
|
||||
void detailNavigation.openGalleryDetail(entry);
|
||||
});
|
||||
void openRpgPublicWorkDetail(entry);
|
||||
}}
|
||||
onOpenLibraryDetail={(entry) => {
|
||||
runProtectedAction(() => {
|
||||
@@ -2382,6 +2681,28 @@ export function PlatformEntryFlowShellImpl({
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{selectionStage === 'work-detail' && selectedPublicWorkDetail && (
|
||||
<motion.div
|
||||
key="platform-work-detail"
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -12 }}
|
||||
className="flex h-full min-h-0 flex-col"
|
||||
>
|
||||
<PlatformWorkDetailView
|
||||
entry={selectedPublicWorkDetail}
|
||||
isBusy={isPublicWorkDetailBusy || isPuzzleBusy || isBigFishBusy}
|
||||
error={publicWorkDetailError}
|
||||
onBack={() => {
|
||||
setPublicWorkDetailError(null);
|
||||
setSelectionStage('platform');
|
||||
}}
|
||||
onStart={startSelectedPublicWork}
|
||||
onRemix={remixSelectedPublicWork}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{selectionStage === 'detail' && (
|
||||
<motion.div
|
||||
key="platform-detail"
|
||||
@@ -2396,6 +2717,22 @@ export function PlatformEntryFlowShellImpl({
|
||||
{detailNavigation.detailError || '正在读取作品详情...'}
|
||||
</div>
|
||||
</div>
|
||||
) : selectedDetailEntry.visibility !== 'draft' ? (
|
||||
<PlatformWorkDetailView
|
||||
entry={mapRpgGalleryCardToPublicWorkDetail(selectedDetailEntry)}
|
||||
isBusy={detailNavigation.isMutatingDetail}
|
||||
error={detailNavigation.detailError}
|
||||
onBack={() => {
|
||||
detailNavigation.setDetailError(null);
|
||||
entryNavigation.backToPlatformHome();
|
||||
}}
|
||||
onStart={handleStartSelectedWorld}
|
||||
onRemix={() => {
|
||||
remixPublicWork(
|
||||
mapRpgGalleryCardToPublicWorkDetail(selectedDetailEntry),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<PlatformEntryWorldDetailView
|
||||
entry={selectedDetailEntry}
|
||||
@@ -2428,7 +2765,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
: null
|
||||
}
|
||||
onUnpublish={
|
||||
selectedDetailEntry.visibility === 'published' &&
|
||||
selectedDetailEntry.visibility !== 'draft' &&
|
||||
detailNavigation.isSelectedWorldOwned
|
||||
? () => {
|
||||
runProtectedAction(() => {
|
||||
@@ -2626,11 +2963,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
error={bigFishError}
|
||||
onBack={() => {
|
||||
reportBigFishObservedPlayTime();
|
||||
setSelectionStage(
|
||||
bigFishRuntimeSessionSource === 'draft'
|
||||
? 'big-fish-result'
|
||||
: 'platform',
|
||||
);
|
||||
setSelectionStage(bigFishRuntimeReturnStage);
|
||||
}}
|
||||
onRestart={() => {
|
||||
reportBigFishObservedPlayTime();
|
||||
@@ -2783,6 +3116,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
onStartGame={() => {
|
||||
void startPuzzleRunFromProfile(
|
||||
selectedPuzzleDetail.profileId,
|
||||
'puzzle-gallery-detail',
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user