feat: integrate jump-hop shelf and asset flow

This commit is contained in:
kdletters
2026-05-24 19:00:21 +08:00
parent 2ba4691bc0
commit 42037860d5
25 changed files with 1018 additions and 149 deletions

View File

@@ -177,9 +177,10 @@ import {
type JumpHopRunResponse,
type JumpHopSessionResponse,
type JumpHopSessionSnapshotResponse,
type JumpHopWorkProfileResponse,
type JumpHopWorkspaceCreateRequest,
JumpHopWorkProfileResponse,
JumpHopWorkspaceCreateRequest,
} from '../../services/jump-hop/jumpHopClient';
import type { JumpHopWorkSummaryResponse } from '../../../packages/shared/src/contracts/jumpHop';
import { match3dCreationClient } from '../../services/match3d-creation';
import { createServerMatch3DRuntimeAdapter } from '../../services/match3d-runtime';
import {
@@ -1853,7 +1854,7 @@ function hasRecoverableGeneratedPuzzleDraft(
);
}
function getGenerationNoticeShelfKeys(item: CreationWorkShelfItem) {
function getGenerationNoticeShelfKeys(item: CreationWorkShelfItem): string[] {
switch (item.source.kind) {
case 'rpg':
return collectDraftNoticeKeys('rpg', [
@@ -1882,6 +1883,13 @@ function getGenerationNoticeShelfKeys(item: CreationWorkShelfItem) {
item.source.item.profileId,
item.source.item.sourceSessionId,
]);
case 'jump-hop':
return collectDraftNoticeKeys('jump-hop', [
item.id,
item.source.item.workId,
item.source.item.profileId,
item.source.item.sourceSessionId,
]);
case 'puzzle':
return collectDraftNoticeKeys('puzzle', [
item.id,
@@ -1967,6 +1975,39 @@ function buildPendingBigFishWorks(
}));
}
function buildPendingJumpHopWorks(
pending: Record<string, PendingDraftShelfState> | undefined,
existingItems: readonly JumpHopWorkSummaryResponse[],
): JumpHopWorkSummaryResponse[] {
if (!pending) {
return [];
}
return Object.entries(pending)
.filter(([sessionId]) =>
existingItems.every((item) => item.sourceSessionId !== sessionId),
)
.map(([sessionId, state]) => ({
runtimeKind: 'jump-hop',
workId: `jump-hop-work-${sessionId}`,
profileId: `jump-hop-profile-${sessionId}`,
ownerUserId: '',
sourceSessionId: sessionId,
workTitle: '跳一跳草稿',
workDescription: '正在生成跳一跳玩法草稿。',
themeTags: [],
difficulty: 'standard',
stylePreset: 'minimal-blocks',
coverImageSrc: null,
publicationStatus: 'draft',
playCount: 0,
updatedAt: state.updatedAt,
publishedAt: null,
publishReady: false,
generationStatus: state.status === 'generating' ? 'generating' : 'ready',
}));
}
function buildPendingMatch3DWorks(
pending: Record<string, PendingDraftShelfState> | undefined,
existingItems: readonly Match3DWorkSummary[],
@@ -2637,6 +2678,9 @@ export function PlatformEntryFlowShellImpl({
const [jumpHopGalleryEntries, setJumpHopGalleryEntries] = useState<
JumpHopGalleryCardResponse[]
>([]);
const [jumpHopWorks, setJumpHopWorks] = useState<
JumpHopWorkSummaryResponse[]
>([]);
const [jumpHopRuntimeReturnStage, setJumpHopRuntimeReturnStage] =
useState<JumpHopRuntimeReturnStage>('jump-hop-result');
const [jumpHopGenerationState, setJumpHopGenerationState] =
@@ -2855,6 +2899,10 @@ export function PlatformEntryFlowShellImpl({
creationEntryTypes,
'big-fish',
);
const isJumpHopCreationVisible = isPlatformCreationTypeVisible(
creationEntryTypes,
'jump-hop',
);
const isSquareHoleCreationVisible = isPlatformCreationTypeVisible(
creationEntryTypes,
'square-hole',
@@ -3304,6 +3352,22 @@ export function PlatformEntryFlowShellImpl({
}
}, []);
const refreshJumpHopShelf = useCallback(async () => {
if (!isJumpHopCreationVisible) {
setJumpHopWorks([]);
return [];
}
try {
const worksResponse = await jumpHopClient.listWorks();
setJumpHopWorks(worksResponse.items);
return worksResponse.items;
} catch {
setJumpHopWorks([]);
return [];
}
}, [isJumpHopCreationVisible]);
const refreshWoodenFishGallery = useCallback(async () => {
try {
const galleryResponse = await woodenFishClient.listGallery();
@@ -3513,6 +3577,22 @@ export function PlatformEntryFlowShellImpl({
selectionStage,
]);
useEffect(() => {
if (!platformBootstrap.canReadProtectedData) {
setJumpHopWorks([]);
return;
}
if (platformBootstrap.platformTab === 'create' || selectionStage === 'platform') {
void refreshJumpHopShelf();
}
}, [
platformBootstrap.canReadProtectedData,
platformBootstrap.platformTab,
refreshJumpHopShelf,
selectionStage,
]);
const sessionController = useRpgCreationSessionController({
userId: authUi?.user?.id,
openLoginModal: authUi?.openLoginModal,
@@ -3860,6 +3940,16 @@ export function PlatformEntryFlowShellImpl({
],
[bigFishWorks, pendingDraftShelfItems],
);
const jumpHopShelfItems = useMemo(
() => [
...buildPendingJumpHopWorks(
pendingDraftShelfItems['jump-hop'],
jumpHopWorks,
),
...jumpHopWorks,
],
[jumpHopWorks, pendingDraftShelfItems],
);
const match3dShelfItems = useMemo(
() => [
...buildPendingMatch3DWorks(pendingDraftShelfItems.match3d, match3dWorks),
@@ -3935,6 +4025,13 @@ export function PlatformEntryFlowShellImpl({
...bigFishShelfItems.flatMap((item) =>
collectDraftNoticeKeys('big-fish', [item.workId, item.sourceSessionId]),
),
...jumpHopShelfItems.flatMap((item) =>
collectDraftNoticeKeys('jump-hop', [
item.workId,
item.profileId,
item.sourceSessionId,
]),
),
...match3dShelfItems.flatMap((item) =>
collectDraftNoticeKeys('match3d', [
item.workId,
@@ -3977,6 +4074,7 @@ export function PlatformEntryFlowShellImpl({
babyObjectMatchDrafts,
barkBattleShelfItems,
bigFishShelfItems,
jumpHopShelfItems,
creationHubItems,
isSquareHoleCreationVisible,
match3dShelfItems,
@@ -7312,6 +7410,22 @@ export function PlatformEntryFlowShellImpl({
setJumpHopSession(response.session);
setJumpHopWork(response.work ?? null);
setJumpHopGenerationState(readyState);
if (response.work) {
setJumpHopWorks((current) =>
[response.work!.summary, ...current.filter((item) => item.workId !== response.work!.summary.workId)],
);
markPendingDraftReady('jump-hop', created.session.sessionId, false);
markDraftReady(
'jump-hop',
[
created.session.sessionId,
response.work.summary.workId,
response.work.summary.profileId,
],
false,
);
void refreshJumpHopShelf().catch(() => undefined);
}
setSelectionStage('jump-hop-result');
} catch (error) {
const errorMessage = resolveRpgCreationErrorMessage(
@@ -7426,6 +7540,10 @@ export function PlatformEntryFlowShellImpl({
try {
const response = await jumpHopClient.publishWork(profileId);
setJumpHopWork(response.item);
setJumpHopWorks((current) =>
[response.item.summary, ...current.filter((item) => item.workId !== response.item.summary.workId)],
);
void refreshJumpHopShelf().catch(() => undefined);
openPublishShareModal({
title: response.item.summary.workTitle || '跳一跳',
publicWorkCode: buildJumpHopPublicWorkCode(
@@ -10121,6 +10239,43 @@ export function PlatformEntryFlowShellImpl({
[openPublicWorkDetail, setJumpHopError, setSelectionStage],
);
const openJumpHopDraft = useCallback(
async (item: JumpHopWorkSummaryResponse) => {
markDraftNoticeSeen(
collectDraftNoticeKeys('jump-hop', [
item.workId,
item.profileId,
item.sourceSessionId,
]),
);
if (item.publicationStatus === 'published') {
void openJumpHopPublicWorkDetail(item.profileId);
return;
}
setJumpHopError(null);
setPublicWorkDetailError(null);
setIsJumpHopBusy(true);
try {
const detail = await jumpHopClient.getWorkDetail(item.profileId);
setJumpHopSession(null);
setJumpHopRun(null);
setJumpHopWork(detail.item);
setJumpHopRuntimeReturnStage('jump-hop-result');
enterCreateTab();
setSelectionStage('jump-hop-result');
} catch (error) {
setJumpHopError(
resolveRpgCreationErrorMessage(error, '读取跳一跳草稿失败。'),
);
} finally {
setIsJumpHopBusy(false);
}
},
[enterCreateTab, markDraftNoticeSeen, openPublicWorkDetail, setSelectionStage],
);
const openWoodenFishPublicWorkDetail = useCallback(
async (profileId: string) => {
setIsPublicWorkDetailBusy(true);
@@ -12842,6 +12997,7 @@ export function PlatformEntryFlowShellImpl({
deletingWorkId={deletingCreationWorkId}
rpgLibraryEntries={platformBootstrap.savedCustomWorldEntries}
bigFishItems={isBigFishCreationVisible ? bigFishShelfItems : []}
jumpHopItems={isJumpHopCreationVisible ? jumpHopShelfItems : []}
onOpenBigFishDetail={
isBigFishCreationVisible
? (item) => {
@@ -12851,6 +13007,15 @@ export function PlatformEntryFlowShellImpl({
}
: undefined
}
onOpenJumpHopDetail={
isJumpHopCreationVisible
? (item) => {
runProtectedAction(() => {
void openJumpHopDraft(item);
});
}
: undefined
}
onDeleteBigFish={
isBigFishCreationVisible
? (item) => {
@@ -12858,6 +13023,7 @@ export function PlatformEntryFlowShellImpl({
}
: null
}
onDeleteJumpHop={null}
match3dItems={match3dShelfItems}
onOpenMatch3DDetail={(item) => {
runProtectedAction(() => {