@@ -36,6 +36,8 @@ import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/p
|
||||
import type {
|
||||
CustomWorldGalleryCard,
|
||||
CustomWorldLibraryEntry,
|
||||
ProfilePlayedWorkSummary,
|
||||
ProfilePlayStatsResponse,
|
||||
} from '../../../packages/shared/src/contracts/runtime';
|
||||
import { buildCustomWorldPlayableCharacters } from '../../data/characterPresets';
|
||||
import {
|
||||
@@ -55,6 +57,7 @@ import {
|
||||
import { listBigFishGallery } from '../../services/big-fish-gallery';
|
||||
import {
|
||||
advanceLocalBigFishRuntimeRun,
|
||||
recordBigFishPlay,
|
||||
startLocalBigFishRuntimeRun,
|
||||
} from '../../services/big-fish-runtime';
|
||||
import {
|
||||
@@ -91,6 +94,7 @@ import {
|
||||
} from '../../services/puzzle-gallery';
|
||||
import {
|
||||
advanceLocalPuzzleNextLevel,
|
||||
startPuzzleRun,
|
||||
submitPuzzleLeaderboard,
|
||||
} from '../../services/puzzle-runtime';
|
||||
import {
|
||||
@@ -107,6 +111,7 @@ import {
|
||||
deleteRpgEntryWorldProfile,
|
||||
getRpgEntryWorldGalleryDetailByCode,
|
||||
} from '../../services/rpg-entry/rpgEntryLibraryClient';
|
||||
import { getRpgProfilePlayStats } from '../../services/rpg-entry/rpgProfileClient';
|
||||
import type { CustomWorldProfile } from '../../types';
|
||||
import { useAuthUi } from '../auth/AuthUiContext';
|
||||
import {
|
||||
@@ -154,6 +159,8 @@ type AgentResultBlockerView = {
|
||||
message: string;
|
||||
};
|
||||
|
||||
type BigFishRuntimeSessionSource = 'draft' | 'work' | null;
|
||||
|
||||
const AGENT_RESULT_STRUCTURAL_BLOCKER_CODES = new Set([
|
||||
'publish_missing_world_hook',
|
||||
'publish_missing_player_premise',
|
||||
@@ -442,6 +449,13 @@ export function PlatformEntryFlowShellImpl({
|
||||
title: string;
|
||||
publicWorkCode: string;
|
||||
} | null>(null);
|
||||
const [bigFishRuntimeWork, setBigFishRuntimeWork] =
|
||||
useState<BigFishWorkSummary | null>(null);
|
||||
const [bigFishRuntimeStartedAt, setBigFishRuntimeStartedAt] = useState<
|
||||
number | null
|
||||
>(null);
|
||||
const [bigFishRuntimeSessionSource, setBigFishRuntimeSessionSource] =
|
||||
useState<BigFishRuntimeSessionSource>(null);
|
||||
const [isBigFishLoadingLibrary, setIsBigFishLoadingLibrary] = useState(false);
|
||||
const [bigFishGenerationState, setBigFishGenerationState] =
|
||||
useState<MiniGameDraftGenerationState | null>(null);
|
||||
@@ -475,6 +489,14 @@ export function PlatformEntryFlowShellImpl({
|
||||
string | null
|
||||
>(null);
|
||||
const isBigFishCreationVisible = isPlatformCreationTypeVisible('big-fish');
|
||||
const [profilePlayStats, setProfilePlayStats] =
|
||||
useState<ProfilePlayStatsResponse | null>(null);
|
||||
const [profilePlayStatsError, setProfilePlayStatsError] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
const [isProfilePlayStatsLoading, setIsProfilePlayStatsLoading] =
|
||||
useState(false);
|
||||
const [isProfilePlayStatsOpen, setIsProfilePlayStatsOpen] = useState(false);
|
||||
const hadReadableProtectedDataRef = useRef(false);
|
||||
const hasInitialAgentSession = Boolean(
|
||||
readCustomWorldAgentUiState().activeSessionId &&
|
||||
@@ -1041,6 +1063,9 @@ export function PlatformEntryFlowShellImpl({
|
||||
setBigFishWorks([]);
|
||||
setBigFishRun(null);
|
||||
setBigFishRuntimeShare(null);
|
||||
setBigFishRuntimeWork(null);
|
||||
setBigFishRuntimeStartedAt(null);
|
||||
setBigFishRuntimeSessionSource(null);
|
||||
setBigFishGenerationState(null);
|
||||
setBigFishError(null);
|
||||
setPuzzleOperation(null);
|
||||
@@ -1052,6 +1077,9 @@ export function PlatformEntryFlowShellImpl({
|
||||
setIsPuzzleNextLevelGenerating(false);
|
||||
setPuzzleError(null);
|
||||
setDeletingCreationWorkId(null);
|
||||
setProfilePlayStats(null);
|
||||
setProfilePlayStatsError(null);
|
||||
setIsProfilePlayStatsOpen(false);
|
||||
resetRpgSessionViewState();
|
||||
setRpgGeneratedCustomWorldProfile(null);
|
||||
setRpgCustomWorldError(null);
|
||||
@@ -1120,6 +1148,9 @@ export function PlatformEntryFlowShellImpl({
|
||||
|
||||
const leaveBigFishFlow = useCallback(() => {
|
||||
setBigFishRun(null);
|
||||
setBigFishRuntimeWork(null);
|
||||
setBigFishRuntimeStartedAt(null);
|
||||
setBigFishRuntimeSessionSource(null);
|
||||
setBigFishGenerationState(null);
|
||||
bigFishFlow.leaveFlow();
|
||||
}, [bigFishFlow]);
|
||||
@@ -1156,22 +1187,62 @@ export function PlatformEntryFlowShellImpl({
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionId = bigFishSession.sessionId;
|
||||
setBigFishError(null);
|
||||
setBigFishRuntimeShare(null);
|
||||
setBigFishRuntimeWork(null);
|
||||
setBigFishRuntimeStartedAt(Date.now());
|
||||
setBigFishRuntimeSessionSource('draft');
|
||||
setBigFishRun(startLocalBigFishRuntimeRun({ session: bigFishSession }));
|
||||
setSelectionStage('big-fish-runtime');
|
||||
}, [bigFishSession, setSelectionStage]);
|
||||
void recordBigFishPlay(sessionId, { elapsedMs: 0 }).catch((error) => {
|
||||
setBigFishError(
|
||||
resolveBigFishErrorMessage(error, '记录大鱼吃小鱼游玩失败。'),
|
||||
);
|
||||
});
|
||||
void refreshBigFishShelf();
|
||||
}, [
|
||||
bigFishSession,
|
||||
refreshBigFishShelf,
|
||||
resolveBigFishErrorMessage,
|
||||
setSelectionStage,
|
||||
]);
|
||||
|
||||
const restartBigFishRun = useCallback(() => {
|
||||
if (!bigFishSession && !bigFishRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionId = bigFishSession?.sessionId ?? bigFishRun?.sessionId;
|
||||
if (!sessionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
setBigFishError(null);
|
||||
setBigFishRuntimeShare(null);
|
||||
setBigFishRun(startLocalBigFishRuntimeRun({ session: bigFishSession }));
|
||||
if (bigFishSession) {
|
||||
setBigFishRuntimeShare(null);
|
||||
}
|
||||
setBigFishRuntimeStartedAt(Date.now());
|
||||
setBigFishRuntimeSessionSource(bigFishSession ? 'draft' : 'work');
|
||||
setBigFishRun(
|
||||
startLocalBigFishRuntimeRun({
|
||||
session: bigFishSession,
|
||||
work: bigFishRuntimeWork,
|
||||
}),
|
||||
);
|
||||
setSelectionStage('big-fish-runtime');
|
||||
}, [bigFishRun, bigFishSession, setSelectionStage]);
|
||||
void recordBigFishPlay(sessionId, { elapsedMs: 0 }).catch((error) => {
|
||||
setBigFishError(
|
||||
resolveBigFishErrorMessage(error, '记录大鱼吃小鱼游玩失败。'),
|
||||
);
|
||||
});
|
||||
}, [
|
||||
bigFishRun,
|
||||
bigFishRuntimeWork,
|
||||
bigFishSession,
|
||||
resolveBigFishErrorMessage,
|
||||
setSelectionStage,
|
||||
]);
|
||||
|
||||
const startPuzzleRunFromProfile = useCallback(
|
||||
async (profileId: string) => {
|
||||
@@ -1184,8 +1255,9 @@ export function PlatformEntryFlowShellImpl({
|
||||
|
||||
try {
|
||||
const { item } = await getPuzzleGalleryDetail(profileId);
|
||||
const { run } = await startPuzzleRun({ profileId: item.profileId });
|
||||
setSelectedPuzzleDetail(item);
|
||||
setPuzzleRun(startLocalPuzzleRun(item));
|
||||
setPuzzleRun(run);
|
||||
setPuzzleRuntimeReturnStage('puzzle-gallery-detail');
|
||||
setSelectionStage('puzzle-runtime');
|
||||
pushAppHistoryPath(
|
||||
@@ -1269,6 +1341,25 @@ export function PlatformEntryFlowShellImpl({
|
||||
[bigFishRun],
|
||||
);
|
||||
|
||||
const reportBigFishObservedPlayTime = useCallback(() => {
|
||||
const sessionId = bigFishRun?.sessionId?.trim();
|
||||
if (!sessionId || !bigFishRuntimeStartedAt) {
|
||||
return;
|
||||
}
|
||||
|
||||
const elapsedMs = Math.max(1_000, Date.now() - bigFishRuntimeStartedAt);
|
||||
setBigFishRuntimeStartedAt(null);
|
||||
void recordBigFishPlay(sessionId, { elapsedMs }).catch((error) => {
|
||||
setBigFishError(
|
||||
resolveBigFishErrorMessage(error, '记录大鱼吃小鱼游玩时长失败。'),
|
||||
);
|
||||
});
|
||||
}, [
|
||||
bigFishRun?.sessionId,
|
||||
bigFishRuntimeStartedAt,
|
||||
resolveBigFishErrorMessage,
|
||||
]);
|
||||
|
||||
const swapPuzzlePiecesInRun = useCallback(
|
||||
(payload: { firstPieceId: string; secondPieceId: string }) => {
|
||||
if (!puzzleRun || isPuzzleBusy) {
|
||||
@@ -1712,17 +1803,25 @@ export function PlatformEntryFlowShellImpl({
|
||||
const publicWorkCode = buildBigFishPublicWorkCode(item.sourceSessionId);
|
||||
setBigFishError(null);
|
||||
bigFishFlow.setSession(null);
|
||||
setBigFishRuntimeWork(item);
|
||||
setBigFishRuntimeShare({
|
||||
title: item.title,
|
||||
publicWorkCode,
|
||||
});
|
||||
setBigFishRuntimeStartedAt(Date.now());
|
||||
setBigFishRuntimeSessionSource('work');
|
||||
setBigFishRun(startLocalBigFishRuntimeRun({ work: item }));
|
||||
setSelectionStage('big-fish-runtime');
|
||||
pushAppHistoryPath(
|
||||
buildPublicWorkStagePath('big-fish-runtime', publicWorkCode),
|
||||
);
|
||||
void recordBigFishPlay(sessionId, { elapsedMs: 0 }).catch((error) => {
|
||||
setBigFishError(
|
||||
resolveBigFishErrorMessage(error, '记录大鱼吃小鱼游玩失败。'),
|
||||
);
|
||||
});
|
||||
},
|
||||
[bigFishFlow, setSelectionStage],
|
||||
[bigFishFlow, resolveBigFishErrorMessage, setSelectionStage],
|
||||
);
|
||||
|
||||
const handlePublicCodeSearch = useCallback(
|
||||
@@ -1871,6 +1970,118 @@ export function PlatformEntryFlowShellImpl({
|
||||
],
|
||||
);
|
||||
|
||||
const openProfilePlayedWorks = useCallback(() => {
|
||||
setIsProfilePlayStatsOpen(true);
|
||||
setIsProfilePlayStatsLoading(true);
|
||||
setProfilePlayStatsError(null);
|
||||
|
||||
void getRpgProfilePlayStats()
|
||||
.then(setProfilePlayStats)
|
||||
.catch((error) => {
|
||||
setProfilePlayStats(null);
|
||||
setProfilePlayStatsError(
|
||||
resolveRpgCreationErrorMessage(error, '读取玩过作品失败。'),
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsProfilePlayStatsLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const openPlayedWork = useCallback(
|
||||
(work: ProfilePlayedWorkSummary) => {
|
||||
const worldType = (work.worldType ?? '').toLowerCase();
|
||||
setIsProfilePlayStatsOpen(false);
|
||||
|
||||
if (worldType === 'puzzle' || work.worldKey.startsWith('puzzle:')) {
|
||||
const profileId =
|
||||
work.profileId ?? work.worldKey.replace(/^puzzle:/u, '');
|
||||
if (profileId) {
|
||||
void openPuzzleDetail(profileId, { tab: 'profile' });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
worldType === 'big_fish' ||
|
||||
worldType === 'big-fish' ||
|
||||
work.worldKey.startsWith('big-fish:')
|
||||
) {
|
||||
const sessionId =
|
||||
work.profileId ?? work.worldKey.replace(/^big-fish:/u, '');
|
||||
if (!sessionId) {
|
||||
return;
|
||||
}
|
||||
void refreshBigFishGallery()
|
||||
.then((entries) => {
|
||||
const matchedEntry = entries.find(
|
||||
(entry) => entry.sourceSessionId === sessionId,
|
||||
);
|
||||
if (matchedEntry) {
|
||||
startBigFishRunFromWork(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,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
setBigFishError(
|
||||
resolveBigFishErrorMessage(error, '进入大鱼吃小鱼作品失败。'),
|
||||
);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const profileId = work.profileId ?? work.worldKey;
|
||||
const ownerUserId = work.ownerUserId;
|
||||
if (!ownerUserId || !profileId) {
|
||||
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,
|
||||
});
|
||||
});
|
||||
},
|
||||
[
|
||||
detailNavigation,
|
||||
openPuzzleDetail,
|
||||
refreshBigFishGallery,
|
||||
resolveBigFishErrorMessage,
|
||||
runProtectedAction,
|
||||
startBigFishRunFromWork,
|
||||
],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const publicWorkCode = initialPublicWorkCode?.trim();
|
||||
if (
|
||||
@@ -2149,7 +2360,19 @@ export function PlatformEntryFlowShellImpl({
|
||||
void handlePublicCodeSearch(keyword);
|
||||
}}
|
||||
isSearchingPublicCode={isSearchingPublicCode}
|
||||
onOpenProfileDashboardCard={() => {
|
||||
profilePlayStats={profilePlayStats}
|
||||
isProfilePlayStatsOpen={isProfilePlayStatsOpen}
|
||||
isProfilePlayStatsLoading={isProfilePlayStatsLoading}
|
||||
profilePlayStatsError={profilePlayStatsError}
|
||||
onCloseProfilePlayStats={() => {
|
||||
setIsProfilePlayStatsOpen(false);
|
||||
}}
|
||||
onOpenPlayedWork={openPlayedWork}
|
||||
onOpenProfileDashboardCard={(cardKey) => {
|
||||
if (cardKey === 'playedWorks') {
|
||||
openProfilePlayedWorks();
|
||||
return;
|
||||
}
|
||||
if (platformBootstrap.dashboardError) {
|
||||
void platformBootstrap.refreshProfileDashboard();
|
||||
}
|
||||
@@ -2402,11 +2625,15 @@ export function PlatformEntryFlowShellImpl({
|
||||
isBusy={isBigFishBusy}
|
||||
error={bigFishError}
|
||||
onBack={() => {
|
||||
reportBigFishObservedPlayTime();
|
||||
setSelectionStage(
|
||||
bigFishSession ? 'big-fish-result' : 'platform',
|
||||
bigFishRuntimeSessionSource === 'draft'
|
||||
? 'big-fish-result'
|
||||
: 'platform',
|
||||
);
|
||||
}}
|
||||
onRestart={() => {
|
||||
reportBigFishObservedPlayTime();
|
||||
void restartBigFishRun();
|
||||
}}
|
||||
onSubmitInput={submitBigFishInput}
|
||||
|
||||
Reference in New Issue
Block a user