Puzzle: support history images & partial generation
Allow history-generated image paths to be submitted where Data URLs were previously required and avoid treating partial/result-page generations as blocking the whole draft. Backend: resolve history /generated-* references via resolve_puzzle_reference_image_as_data_url and convert to PuzzleDownloadedImage; add PuzzleDownloadedImage::from_resolved_reference_image; extend draft handling to apply generated level metadata (auto-naming) and normalize generation_status to treat levels with images as ready. API: add shouldAutoNameLevel to action contracts and use it to request/refine generated level names. Spacetime/module and mappers: normalize completed level statuses when saving/reading so result-page background or per-level generation doesn't mask completed drafts. Frontend: expose resolver helpers, only mark a work as generating when no usable cover or ready level exists, keep level controls enabled during UI-background regeneration, and add tests covering history-image submission, auto-naming, and UI-background/partial-generation behaviors.
This commit is contained in:
@@ -641,7 +641,7 @@ function isCreationTypeReferenceCoverImageSrc(value?: string | null) {
|
||||
);
|
||||
}
|
||||
|
||||
function resolvePuzzleWorkCoverImageSrc(item: PuzzleWorkSummary) {
|
||||
export function resolvePuzzleWorkCoverImageSrc(item: PuzzleWorkSummary) {
|
||||
const directCoverImageSrc = normalizeCoverImageSrc(item.coverImageSrc);
|
||||
if (
|
||||
directCoverImageSrc &&
|
||||
@@ -651,33 +651,44 @@ function resolvePuzzleWorkCoverImageSrc(item: PuzzleWorkSummary) {
|
||||
}
|
||||
|
||||
for (const level of item.levels ?? []) {
|
||||
const levelCoverImageSrc = normalizeCoverImageSrc(level.coverImageSrc);
|
||||
if (
|
||||
levelCoverImageSrc &&
|
||||
!isCreationTypeReferenceCoverImageSrc(levelCoverImageSrc)
|
||||
) {
|
||||
return levelCoverImageSrc;
|
||||
const levelImageSrc = resolvePuzzleLevelCoverImageSrc(level);
|
||||
if (levelImageSrc) {
|
||||
return levelImageSrc;
|
||||
}
|
||||
}
|
||||
|
||||
const selectedCandidateImageSrc =
|
||||
level.selectedCandidateId && level.candidates.length > 0
|
||||
? normalizeCoverImageSrc(
|
||||
level.candidates.find(
|
||||
(candidate) => candidate.candidateId === level.selectedCandidateId,
|
||||
)?.imageSrc,
|
||||
return null;
|
||||
}
|
||||
|
||||
export function resolvePuzzleLevelCoverImageSrc(
|
||||
level: NonNullable<PuzzleWorkSummary['levels']>[number],
|
||||
) {
|
||||
const levelCoverImageSrc = normalizeCoverImageSrc(level.coverImageSrc);
|
||||
if (
|
||||
levelCoverImageSrc &&
|
||||
!isCreationTypeReferenceCoverImageSrc(levelCoverImageSrc)
|
||||
) {
|
||||
return levelCoverImageSrc;
|
||||
}
|
||||
|
||||
const selectedCandidateImageSrc =
|
||||
level.selectedCandidateId && level.candidates.length > 0
|
||||
? normalizeCoverImageSrc(
|
||||
level.candidates.find(
|
||||
(candidate) => candidate.candidateId === level.selectedCandidateId,
|
||||
)?.imageSrc,
|
||||
)
|
||||
: null;
|
||||
const fallbackCandidateImageSrc = normalizeCoverImageSrc(
|
||||
level.candidates[level.candidates.length - 1]?.imageSrc,
|
||||
);
|
||||
const candidateImageSrc = selectedCandidateImageSrc || fallbackCandidateImageSrc;
|
||||
: null;
|
||||
const fallbackCandidateImageSrc = normalizeCoverImageSrc(
|
||||
level.candidates[level.candidates.length - 1]?.imageSrc,
|
||||
);
|
||||
const candidateImageSrc = selectedCandidateImageSrc || fallbackCandidateImageSrc;
|
||||
|
||||
if (
|
||||
candidateImageSrc &&
|
||||
!isCreationTypeReferenceCoverImageSrc(candidateImageSrc)
|
||||
) {
|
||||
return candidateImageSrc;
|
||||
}
|
||||
if (
|
||||
candidateImageSrc &&
|
||||
!isCreationTypeReferenceCoverImageSrc(candidateImageSrc)
|
||||
) {
|
||||
return candidateImageSrc;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -804,12 +815,26 @@ function isPersistedCreationWorkGenerating(item: CreationWorkShelfItem) {
|
||||
case 'match3d':
|
||||
return item.source.item.generationStatus === 'generating';
|
||||
case 'puzzle':
|
||||
return item.source.item.generationStatus === 'generating';
|
||||
return isPersistedPuzzleDraftGenerating(item.source.item);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function isPersistedPuzzleDraftGenerating(item: PuzzleWorkSummary) {
|
||||
if (item.generationStatus !== 'generating') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const hasUsableCover = Boolean(resolvePuzzleWorkCoverImageSrc(item));
|
||||
const hasReadyLevel = (item.levels ?? []).some((level) =>
|
||||
Boolean(resolvePuzzleLevelCoverImageSrc(level)),
|
||||
);
|
||||
|
||||
// 中文注释:作品架“生成中”只表示初始草稿还没有可查看结果;结果页追加关卡或重绘局部图片不能锁住整张草稿卡。
|
||||
return !hasUsableCover && !hasReadyLevel;
|
||||
}
|
||||
|
||||
function buildRpgWorkShelfActions(
|
||||
item: CustomWorldWorkSummary,
|
||||
adapter: RpgWorkShelfAdapter,
|
||||
|
||||
Reference in New Issue
Block a user