Integrate Match3D Q1 flow
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-05-01 13:53:59 +08:00
parent 375f7493a3
commit df24467e1d
24 changed files with 2089 additions and 361 deletions

View File

@@ -27,6 +27,11 @@ import type {
Match3DSessionResponse,
SendMatch3DMessageRequest,
} from '../../../packages/shared/src/contracts/match3dAgent';
import type { Match3DRunSnapshot } from '../../../packages/shared/src/contracts/match3dRuntime';
import type {
Match3DWorkProfile,
Match3DWorkSummary,
} from '../../../packages/shared/src/contracts/match3dWorks';
import type {
PuzzleAgentActionRequest,
PuzzleAgentOperationRecord,
@@ -85,6 +90,19 @@ import {
shouldRestoreCustomWorldAgentUiState,
} from '../../services/customWorldAgentUiState';
import { match3dCreationClient } from '../../services/match3d-creation';
import {
clickMatch3DItem,
finishMatch3DTimeUp,
restartMatch3DRun,
startMatch3DRun,
stopMatch3DRun,
} from '../../services/match3d-runtime';
import {
deleteMatch3DWork,
getMatch3DWorkDetail,
listMatch3DGallery,
listMatch3DWorks,
} from '../../services/match3d-works';
import {
buildBigFishGenerationAnchorEntries,
buildMiniGameDraftGenerationProgress,
@@ -95,8 +113,10 @@ import {
import { getPlatformProfileDashboard } from '../../services/platform-entry/platformProfileClient';
import {
buildBigFishPublicWorkCode,
buildMatch3DPublicWorkCode,
buildPuzzlePublicWorkCode,
isSameBigFishPublicWorkCode,
isSameMatch3DPublicWorkCode,
isSamePuzzlePublicWorkCode,
} from '../../services/publicWorkCode';
import {
@@ -153,8 +173,10 @@ import type { CustomWorldProfile } from '../../types';
import { useAuthUi } from '../auth/AuthUiContext';
import {
isBigFishGalleryEntry,
isMatch3DGalleryEntry,
isPuzzleGalleryEntry,
mapBigFishWorkToPlatformGalleryCard,
mapMatch3DWorkToPlatformGalleryCard,
mapPuzzleWorkToPlatformGalleryCard,
type PlatformPublicGalleryCard,
} from '../rpg-entry/rpgEntryWorldPresentation';
@@ -239,7 +261,9 @@ function getPlatformPublicGalleryEntryKey(entry: PlatformPublicGalleryCard) {
? 'big-fish'
: isPuzzleGalleryEntry(entry)
? 'puzzle'
: 'rpg';
: isMatch3DGalleryEntry(entry)
? 'match3d'
: 'rpg';
return `${kind}:${entry.ownerUserId}:${entry.profileId}`;
}
@@ -282,12 +306,76 @@ function mapPuzzleWorkToPublicWorkDetail(
return mapPuzzleWorkToPlatformGalleryCard(item);
}
function mapMatch3DWorkToPublicWorkDetail(
item: Match3DWorkSummary,
): PlatformPublicGalleryCard {
return mapMatch3DWorkToPlatformGalleryCard(item);
}
function mapBigFishWorkToPublicWorkDetail(
item: BigFishWorkSummary,
): PlatformPublicGalleryCard {
return mapBigFishWorkToPlatformGalleryCard(item);
}
function mapPublicWorkDetailToMatch3DWork(
entry: PlatformPublicGalleryCard,
): Match3DWorkSummary | null {
if (!isMatch3DGalleryEntry(entry)) {
return null;
}
return {
workId: entry.workId,
profileId: entry.profileId,
ownerUserId: entry.ownerUserId,
sourceSessionId: null,
gameName: entry.worldName,
themeText: entry.themeTags[0] ?? '经典消除',
summary: entry.summaryText,
tags: entry.themeTags,
coverImageSrc: entry.coverImageSrc,
referenceImageSrc: null,
clearCount: 12,
difficulty: 4,
publicationStatus: 'published',
playCount: entry.playCount ?? 0,
updatedAt: entry.updatedAt,
publishedAt: entry.publishedAt,
publishReady: true,
};
}
function buildMatch3DProfileFromSession(
session: Match3DAgentSessionSnapshot | null,
): Match3DWorkProfile | null {
const draft = session?.draft;
if (!session || !draft?.profileId) {
return null;
}
const now = session.updatedAt || new Date().toISOString();
return {
workId: draft.profileId,
profileId: draft.profileId,
ownerUserId: 'current-user',
sourceSessionId: session.sessionId,
gameName: draft.gameName,
themeText: draft.themeText,
summary: draft.summary ?? draft.summaryText ?? '',
tags: draft.tags,
coverImageSrc: draft.coverImageSrc ?? draft.referenceImageSrc ?? null,
referenceImageSrc: draft.referenceImageSrc ?? null,
clearCount: draft.clearCount,
difficulty: draft.difficulty,
publicationStatus: 'draft',
playCount: 0,
updatedAt: now,
publishedAt: null,
publishReady: Boolean(draft.publishReady),
};
}
function mapPublicWorkDetailToPuzzleWork(
entry: PlatformPublicGalleryCard,
): PuzzleWorkSummary | null {
@@ -686,10 +774,17 @@ const Match3DAgentWorkspace = lazy(async () => {
};
});
const Match3DDraftReadyView = lazy(async () => {
const module = await import('../match3d-creation/Match3DDraftReadyView');
const Match3DResultView = lazy(async () => {
const module = await import('../match3d-result/Match3DResultView');
return {
default: module.Match3DDraftReadyView,
default: module.Match3DResultView,
};
});
const Match3DRuntimeShell = lazy(async () => {
const module = await import('../match3d-runtime/Match3DRuntimeShell');
return {
default: module.Match3DRuntimeShell,
};
});
@@ -823,6 +918,16 @@ export function PlatformEntryFlowShellImpl({
const [bigFishGalleryEntries, setBigFishGalleryEntries] = useState<
BigFishWorkSummary[]
>([]);
const [match3dWorks, setMatch3DWorks] = useState<Match3DWorkSummary[]>([]);
const [match3dGalleryEntries, setMatch3DGalleryEntries] = useState<
Match3DWorkSummary[]
>([]);
const [match3dProfile, setMatch3DProfile] =
useState<Match3DWorkProfile | null>(null);
const [match3dRun, setMatch3DRun] = useState<Match3DRunSnapshot | null>(null);
const [match3dRuntimeReturnStage, setMatch3DRuntimeReturnStage] =
useState<'match3d-result' | 'work-detail'>('match3d-result');
const [isMatch3DLoadingLibrary, setIsMatch3DLoadingLibrary] = useState(false);
const [bigFishRun, setBigFishRun] =
useState<BigFishRuntimeSnapshotResponse | null>(null);
const [bigFishRuntimeShare, setBigFishRuntimeShare] = useState<{
@@ -955,6 +1060,34 @@ export function PlatformEntryFlowShellImpl({
}
}, [resolveBigFishErrorMessage]);
const refreshMatch3DShelf = useCallback(async () => {
setIsMatch3DLoadingLibrary(true);
try {
const worksResponse = await listMatch3DWorks();
setMatch3DWorks(worksResponse.items);
setMatch3DError(null);
} catch (error) {
setMatch3DError(
resolveMatch3DErrorMessage(error, '读取抓大鹅作品列表失败。'),
);
} finally {
setIsMatch3DLoadingLibrary(false);
}
}, [resolveMatch3DErrorMessage]);
const refreshMatch3DGallery = useCallback(async () => {
try {
const galleryResponse = await listMatch3DGallery();
setMatch3DGalleryEntries(galleryResponse.items);
return galleryResponse.items;
} catch (error) {
setMatch3DGalleryEntries([]);
setMatch3DError(resolveMatch3DErrorMessage(error, '读取抓大鹅广场失败。'));
return [];
}
}, [resolveMatch3DErrorMessage]);
const refreshPuzzleShelf = useCallback(async () => {
setIsPuzzleLoadingLibrary(true);
@@ -1136,16 +1269,20 @@ export function PlatformEntryFlowShellImpl({
const bigFishPublicEntries = isBigFishCreationVisible
? bigFishGalleryEntries.map(mapBigFishWorkToPlatformGalleryCard)
: [];
const match3dPublicEntries = match3dGalleryEntries.map(
mapMatch3DWorkToPlatformGalleryCard,
);
const puzzlePublicEntries = puzzleGalleryEntries.map(
mapPuzzleWorkToPlatformGalleryCard,
);
return mergePlatformPublicGalleryEntries(
platformBootstrap.publishedGalleryEntries,
[...bigFishPublicEntries, ...puzzlePublicEntries],
[...bigFishPublicEntries, ...match3dPublicEntries, ...puzzlePublicEntries],
).slice(0, 6);
}, [
isBigFishCreationVisible,
bigFishGalleryEntries,
match3dGalleryEntries,
platformBootstrap.publishedGalleryEntries,
puzzleGalleryEntries,
]);
@@ -1157,12 +1294,14 @@ export function PlatformEntryFlowShellImpl({
...(isBigFishCreationVisible
? bigFishGalleryEntries.map(mapBigFishWorkToPlatformGalleryCard)
: []),
...match3dGalleryEntries.map(mapMatch3DWorkToPlatformGalleryCard),
...puzzleGalleryEntries.map(mapPuzzleWorkToPlatformGalleryCard),
],
),
[
isBigFishCreationVisible,
bigFishGalleryEntries,
match3dGalleryEntries,
platformBootstrap.publishedGalleryEntries,
puzzleGalleryEntries,
],
@@ -1336,8 +1475,25 @@ export function PlatformEntryFlowShellImpl({
onSessionOpened: () => {
setShowCreationTypeModal(false);
},
onActionComplete: ({ response, setSession }) => {
onActionComplete: async ({ payload, response, setSession }) => {
setSession(response.session);
if (payload.action !== 'match3d_compile_draft') {
return;
}
const profileId = response.session.draft?.profileId;
if (!profileId) {
setMatch3DProfile(null);
return;
}
try {
const { item } = await getMatch3DWorkDetail(profileId);
setMatch3DProfile(item);
await refreshMatch3DShelf().catch(() => undefined);
} catch {
setMatch3DProfile(buildMatch3DProfileFromSession(response.session));
}
},
});
@@ -1495,6 +1651,8 @@ export function PlatformEntryFlowShellImpl({
const openMatch3DAgentWorkspace = useCallback(async () => {
setMatch3DSession(null);
setMatch3DProfile(null);
setMatch3DRun(null);
setMatch3DError(null);
setStreamingMatch3DReplyText('');
setIsStreamingMatch3DReply(false);
@@ -1503,6 +1661,8 @@ export function PlatformEntryFlowShellImpl({
match3dFlow,
setIsStreamingMatch3DReply,
setMatch3DError,
setMatch3DProfile,
setMatch3DRun,
setMatch3DSession,
setStreamingMatch3DReplyText,
]);
@@ -1595,6 +1755,11 @@ export function PlatformEntryFlowShellImpl({
setBigFishGenerationState(null);
setBigFishError(null);
setMatch3DSession(null);
setMatch3DProfile(null);
setMatch3DWorks([]);
setMatch3DGalleryEntries([]);
setMatch3DRun(null);
setMatch3DRuntimeReturnStage('match3d-result');
setMatch3DError(null);
setStreamingMatch3DReplyText('');
setIsStreamingMatch3DReply(false);
@@ -1689,6 +1854,8 @@ export function PlatformEntryFlowShellImpl({
}, [bigFishFlow]);
const leaveMatch3DFlow = useCallback(() => {
setMatch3DRun(null);
setMatch3DRuntimeReturnStage('match3d-result');
match3dFlow.leaveFlow();
}, [match3dFlow]);
@@ -1758,7 +1925,10 @@ export function PlatformEntryFlowShellImpl({
match3dSession ? 'match3d-agent-workspace' : 'platform',
);
}
}, [match3dSession, selectionStage, setSelectionStage]);
if (selectionStage === 'match3d-runtime' && !match3dRun) {
setSelectionStage(match3dSession?.draft ? 'match3d-result' : 'platform');
}
}, [match3dRun, match3dSession, selectionStage, setSelectionStage]);
const startBigFishRun = useCallback(() => {
if (!bigFishSession) {
@@ -1875,6 +2045,54 @@ export function PlatformEntryFlowShellImpl({
],
);
const startMatch3DRunFromProfile = useCallback(
async (
profile: Match3DWorkProfile | Match3DWorkSummary,
returnStage: 'match3d-result' | 'work-detail' = 'match3d-result',
mirrorErrorToPublicDetail = false,
) => {
if (isMatch3DBusy) {
return;
}
match3dFlow.setIsBusy(true);
setMatch3DError(null);
try {
const { run } = await startMatch3DRun(profile.profileId);
setMatch3DRun(run);
setMatch3DRuntimeReturnStage(returnStage);
setSelectionStage('match3d-runtime');
if (profile.publicationStatus === 'published') {
pushAppHistoryPath(
buildPublicWorkStagePath(
'work-detail',
buildMatch3DPublicWorkCode(profile.profileId),
),
);
}
} catch (error) {
const message = resolveMatch3DErrorMessage(
error,
'启动抓大鹅玩法失败。',
);
setMatch3DError(message);
if (mirrorErrorToPublicDetail) {
setPublicWorkDetailError(message);
}
} finally {
match3dFlow.setIsBusy(false);
}
},
[
isMatch3DBusy,
match3dFlow,
resolveMatch3DErrorMessage,
setMatch3DError,
setSelectionStage,
],
);
const buildPuzzleTestWork = useCallback(
(draft: PuzzleResultDraft) => {
const profileId =
@@ -2595,6 +2813,47 @@ export function PlatformEntryFlowShellImpl({
],
);
const handleDeleteMatch3DWork = useCallback(
(work: Match3DWorkSummary) => {
if (deletingCreationWorkId) {
return;
}
runProtectedAction(() => {
const confirmed = window.confirm(
`确认删除作品《${work.gameName}》吗?删除后会从你的作品列表中移除。`,
);
if (!confirmed) {
return;
}
setDeletingCreationWorkId(work.workId);
setMatch3DError(null);
void deleteMatch3DWork(work.profileId)
.then((response) => {
setMatch3DWorks(response.items);
void refreshMatch3DGallery();
})
.catch((error) => {
setMatch3DError(
resolveMatch3DErrorMessage(error, '删除抓大鹅作品失败。'),
);
})
.finally(() => {
setDeletingCreationWorkId(null);
});
});
},
[
deletingCreationWorkId,
refreshMatch3DGallery,
resolveMatch3DErrorMessage,
runProtectedAction,
setMatch3DError,
],
);
const clearSelectedPublicWorkAuthor = useCallback(() => {
publicWorkAuthorRequestKeyRef.current += 1;
setSelectedPublicWorkAuthor(null);
@@ -2911,6 +3170,45 @@ export function PlatformEntryFlowShellImpl({
],
);
const openMatch3DPublicWorkDetail = useCallback(
async (profileId: string) => {
setIsPublicWorkDetailBusy(true);
setMatch3DError(null);
setPublicWorkDetailError(null);
setSelectionStage('work-detail');
try {
const entries =
match3dGalleryEntries.length > 0
? match3dGalleryEntries
: await refreshMatch3DGallery();
const matchedEntry = entries.find(
(entry) => entry.profileId === profileId,
);
if (!matchedEntry) {
throw new Error('未找到抓大鹅作品。');
}
openPublicWorkDetail(mapMatch3DWorkToPublicWorkDetail(matchedEntry));
} catch (error) {
setPublicWorkDetailError(
resolveMatch3DErrorMessage(error, '读取抓大鹅详情失败。'),
);
} finally {
setIsPublicWorkDetailBusy(false);
}
},
[
match3dGalleryEntries,
openPublicWorkDetail,
refreshMatch3DGallery,
resolveMatch3DErrorMessage,
setMatch3DError,
setSelectionStage,
],
);
const openPuzzleDetail = useCallback(
async (
profileId: string,
@@ -2986,6 +3284,47 @@ export function PlatformEntryFlowShellImpl({
],
);
const openMatch3DDraft = useCallback(
async (item: Match3DWorkSummary) => {
setMatch3DRun(null);
setMatch3DError(null);
setMatch3DProfile(null);
if (item.publicationStatus === 'published') {
openPublicWorkDetail(mapMatch3DWorkToPublicWorkDetail(item));
return;
}
if (!item.sourceSessionId?.trim()) {
setMatch3DError('这份抓大鹅草稿缺少会话信息,请重新开始创作。');
return;
}
const restoredSession = await match3dFlow.restoreDraft(item.sourceSessionId);
if (!restoredSession) {
await refreshMatch3DShelf().catch(() => undefined);
return;
}
try {
const { item: profile } = await getMatch3DWorkDetail(item.profileId);
setMatch3DProfile(profile);
} catch (error) {
setMatch3DProfile(buildMatch3DProfileFromSession(restoredSession));
setMatch3DError(
resolveMatch3DErrorMessage(error, '读取抓大鹅作品详情失败。'),
);
}
},
[
match3dFlow,
openPublicWorkDetail,
refreshMatch3DShelf,
resolveMatch3DErrorMessage,
setMatch3DError,
],
);
const startBigFishRunFromWork = useCallback(
(
item: BigFishWorkSummary,
@@ -3047,6 +3386,17 @@ export function PlatformEntryFlowShellImpl({
return;
}
if (isMatch3DGalleryEntry(selectedPublicWorkDetail)) {
const work = mapPublicWorkDetailToMatch3DWork(selectedPublicWorkDetail);
if (!work) {
setPublicWorkDetailError('当前抓大鹅作品信息不完整,暂时无法进入玩法。');
return;
}
setPublicWorkDetailError(null);
void startMatch3DRunFromProfile(work, 'work-detail', true);
return;
}
const launchEntry =
selectedDetailEntry?.profileId === selectedPublicWorkDetail.profileId
? selectedDetailEntry
@@ -3085,6 +3435,7 @@ export function PlatformEntryFlowShellImpl({
selectedDetailEntry,
selectedPublicWorkDetail,
startBigFishRunFromWork,
startMatch3DRunFromProfile,
startPuzzleRunFromProfile,
]);
@@ -3135,6 +3486,12 @@ export function PlatformEntryFlowShellImpl({
return;
}
if (isMatch3DGalleryEntry(entry)) {
setPublicWorkDetailError('抓大鹅作品改造将在后续版本开放。');
setIsPublicWorkDetailBusy(false);
return;
}
void remixRpgEntryWorldGallery(entry.ownerUserId, entry.profileId)
.then((response) => {
const nextEntry = response.entry;
@@ -3194,10 +3551,12 @@ export function PlatformEntryFlowShellImpl({
normalizedKeyword,
);
const shouldSearchBigFishFirst = upperKeyword.startsWith('BF');
const shouldSearchMatch3DFirst = upperKeyword.startsWith('M3');
const shouldSearchPuzzleFirst = upperKeyword.startsWith('PZ');
const shouldSearchWorkFirst =
!shouldSearchUserIdFirst &&
!shouldSearchBigFishFirst &&
!shouldSearchMatch3DFirst &&
!shouldSearchPuzzleFirst &&
(upperKeyword.startsWith('CW') || /^\d{1,8}$/u.test(normalizedKeyword));
const shouldSearchUserFirst =
@@ -3205,6 +3564,7 @@ export function PlatformEntryFlowShellImpl({
upperKeyword.startsWith('SY') ||
(!shouldSearchWorkFirst &&
!shouldSearchBigFishFirst &&
!shouldSearchMatch3DFirst &&
!shouldSearchPuzzleFirst);
const tryOpenGalleryEntry = async () => {
@@ -3265,6 +3625,21 @@ export function PlatformEntryFlowShellImpl({
openPublicWorkDetail(mapBigFishWorkToPublicWorkDetail(matchedEntry));
};
const tryOpenMatch3DGalleryEntry = async () => {
const entries =
match3dGalleryEntries.length > 0
? match3dGalleryEntries
: await refreshMatch3DGallery();
const matchedEntry = entries.find((entry) =>
isSameMatch3DPublicWorkCode(normalizedKeyword, entry.profileId),
);
if (!matchedEntry) {
throw new Error('未找到抓大鹅作品。');
}
openPublicWorkDetail(mapMatch3DWorkToPublicWorkDetail(matchedEntry));
};
try {
if (shouldSearchUserIdFirst) {
@@ -3283,6 +3658,11 @@ export function PlatformEntryFlowShellImpl({
return;
}
if (shouldSearchMatch3DFirst) {
await tryOpenMatch3DGalleryEntry();
return;
}
if (shouldSearchWorkFirst) {
try {
await tryOpenGalleryEntry();
@@ -3319,6 +3699,8 @@ export function PlatformEntryFlowShellImpl({
},
[
bigFishGalleryEntries,
match3dGalleryEntries,
refreshMatch3DGallery,
openPuzzlePublicWorkDetail,
openPublicWorkDetail,
platformBootstrap.platformTab,
@@ -3360,6 +3742,19 @@ export function PlatformEntryFlowShellImpl({
return;
}
if (
worldType === 'match3d' ||
worldType === 'match_3d' ||
work.worldKey.startsWith('match3d:')
) {
const profileId =
work.profileId ?? work.worldKey.replace(/^match3d:/u, '');
if (profileId) {
void openMatch3DPublicWorkDetail(profileId);
}
return;
}
if (
worldType === 'big_fish' ||
worldType === 'big-fish' ||
@@ -3437,6 +3832,7 @@ export function PlatformEntryFlowShellImpl({
});
},
[
openMatch3DPublicWorkDetail,
openPuzzlePublicWorkDetail,
openPublicWorkDetail,
openRpgPublicWorkDetail,
@@ -3476,11 +3872,13 @@ export function PlatformEntryFlowShellImpl({
if (isBigFishCreationVisible) {
void refreshBigFishGallery();
}
void refreshMatch3DGallery();
void refreshPuzzleGallery();
}
}, [
isBigFishCreationVisible,
refreshBigFishGallery,
refreshMatch3DGallery,
refreshPuzzleGallery,
selectionStage,
]);
@@ -3492,10 +3890,12 @@ export function PlatformEntryFlowShellImpl({
platformBootstrap.canReadProtectedData
) {
void refreshPuzzleShelf();
void refreshMatch3DShelf();
}
}, [
platformBootstrap.canReadProtectedData,
platformBootstrap.platformTab,
refreshMatch3DShelf,
refreshPuzzleShelf,
selectionStage,
]);
@@ -3524,11 +3924,13 @@ export function PlatformEntryFlowShellImpl({
loading={
platformBootstrap.isLoadingPlatform ||
isBigFishLoadingLibrary ||
isMatch3DLoadingLibrary ||
isPuzzleLoadingLibrary
}
error={
platformBootstrap.isLoadingPlatform ||
isBigFishLoadingLibrary ||
isMatch3DLoadingLibrary ||
isPuzzleLoadingLibrary
? null
: (platformBootstrap.platformError ??
@@ -3550,6 +3952,7 @@ export function PlatformEntryFlowShellImpl({
if (isBigFishCreationVisible) {
void refreshBigFishShelf();
}
void refreshMatch3DShelf();
void refreshPuzzleShelf();
}}
createError={
@@ -3603,6 +4006,15 @@ export function PlatformEntryFlowShellImpl({
}
: null
}
match3dItems={match3dWorks}
onOpenMatch3DDetail={(item) => {
runProtectedAction(() => {
void openMatch3DDraft(item);
});
}}
onDeleteMatch3D={(item) => {
handleDeleteMatch3DWork(item);
}}
puzzleItems={puzzleWorks}
onOpenPuzzleDetail={(item) => {
runProtectedAction(() => {
@@ -3684,6 +4096,11 @@ export function PlatformEntryFlowShellImpl({
return;
}
if (isMatch3DGalleryEntry(entry)) {
openPublicWorkDetail(entry);
return;
}
void openRpgPublicWorkDetail(entry);
}}
onOpenLibraryDetail={(entry) => {
@@ -4076,13 +4493,106 @@ export function PlatformEntryFlowShellImpl({
<Suspense
fallback={<LazyPanelFallback label="正在加载抓大鹅结果..." />}
>
<Match3DDraftReadyView
session={match3dSession}
<Match3DResultView
profile={
match3dProfile ?? buildMatch3DProfileFromSession(match3dSession)!
}
draft={match3dSession.draft}
isBusy={isMatch3DBusy}
error={match3dError}
onBack={() => {
setSelectionStage('match3d-agent-workspace');
}}
onSaved={(profile) => {
setMatch3DProfile(profile);
}}
onPublished={(profile) => {
setMatch3DProfile(profile);
void Promise.allSettled([
refreshMatch3DShelf(),
refreshMatch3DGallery(),
]);
openPublicWorkDetail(mapMatch3DWorkToPublicWorkDetail(profile));
}}
onStartTestRun={(profile) => {
setMatch3DProfile(profile);
void startMatch3DRunFromProfile(profile, 'match3d-result');
}}
/>
</Suspense>
</motion.div>
)}
{selectionStage === 'match3d-runtime' && (
<motion.div
key="match3d-runtime"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 z-[100]"
>
<Suspense
fallback={<LazyPanelFallback label="正在加载抓大鹅玩法..." />}
>
<Match3DRuntimeShell
run={match3dRun}
isBusy={isMatch3DBusy}
error={match3dError}
onBack={() => {
if (match3dRun?.runId && match3dRun.status === 'running') {
void stopMatch3DRun(match3dRun.runId).catch(() => undefined);
}
setSelectionStage(match3dRuntimeReturnStage);
}}
onRestart={() => {
if (!match3dRun?.runId || isMatch3DBusy) {
return;
}
match3dFlow.setIsBusy(true);
setMatch3DError(null);
void restartMatch3DRun(match3dRun.runId)
.then(({ run }) => {
setMatch3DRun(run);
})
.catch((error) => {
setMatch3DError(
resolveMatch3DErrorMessage(
error,
'重新开始抓大鹅玩法失败。',
),
);
})
.finally(() => {
match3dFlow.setIsBusy(false);
});
}}
onOptimisticRunChange={setMatch3DRun}
onClickItem={(payload) => {
const runId = payload.runId ?? match3dRun?.runId;
if (!runId) {
return Promise.reject(new Error('抓大鹅运行态缺少 runId。'));
}
return clickMatch3DItem(runId, payload);
}}
onTimeExpired={() => {
if (!match3dRun?.runId) {
return;
}
void finishMatch3DTimeUp(match3dRun.runId)
.then(({ run }) => {
setMatch3DRun(run);
})
.catch((error) => {
setMatch3DError(
resolveMatch3DErrorMessage(
error,
'同步抓大鹅倒计时失败。',
),
);
});
}}
/>
</Suspense>
</motion.div>