refactor: 收口创作恢复身份匹配

This commit is contained in:
2026-06-04 02:04:53 +08:00
parent a504da1e32
commit e6e0f93102
7 changed files with 288 additions and 53 deletions

View File

@@ -408,9 +408,16 @@ import {
buildVisualNovelCreationUrlState,
buildWoodenFishCreationUrlState,
hasPuzzleRuntimeUrlStateValue,
matchesBabyObjectMatchCreationUrlRestoreTarget,
matchesBarkBattleCreationUrlRestoreTarget,
matchesBigFishCreationUrlRestoreTarget,
matchesSessionProfileWorkCreationUrlRestoreTarget,
matchesVisualNovelCreationUrlRestoreTarget,
normalizeCreationUrlValue,
resolveCreationUrlRestoreTarget,
resolveInitialCreationUrlRestoreDecision,
resolveJumpHopCreationUrlRestoreStage,
resolveWoodenFishCreationUrlRestoreStage,
} from './platformCreationUrlStateModel';
import { resolvePlatformCreationWorkDeleteConfirmationModel } from './platformCreationWorkDeleteFlow';
import {
@@ -12150,7 +12157,7 @@ export function PlatformEntryFlowShellImpl({
if (!target) {
return;
}
const { sessionId, profileId, draftId, workId } = target;
const { sessionId, profileId } = target;
if (target.kind === 'big-fish') {
const targetSessionId = target.bigFishSessionId;
@@ -12159,10 +12166,8 @@ export function PlatformEntryFlowShellImpl({
(bigFishWorks.length > 0
? bigFishWorks
: (await listBigFishWorks().catch(() => ({ items: [] }))).items
).find(
(item) =>
item.sourceSessionId === targetSessionId ||
item.workId === workId,
).find((item) =>
matchesBigFishCreationUrlRestoreTarget(item, target),
) ?? null;
if (matchedWork) {
await openBigFishDraft(matchedWork);
@@ -12180,11 +12185,8 @@ export function PlatformEntryFlowShellImpl({
: mapMatch3DWorksForRuntimeUi(
(await listMatch3DWorks().catch(() => ({ items: [] }))).items,
)
).find(
(item) =>
item.sourceSessionId === sessionId ||
item.profileId === profileId ||
item.workId === workId,
).find((item) =>
matchesSessionProfileWorkCreationUrlRestoreTarget(item, target),
) ?? null;
if (matchedWork) {
await openMatch3DDraft(matchedWork, { forceDraft: true });
@@ -12201,11 +12203,8 @@ export function PlatformEntryFlowShellImpl({
(squareHoleWorks.length > 0
? squareHoleWorks
: (await listSquareHoleWorks().catch(() => ({ items: [] }))).items
).find(
(item) =>
item.sourceSessionId === sessionId ||
item.profileId === profileId ||
item.workId === workId,
).find((item) =>
matchesSessionProfileWorkCreationUrlRestoreTarget(item, target),
) ?? null;
if (matchedWork) {
await openSquareHoleDraft(matchedWork, { forceDraft: true });
@@ -12222,11 +12221,8 @@ export function PlatformEntryFlowShellImpl({
(puzzleWorks.length > 0
? puzzleWorks
: (await listPuzzleWorks().catch(() => ({ items: [] }))).items
).find(
(item) =>
item.sourceSessionId === sessionId ||
item.profileId === profileId ||
item.workId === workId,
).find((item) =>
matchesSessionProfileWorkCreationUrlRestoreTarget(item, target),
) ?? null;
if (matchedWork) {
await openPuzzleDraft(matchedWork);
@@ -12243,7 +12239,9 @@ export function PlatformEntryFlowShellImpl({
(visualNovelWorks.length > 0
? visualNovelWorks
: (await listVisualNovelWorks().catch(() => ({ works: [] }))).works
).find((item) => item.profileId === profileId) ?? null;
).find((item) =>
matchesVisualNovelCreationUrlRestoreTarget(item, target),
) ?? null;
if (matchedWork) {
await openVisualNovelDraft(matchedWork, { forceDraft: true });
return;
@@ -12259,8 +12257,8 @@ export function PlatformEntryFlowShellImpl({
(barkBattleWorks.length > 0
? barkBattleWorks
: (await listBarkBattleWorks().catch(() => ({ items: [] }))).items
).find(
(item) => item.workId === workId || item.draftId === draftId,
).find((item) =>
matchesBarkBattleCreationUrlRestoreTarget(item, target),
) ?? null;
if (matchedWork) {
openBarkBattleDraft(matchedWork, { forceDraft: true });
@@ -12273,11 +12271,8 @@ export function PlatformEntryFlowShellImpl({
(babyObjectMatchDrafts.length > 0
? babyObjectMatchDrafts
: await listLocalBabyObjectMatchDrafts().catch(() => [])
).find(
(item) =>
item.profileId === profileId ||
item.draftId === draftId ||
item.profileId === workId,
).find((item) =>
matchesBabyObjectMatchCreationUrlRestoreTarget(item, target),
) ?? null;
if (matchedDraft) {
openBabyObjectMatchDraft(matchedDraft);
@@ -12313,11 +12308,11 @@ export function PlatformEntryFlowShellImpl({
);
enterCreateTab();
setSelectionStage(
target.isGeneratingPath
? 'jump-hop-generating'
: session?.draft || work
? 'jump-hop-result'
: 'jump-hop-workspace',
resolveJumpHopCreationUrlRestoreStage({
isGeneratingPath: target.isGeneratingPath,
hasRestoredDraft: Boolean(session?.draft),
hasRestoredWork: Boolean(work),
}),
);
} catch (error) {
setJumpHopError(
@@ -12344,11 +12339,10 @@ export function PlatformEntryFlowShellImpl({
);
enterCreateTab();
setSelectionStage(
target.isGeneratingPath
? 'wooden-fish-generating'
: session.draft
? 'wooden-fish-result'
: 'wooden-fish-workspace',
resolveWoodenFishCreationUrlRestoreStage({
isGeneratingPath: target.isGeneratingPath,
hasRestoredDraft: Boolean(session.draft),
}),
);
} catch (error) {
setWoodenFishError(

View File

@@ -31,9 +31,16 @@ import {
buildWoodenFishCreationUrlState,
hasCreationUrlStateValue,
hasPuzzleRuntimeUrlStateValue,
matchesBabyObjectMatchCreationUrlRestoreTarget,
matchesBarkBattleCreationUrlRestoreTarget,
matchesBigFishCreationUrlRestoreTarget,
matchesSessionProfileWorkCreationUrlRestoreTarget,
matchesVisualNovelCreationUrlRestoreTarget,
normalizeCreationUrlValue,
resolveCreationUrlRestoreTarget,
resolveInitialCreationUrlRestoreDecision,
resolveJumpHopCreationUrlRestoreStage,
resolveWoodenFishCreationUrlRestoreStage,
} from './platformCreationUrlStateModel';
describe('platformCreationUrlStateModel', () => {
@@ -193,6 +200,129 @@ describe('platformCreationUrlStateModel', () => {
).toBeNull();
});
test('matches restore targets against work and draft identities', () => {
const bigFishTarget = resolveCreationUrlRestoreTarget(
'/creation/big-fish/result',
{
workId: 'big-fish-work-river',
},
);
expect(bigFishTarget?.kind).toBe('big-fish');
if (bigFishTarget?.kind !== 'big-fish') {
throw new Error('big fish target expected');
}
expect(
matchesBigFishCreationUrlRestoreTarget(
{ sourceSessionId: 'river' },
bigFishTarget,
),
).toBe(true);
expect(
matchesBigFishCreationUrlRestoreTarget(
{ workId: 'big-fish-work-river' },
bigFishTarget,
),
).toBe(true);
const target = {
sessionId: 'session-1',
profileId: 'profile-1',
draftId: 'draft-1',
workId: 'work-1',
};
expect(
matchesSessionProfileWorkCreationUrlRestoreTarget(
{ sourceSessionId: 'session-1' },
target,
),
).toBe(true);
expect(
matchesSessionProfileWorkCreationUrlRestoreTarget(
{ profileId: 'profile-1' },
target,
),
).toBe(true);
expect(
matchesSessionProfileWorkCreationUrlRestoreTarget(
{ workId: 'work-1' },
target,
),
).toBe(true);
expect(
matchesVisualNovelCreationUrlRestoreTarget(
{ profileId: 'profile-1' },
target,
),
).toBe(true);
expect(
matchesBarkBattleCreationUrlRestoreTarget(
{ draftId: 'draft-1' },
target,
),
).toBe(true);
expect(
matchesBabyObjectMatchCreationUrlRestoreTarget(
{ profileId: 'work-1' },
target,
),
).toBe(true);
expect(
matchesSessionProfileWorkCreationUrlRestoreTarget(
{ sourceSessionId: null, profileId: null, workId: null },
{ sessionId: null, profileId: null, workId: null },
),
).toBe(false);
expect(
matchesBarkBattleCreationUrlRestoreTarget(
{ workId: null, draftId: null },
{ workId: null, draftId: null },
),
).toBe(false);
});
test('resolves work backed restore stages', () => {
expect(
resolveJumpHopCreationUrlRestoreStage({
isGeneratingPath: true,
hasRestoredDraft: false,
hasRestoredWork: true,
}),
).toBe('jump-hop-generating');
expect(
resolveJumpHopCreationUrlRestoreStage({
isGeneratingPath: false,
hasRestoredDraft: false,
hasRestoredWork: true,
}),
).toBe('jump-hop-result');
expect(
resolveJumpHopCreationUrlRestoreStage({
isGeneratingPath: false,
hasRestoredDraft: false,
hasRestoredWork: false,
}),
).toBe('jump-hop-workspace');
expect(
resolveWoodenFishCreationUrlRestoreStage({
isGeneratingPath: true,
hasRestoredDraft: true,
}),
).toBe('wooden-fish-generating');
expect(
resolveWoodenFishCreationUrlRestoreStage({
isGeneratingPath: false,
hasRestoredDraft: true,
}),
).toBe('wooden-fish-result');
expect(
resolveWoodenFishCreationUrlRestoreStage({
isGeneratingPath: false,
hasRestoredDraft: false,
}),
).toBe('wooden-fish-workspace');
});
test('builds creation restore state for core session based plays', () => {
expect(
buildBigFishCreationUrlState({

View File

@@ -20,6 +20,7 @@ import type {
WoodenFishSessionSnapshotResponse,
WoodenFishWorkProfileResponse,
} from '../../services/wooden-fish/woodenFishClient';
import type { SelectionStage } from './platformEntryTypes';
import {
buildPuzzleResultProfileId,
buildPuzzleResultWorkId,
@@ -80,19 +81,43 @@ type CreationUrlRestoreTargetBase = {
isGeneratingPath: boolean;
};
export type CreationUrlRestoreTarget =
| (CreationUrlRestoreTargetBase & {
kind: 'big-fish';
bigFishSessionId: string | null;
})
| (CreationUrlRestoreTargetBase & {
kind: Exclude<CreationUrlRestoreTargetKind, 'big-fish'>;
});
export type BigFishCreationUrlRestoreTarget = CreationUrlRestoreTargetBase & {
kind: 'big-fish';
bigFishSessionId: string | null;
};
type NonBigFishCreationUrlRestoreTarget = Extract<
CreationUrlRestoreTarget,
{ kind: Exclude<CreationUrlRestoreTargetKind, 'big-fish'> }
>;
type NonBigFishCreationUrlRestoreTarget = CreationUrlRestoreTargetBase & {
kind: Exclude<CreationUrlRestoreTargetKind, 'big-fish'>;
};
export type CreationUrlRestoreTarget =
| BigFishCreationUrlRestoreTarget
| NonBigFishCreationUrlRestoreTarget;
export type BigFishRestoreWorkIdentity = {
sourceSessionId?: string | null;
workId?: string | null;
};
export type SessionProfileWorkRestoreIdentity = {
sourceSessionId?: string | null;
profileId?: string | null;
workId?: string | null;
};
export type ProfileRestoreWorkIdentity = {
profileId?: string | null;
};
export type BarkBattleRestoreWorkIdentity = {
workId?: string | null;
draftId?: string | null;
};
export type BabyObjectMatchRestoreDraftIdentity = {
profileId?: string | null;
draftId?: string | null;
};
const CREATION_URL_RESTORE_TARGET_ROUTES = [
['/creation/big-fish', 'big-fish'],
@@ -147,6 +172,89 @@ export function resolveCreationUrlRestoreTarget(
return base as NonBigFishCreationUrlRestoreTarget;
}
function matchesRestoreValue(
itemValue: string | null | undefined,
targetValue: string | null,
) {
return Boolean(targetValue && itemValue === targetValue);
}
export function matchesBigFishCreationUrlRestoreTarget(
item: BigFishRestoreWorkIdentity,
target: BigFishCreationUrlRestoreTarget,
) {
return (
matchesRestoreValue(item.sourceSessionId, target.bigFishSessionId) ||
matchesRestoreValue(item.workId, target.workId)
);
}
export function matchesSessionProfileWorkCreationUrlRestoreTarget(
item: SessionProfileWorkRestoreIdentity,
target: Pick<CreationUrlRestoreTarget, 'sessionId' | 'profileId' | 'workId'>,
) {
return (
matchesRestoreValue(item.sourceSessionId, target.sessionId) ||
matchesRestoreValue(item.profileId, target.profileId) ||
matchesRestoreValue(item.workId, target.workId)
);
}
export function matchesVisualNovelCreationUrlRestoreTarget(
item: ProfileRestoreWorkIdentity,
target: Pick<CreationUrlRestoreTarget, 'profileId'>,
) {
return matchesRestoreValue(item.profileId, target.profileId);
}
export function matchesBarkBattleCreationUrlRestoreTarget(
item: BarkBattleRestoreWorkIdentity,
target: Pick<CreationUrlRestoreTarget, 'workId' | 'draftId'>,
) {
return (
matchesRestoreValue(item.workId, target.workId) ||
matchesRestoreValue(item.draftId, target.draftId)
);
}
export function matchesBabyObjectMatchCreationUrlRestoreTarget(
item: BabyObjectMatchRestoreDraftIdentity,
target: Pick<CreationUrlRestoreTarget, 'profileId' | 'draftId' | 'workId'>,
) {
return (
matchesRestoreValue(item.profileId, target.profileId) ||
matchesRestoreValue(item.draftId, target.draftId) ||
matchesRestoreValue(item.profileId, target.workId)
);
}
export function resolveJumpHopCreationUrlRestoreStage(params: {
isGeneratingPath: boolean;
hasRestoredDraft: boolean;
hasRestoredWork: boolean;
}): SelectionStage {
if (params.isGeneratingPath) {
return 'jump-hop-generating';
}
return params.hasRestoredDraft || params.hasRestoredWork
? 'jump-hop-result'
: 'jump-hop-workspace';
}
export function resolveWoodenFishCreationUrlRestoreStage(params: {
isGeneratingPath: boolean;
hasRestoredDraft: boolean;
}): SelectionStage {
if (params.isGeneratingPath) {
return 'wooden-fish-generating';
}
return params.hasRestoredDraft
? 'wooden-fish-result'
: 'wooden-fish-workspace';
}
export type InitialCreationUrlRestoreDecision =
| { type: 'skip' }
| { type: 'mark-handled' }