合并 master 到图片画布素材分支
保留图片画布素材元数据迁移记录 合入 Pingora 网关与生产运维更新 解决外部生成 worker 测试夹具时间戳冲突
This commit is contained in:
@@ -167,6 +167,7 @@ import {
|
||||
fetchCreationEntryConfig,
|
||||
} from '../../services/creationEntryConfigService';
|
||||
import {
|
||||
type CreationUrlState,
|
||||
clearCreationUrlState,
|
||||
readCreationUrlState,
|
||||
writeCreationUrlState,
|
||||
@@ -501,6 +502,7 @@ import {
|
||||
type PendingDraftShelfKind,
|
||||
type PendingDraftShelfMap,
|
||||
type PendingDraftShelfMetadata,
|
||||
buildPuzzleDraftOpenNoticeKeys,
|
||||
resolveBigFishDraftOpenIntent,
|
||||
resolveJumpHopDraftOpenIntent,
|
||||
resolveMatch3DDraftOpenIntent,
|
||||
@@ -912,6 +914,29 @@ function pushPuzzleResultHistoryEntry(
|
||||
writeCreationUrlState(buildPuzzleCreationUrlState(session));
|
||||
}
|
||||
|
||||
function pushPuzzleGeneratingHistoryEntry(
|
||||
session: PuzzleAgentSessionSnapshot | null,
|
||||
fallbackState?: CreationUrlState,
|
||||
) {
|
||||
pushAppHistoryPath('/creation/puzzle/generating');
|
||||
writeCreationUrlState(
|
||||
session ? buildPuzzleCreationUrlState(session) : (fallbackState ?? {}),
|
||||
);
|
||||
}
|
||||
|
||||
function buildPuzzleCreationUrlStateFromWork(
|
||||
item: PuzzleWorkSummary,
|
||||
): CreationUrlState {
|
||||
const sourceSessionId = item.sourceSessionId?.trim() || null;
|
||||
|
||||
return {
|
||||
sessionId: sourceSessionId,
|
||||
profileId:
|
||||
item.profileId?.trim() || buildPuzzleResultProfileId(sourceSessionId),
|
||||
workId: item.workId?.trim() || buildPuzzleResultWorkId(sourceSessionId),
|
||||
};
|
||||
}
|
||||
|
||||
const PUZZLE_DRAFT_GENERATION_POINT_COST = 2;
|
||||
const PUZZLE_BACKGROUND_ACTION_POLL_INTERVAL_MS = 3000;
|
||||
const PUZZLE_BACKGROUND_ACTION_MAX_POLL_ATTEMPTS = 160;
|
||||
@@ -4291,6 +4316,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
);
|
||||
activePuzzleGenerationSessionIdRef.current =
|
||||
response.session.sessionId;
|
||||
pushPuzzleGeneratingHistoryEntry(response.session);
|
||||
setSelectionStage('puzzle-generating');
|
||||
markDraftGenerating('puzzle', [
|
||||
response.session.sessionId,
|
||||
@@ -4435,6 +4461,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
);
|
||||
selectionStageRef.current = 'puzzle-generating';
|
||||
activePuzzleGenerationSessionIdRef.current = session.sessionId;
|
||||
pushPuzzleGeneratingHistoryEntry(session);
|
||||
setSelectionStage('puzzle-generating');
|
||||
const nextGenerationState = createPuzzleDraftGenerationStateFromPayload(
|
||||
formPayload ?? buildPuzzleFormPayloadFromSession(session),
|
||||
@@ -5707,6 +5734,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
}
|
||||
selectionStageRef.current = 'puzzle-generating';
|
||||
activePuzzleGenerationSessionIdRef.current = nextSession.sessionId;
|
||||
pushPuzzleGeneratingHistoryEntry(nextSession);
|
||||
setSelectionStage('puzzle-generating');
|
||||
|
||||
try {
|
||||
@@ -12110,6 +12138,66 @@ export function PlatformEntryFlowShellImpl({
|
||||
setPuzzleRun(null);
|
||||
setPuzzleRuntimeAuthMode('default');
|
||||
setSelectedPuzzleDetail(null);
|
||||
const restorePuzzleGeneratingSession = (
|
||||
latestSession: PuzzleAgentSessionSnapshot,
|
||||
) => {
|
||||
const payload = buildPuzzleFormPayloadFromSession(latestSession);
|
||||
const startedAtMs = resolveMiniGameDraftGenerationStartedAtMs(
|
||||
latestSession.updatedAt,
|
||||
);
|
||||
const baseGenerationState =
|
||||
createMiniGameDraftGenerationStateForRestoredDraft(
|
||||
'puzzle',
|
||||
{
|
||||
puzzleAiRedraw: payload.aiRedraw ?? true,
|
||||
puzzleProgressPercent:
|
||||
latestSession.draft && !latestSession.draft.formDraft
|
||||
? latestSession.progressPercent
|
||||
: undefined,
|
||||
},
|
||||
startedAtMs,
|
||||
);
|
||||
const generationState = mergePuzzleSessionProgressIntoGenerationState(
|
||||
baseGenerationState,
|
||||
latestSession,
|
||||
);
|
||||
puzzleFlow.setSession(latestSession);
|
||||
setPuzzleFormDraftPayload(payload);
|
||||
setPuzzleGenerationState(generationState);
|
||||
setPuzzleBackgroundCompileTasks((current) => ({
|
||||
...current,
|
||||
[latestSession.sessionId]: {
|
||||
session: latestSession,
|
||||
payload,
|
||||
generationState,
|
||||
error: null,
|
||||
},
|
||||
}));
|
||||
enterCreateTab();
|
||||
selectionStageRef.current = 'puzzle-generating';
|
||||
activePuzzleGenerationSessionIdRef.current = latestSession.sessionId;
|
||||
pushPuzzleGeneratingHistoryEntry(latestSession);
|
||||
setSelectionStage('puzzle-generating');
|
||||
};
|
||||
const openPuzzleReadyResultSession = (
|
||||
latestSession: PuzzleAgentSessionSnapshot,
|
||||
) => {
|
||||
const payload = buildPuzzleFormPayloadFromSession(latestSession);
|
||||
puzzleFlow.setSession(latestSession);
|
||||
setPuzzleFormDraftPayload(payload);
|
||||
setPuzzleGenerationState(null);
|
||||
setPuzzleBackgroundCompileTasks((current) => {
|
||||
const next = { ...current };
|
||||
delete next[latestSession.sessionId];
|
||||
return next;
|
||||
});
|
||||
enterCreateTab();
|
||||
selectionStageRef.current = 'puzzle-result';
|
||||
activePuzzleGenerationSessionIdRef.current = null;
|
||||
markDraftNoticeSeen(buildPuzzleDraftOpenNoticeKeys(item));
|
||||
pushPuzzleResultHistoryEntry(latestSession);
|
||||
setSelectionStage('puzzle-result');
|
||||
};
|
||||
|
||||
if (openIntent.type === 'open-published-detail') {
|
||||
await openPuzzleDetail(item.profileId, { tab: 'create' });
|
||||
@@ -12175,6 +12263,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
enterCreateTab();
|
||||
selectionStageRef.current = 'puzzle-generating';
|
||||
activePuzzleGenerationSessionIdRef.current = sourceSessionId;
|
||||
pushPuzzleGeneratingHistoryEntry(
|
||||
failedSession,
|
||||
buildPuzzleCreationUrlStateFromWork(item),
|
||||
);
|
||||
setPuzzleGenerationState(failedGenerationState);
|
||||
setSelectionStage('puzzle-generating');
|
||||
return;
|
||||
@@ -12189,6 +12281,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
enterCreateTab();
|
||||
selectionStageRef.current = 'puzzle-generating';
|
||||
activePuzzleGenerationSessionIdRef.current = sourceSessionId;
|
||||
pushPuzzleGeneratingHistoryEntry(
|
||||
puzzleSession ?? backgroundTask?.session ?? null,
|
||||
buildPuzzleCreationUrlStateFromWork(item),
|
||||
);
|
||||
setPuzzleGenerationState(rebasedGenerationState);
|
||||
if (backgroundTask) {
|
||||
setPuzzleBackgroundCompileTasks((current) => ({
|
||||
@@ -12219,6 +12315,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
enterCreateTab();
|
||||
selectionStageRef.current = 'puzzle-generating';
|
||||
activePuzzleGenerationSessionIdRef.current = sourceSessionId;
|
||||
pushPuzzleGeneratingHistoryEntry(rebasedTask.session);
|
||||
setSelectionStage('puzzle-generating');
|
||||
return;
|
||||
}
|
||||
@@ -12227,42 +12324,14 @@ export function PlatformEntryFlowShellImpl({
|
||||
try {
|
||||
const { session: latestSession } =
|
||||
await getPuzzleAgentSession(sourceSessionId);
|
||||
const payload = buildPuzzleFormPayloadFromSession(latestSession);
|
||||
const startedAtMs = resolveMiniGameDraftGenerationStartedAtMs(
|
||||
latestSession.updatedAt,
|
||||
);
|
||||
const baseGenerationState =
|
||||
createMiniGameDraftGenerationStateForRestoredDraft(
|
||||
'puzzle',
|
||||
{
|
||||
puzzleAiRedraw: payload.aiRedraw ?? true,
|
||||
puzzleProgressPercent:
|
||||
latestSession.draft && !latestSession.draft.formDraft
|
||||
? latestSession.progressPercent
|
||||
: undefined,
|
||||
},
|
||||
startedAtMs,
|
||||
);
|
||||
const generationState = mergePuzzleSessionProgressIntoGenerationState(
|
||||
baseGenerationState,
|
||||
latestSession,
|
||||
);
|
||||
puzzleFlow.setSession(latestSession);
|
||||
setPuzzleFormDraftPayload(payload);
|
||||
setPuzzleGenerationState(generationState);
|
||||
setPuzzleBackgroundCompileTasks((current) => ({
|
||||
...current,
|
||||
[latestSession.sessionId]: {
|
||||
session: latestSession,
|
||||
payload,
|
||||
generationState,
|
||||
error: null,
|
||||
},
|
||||
}));
|
||||
enterCreateTab();
|
||||
selectionStageRef.current = 'puzzle-generating';
|
||||
activePuzzleGenerationSessionIdRef.current = sourceSessionId;
|
||||
setSelectionStage('puzzle-generating');
|
||||
if (
|
||||
!isPuzzleFormOnlyDraft(latestSession) &&
|
||||
isPuzzleCompileActionReady(latestSession)
|
||||
) {
|
||||
openPuzzleReadyResultSession(latestSession);
|
||||
return;
|
||||
}
|
||||
restorePuzzleGeneratingSession(latestSession);
|
||||
return;
|
||||
} catch (error) {
|
||||
setPuzzleError(
|
||||
@@ -12288,6 +12357,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
setSelectionStage('puzzle-agent-workspace');
|
||||
} else {
|
||||
setPuzzleFormDraftPayload(null);
|
||||
pushPuzzleResultHistoryEntry(restoredSession);
|
||||
selectionStageRef.current = 'puzzle-result';
|
||||
activePuzzleGenerationSessionIdRef.current = null;
|
||||
setSelectionStage('puzzle-result');
|
||||
}
|
||||
},
|
||||
[
|
||||
|
||||
@@ -99,6 +99,58 @@ describe('platformDraftGenerationShelfModel', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('resolvePuzzleDraftOpenIntent ignores stale generating notice after puzzle draft is ready', () => {
|
||||
expect(
|
||||
resolvePuzzleDraftOpenIntent({
|
||||
item: buildPuzzleWork({
|
||||
generationStatus: 'ready',
|
||||
coverImageSrc: '/generated-puzzle-assets/session/cover.png',
|
||||
levels: [
|
||||
{
|
||||
levelId: 'level-1',
|
||||
levelName: '金色数字舞台',
|
||||
pictureDescription: '123456',
|
||||
pictureReference: null,
|
||||
candidates: [],
|
||||
selectedCandidateId: null,
|
||||
coverImageSrc: '/generated-puzzle-assets/session/cover.png',
|
||||
coverAssetId: 'asset-1',
|
||||
generationStatus: 'ready',
|
||||
},
|
||||
],
|
||||
}),
|
||||
notices: {
|
||||
'puzzle:puzzle-session-base': {
|
||||
status: 'generating',
|
||||
seen: false,
|
||||
},
|
||||
},
|
||||
generation: emptyGenerationFacts(),
|
||||
}),
|
||||
).toMatchObject({
|
||||
type: 'restore-draft',
|
||||
});
|
||||
});
|
||||
|
||||
test('resolvePuzzleDraftOpenIntent restores generating page from notice even before persisted status catches up', () => {
|
||||
expect(
|
||||
resolvePuzzleDraftOpenIntent({
|
||||
item: buildPuzzleWork({
|
||||
generationStatus: 'idle',
|
||||
}),
|
||||
notices: {
|
||||
'puzzle:puzzle-session-base': {
|
||||
status: 'generating',
|
||||
seen: false,
|
||||
},
|
||||
},
|
||||
generation: emptyGenerationFacts(),
|
||||
}),
|
||||
).toMatchObject({
|
||||
type: 'restore-generating',
|
||||
});
|
||||
});
|
||||
|
||||
test('resolveMatch3DDraftOpenIntent opens published work detail unless forced into draft', () => {
|
||||
const item = buildMatch3DWork({
|
||||
publicationStatus: 'published',
|
||||
|
||||
@@ -595,7 +595,7 @@ describe('PuzzleResultView', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('adds and deletes levels from the list', async () => {
|
||||
test('keeps a blank new level local until the creator edits it', async () => {
|
||||
vi.useFakeTimers();
|
||||
vi.mocked(puzzleWorksService.updatePuzzleWork).mockResolvedValue({
|
||||
item: {} as never,
|
||||
@@ -621,25 +621,36 @@ describe('PuzzleResultView', () => {
|
||||
expect(
|
||||
within(dialog).queryByRole('button', { name: /关卡测试/u }),
|
||||
).toBeNull();
|
||||
fireEvent.click(screen.getByLabelText('关闭关卡详情'));
|
||||
expect(screen.getAllByText('第2关').length).toBeGreaterThan(0);
|
||||
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
});
|
||||
|
||||
expect(puzzleWorksService.updatePuzzleWork).toHaveBeenLastCalledWith(
|
||||
expect(screen.getByRole('dialog', { name: '关卡详情' })).toBeTruthy();
|
||||
expect(screen.queryByText('保存中')).toBeNull();
|
||||
expect(puzzleWorksService.updatePuzzleWork).not.toHaveBeenCalled();
|
||||
|
||||
fireEvent.change(within(dialog).getByLabelText('关卡名称'), {
|
||||
target: { value: '钟楼猫街' },
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
});
|
||||
|
||||
expect(puzzleWorksService.updatePuzzleWork).toHaveBeenCalledWith(
|
||||
'puzzle-profile-session-1',
|
||||
expect.objectContaining({
|
||||
levels: expect.arrayContaining([
|
||||
expect.objectContaining({ levelId: 'puzzle-level-1' }),
|
||||
expect.objectContaining({ levelName: '' }),
|
||||
expect.objectContaining({ levelName: '钟楼猫街' }),
|
||||
]),
|
||||
}),
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByLabelText('删除关卡 第2关'));
|
||||
expect(screen.queryByText('第2关')).toBeNull();
|
||||
fireEvent.click(screen.getByLabelText('关闭关卡详情'));
|
||||
fireEvent.click(screen.getByLabelText('删除关卡 钟楼猫街'));
|
||||
expect(screen.queryByText('钟楼猫街')).toBeNull();
|
||||
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
@@ -657,6 +668,48 @@ describe('PuzzleResultView', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('keeps the blank level dialog open across incoming draft refreshes', async () => {
|
||||
vi.useFakeTimers();
|
||||
const base = createSession();
|
||||
const { rerender } = render(
|
||||
<PuzzleResultView
|
||||
session={base}
|
||||
profileId="puzzle-profile-session-1"
|
||||
onBack={() => {}}
|
||||
onExecuteAction={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
openPuzzleLevelsTab();
|
||||
fireEvent.click(screen.getByRole('button', { name: /新增关卡/u }));
|
||||
expect(screen.getByRole('dialog', { name: '关卡详情' })).toBeTruthy();
|
||||
|
||||
rerender(
|
||||
<PuzzleResultView
|
||||
session={{
|
||||
...base,
|
||||
updatedAt: '2026-04-26T10:00:01.000Z',
|
||||
}}
|
||||
profileId="puzzle-profile-session-1"
|
||||
onBack={() => {}}
|
||||
onExecuteAction={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
const dialog = screen.getByRole('dialog', { name: '关卡详情' });
|
||||
expect(
|
||||
(within(dialog).getByLabelText('关卡名称') as HTMLInputElement).value,
|
||||
).toBe('');
|
||||
expect(screen.getByLabelText('删除关卡 第2关')).toBeTruthy();
|
||||
expect(screen.queryByText('保存中')).toBeNull();
|
||||
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
});
|
||||
|
||||
expect(puzzleWorksService.updatePuzzleWork).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('generates image for a newly added level with the current levels snapshot', () => {
|
||||
vi.spyOn(Date, 'now').mockReturnValue(1_775_000_000_000);
|
||||
const onExecuteAction = vi.fn();
|
||||
|
||||
@@ -166,6 +166,96 @@ function resolveLevelFormalImageSrc(level: PuzzleDraftLevel) {
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeOptionalPuzzleLevelText(value?: string | null) {
|
||||
return value?.trim() || null;
|
||||
}
|
||||
|
||||
function normalizePuzzleLevelForSave(level: PuzzleDraftLevel) {
|
||||
return {
|
||||
...level,
|
||||
levelName: level.levelName.trim(),
|
||||
pictureDescription: level.pictureDescription.trim(),
|
||||
pictureReference: normalizeOptionalPuzzleLevelText(level.pictureReference),
|
||||
uiBackgroundPrompt: normalizeOptionalPuzzleLevelText(
|
||||
level.uiBackgroundPrompt,
|
||||
),
|
||||
uiBackgroundImageSrc: normalizeOptionalPuzzleLevelText(
|
||||
level.uiBackgroundImageSrc,
|
||||
),
|
||||
uiBackgroundImageObjectKey: normalizeOptionalPuzzleLevelText(
|
||||
level.uiBackgroundImageObjectKey,
|
||||
),
|
||||
levelSceneImageSrc: normalizeOptionalPuzzleLevelText(
|
||||
level.levelSceneImageSrc,
|
||||
),
|
||||
levelSceneImageObjectKey: normalizeOptionalPuzzleLevelText(
|
||||
level.levelSceneImageObjectKey,
|
||||
),
|
||||
uiSpritesheetImageSrc: normalizeOptionalPuzzleLevelText(
|
||||
level.uiSpritesheetImageSrc,
|
||||
),
|
||||
uiSpritesheetImageObjectKey: normalizeOptionalPuzzleLevelText(
|
||||
level.uiSpritesheetImageObjectKey,
|
||||
),
|
||||
levelBackgroundImageSrc: normalizeOptionalPuzzleLevelText(
|
||||
level.levelBackgroundImageSrc,
|
||||
),
|
||||
levelBackgroundImageObjectKey: normalizeOptionalPuzzleLevelText(
|
||||
level.levelBackgroundImageObjectKey,
|
||||
),
|
||||
generationStatus: level.generationStatus || 'idle',
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeDraftEditStateForSave(state: DraftEditState): DraftEditState {
|
||||
return {
|
||||
...state,
|
||||
workTitle: state.workTitle.trim(),
|
||||
workDescription: state.workDescription.trim(),
|
||||
themeTags: normalizeThemeTagInput(state.themeTags.join(',')),
|
||||
levels: state.levels.map(normalizePuzzleLevelForSave),
|
||||
};
|
||||
}
|
||||
|
||||
function isBlankPuzzleLevel(level: PuzzleDraftLevel) {
|
||||
return (
|
||||
!level.levelName.trim() &&
|
||||
!level.pictureDescription.trim() &&
|
||||
!level.pictureReference?.trim() &&
|
||||
!level.uiBackgroundPrompt?.trim() &&
|
||||
!level.uiBackgroundImageSrc?.trim() &&
|
||||
!level.uiBackgroundImageObjectKey?.trim() &&
|
||||
!level.levelSceneImageSrc?.trim() &&
|
||||
!level.levelSceneImageObjectKey?.trim() &&
|
||||
!level.uiSpritesheetImageSrc?.trim() &&
|
||||
!level.uiSpritesheetImageObjectKey?.trim() &&
|
||||
!level.levelBackgroundImageSrc?.trim() &&
|
||||
!level.levelBackgroundImageObjectKey?.trim() &&
|
||||
!level.selectedCandidateId?.trim() &&
|
||||
!level.coverImageSrc?.trim() &&
|
||||
!level.coverAssetId?.trim() &&
|
||||
!level.backgroundMusic &&
|
||||
level.candidates.length === 0 &&
|
||||
(level.generationStatus || 'idle') === 'idle'
|
||||
);
|
||||
}
|
||||
|
||||
function removeUnsavedBlankPuzzleLevels(
|
||||
state: DraftEditState,
|
||||
originalState: DraftEditState,
|
||||
) {
|
||||
const originalLevelIds = new Set(
|
||||
originalState.levels.map((level) => level.levelId),
|
||||
);
|
||||
return {
|
||||
...state,
|
||||
levels: state.levels.filter(
|
||||
(level) =>
|
||||
originalLevelIds.has(level.levelId) || !isBlankPuzzleLevel(level),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function buildFallbackLevelFromDraft(
|
||||
draft: PuzzleResultDraft,
|
||||
): PuzzleDraftLevel {
|
||||
@@ -261,13 +351,20 @@ function mergeDraftEditStateWithIncomingState(
|
||||
incomingLevel?.generationStatus !== 'generating'
|
||||
);
|
||||
});
|
||||
const hasLocalBlankLevel = currentState.levels.some(
|
||||
(level) =>
|
||||
!incomingLevelsById.has(level.levelId) && isBlankPuzzleLevel(level),
|
||||
);
|
||||
|
||||
if (!shouldPreserveLocalEdits) {
|
||||
if (!shouldPreserveLocalEdits && !hasLocalBlankLevel) {
|
||||
return incomingState;
|
||||
}
|
||||
|
||||
const mergedLevels = currentState.levels.map((level) => {
|
||||
const incomingLevel = incomingLevelsById.get(level.levelId);
|
||||
if (!incomingLevel && isBlankPuzzleLevel(level)) {
|
||||
return level;
|
||||
}
|
||||
if (
|
||||
!incomingLevel ||
|
||||
level.generationStatus !== 'generating' ||
|
||||
@@ -1411,36 +1508,17 @@ export function PuzzleResultView({
|
||||
return;
|
||||
}
|
||||
|
||||
const normalizedState: DraftEditState = {
|
||||
...editState,
|
||||
workTitle: editState.workTitle.trim(),
|
||||
workDescription: editState.workDescription.trim(),
|
||||
themeTags: normalizeThemeTagInput(editState.themeTags.join(',')),
|
||||
levels: editState.levels.map((level) => ({
|
||||
...level,
|
||||
levelName: level.levelName.trim(),
|
||||
pictureDescription: level.pictureDescription.trim(),
|
||||
pictureReference: level.pictureReference?.trim() || null,
|
||||
uiBackgroundPrompt: level.uiBackgroundPrompt?.trim() || null,
|
||||
uiBackgroundImageSrc: level.uiBackgroundImageSrc?.trim() || null,
|
||||
uiBackgroundImageObjectKey:
|
||||
level.uiBackgroundImageObjectKey?.trim() || null,
|
||||
levelSceneImageSrc: level.levelSceneImageSrc?.trim() || null,
|
||||
levelSceneImageObjectKey:
|
||||
level.levelSceneImageObjectKey?.trim() || null,
|
||||
uiSpritesheetImageSrc: level.uiSpritesheetImageSrc?.trim() || null,
|
||||
uiSpritesheetImageObjectKey:
|
||||
level.uiSpritesheetImageObjectKey?.trim() || null,
|
||||
levelBackgroundImageSrc: level.levelBackgroundImageSrc?.trim() || null,
|
||||
levelBackgroundImageObjectKey:
|
||||
level.levelBackgroundImageObjectKey?.trim() || null,
|
||||
generationStatus: level.generationStatus || 'idle',
|
||||
})),
|
||||
};
|
||||
const originalState =
|
||||
savedEditStateRef.current ?? createDraftEditState(draft);
|
||||
const normalizedOriginalState =
|
||||
normalizeDraftEditStateForSave(originalState);
|
||||
const normalizedState = removeUnsavedBlankPuzzleLevels(
|
||||
normalizeDraftEditStateForSave(editState),
|
||||
normalizedOriginalState,
|
||||
);
|
||||
const changed =
|
||||
JSON.stringify(normalizedState) !== JSON.stringify(originalState);
|
||||
JSON.stringify(normalizedState) !==
|
||||
JSON.stringify(normalizedOriginalState);
|
||||
|
||||
if (!changed || normalizedState.levels.length <= 0) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user