继续收口工具弹窗与分段切换预设
新增 PlatformToolModalShell 承接白底工具弹窗壳层和固定可访问名称 新增 PlatformSegmentedTabPresets 沉淀频道下划线、创作 pill rail 与二列 option segment 迁移拼图、抓大鹅、历史素材弹窗和首页 / 作品架 / 充值切换的重复组件写法 同步 PlatformUiKit 文档和 Hermes 决策记录
This commit is contained in:
@@ -7,7 +7,6 @@ import {
|
||||
Trash2,
|
||||
} from 'lucide-react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
import type { CreativeDraftEditResult } from '../../../packages/shared/src/contracts/creativeAgent';
|
||||
import type { PuzzleAgentActionRequest } from '../../../packages/shared/src/contracts/puzzleAgentActions';
|
||||
@@ -19,7 +18,6 @@ import type { PuzzleAgentSessionSnapshot } from '../../../packages/shared/src/co
|
||||
import { updatePuzzleWork } from '../../services/puzzle-works';
|
||||
import { getPuzzleHistoryAssetReferenceLabel } from '../../services/puzzle-works/puzzleHistoryAsset';
|
||||
import { readPuzzleReferenceImageAsDataUrl } from '../../services/puzzleReferenceImage';
|
||||
import { useAuthUi } from '../auth/AuthUiContext';
|
||||
import { CreativeImageInputPanel } from '../common/CreativeImageInputPanel';
|
||||
import { PlatformActionButton } from '../common/PlatformActionButton';
|
||||
import { PlatformBackActionButton } from '../common/PlatformBackActionButton';
|
||||
@@ -28,7 +26,6 @@ import { PlatformFieldLabel } from '../common/PlatformFieldLabel';
|
||||
import { PlatformIconBadge } from '../common/PlatformIconBadge';
|
||||
import { PlatformIconButton } from '../common/PlatformIconButton';
|
||||
import { PlatformMediaFrame } from '../common/PlatformMediaFrame';
|
||||
import { PlatformModalCloseButton } from '../common/PlatformModalCloseButton';
|
||||
import { PlatformMudPointConfirmDialog } from '../common/PlatformMudPointConfirmDialog';
|
||||
import { PlatformPillBadge } from '../common/PlatformPillBadge';
|
||||
import { PlatformProgressBar } from '../common/PlatformProgressBar';
|
||||
@@ -37,6 +34,7 @@ import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
|
||||
import { PlatformSubpanel } from '../common/PlatformSubpanel';
|
||||
import { PlatformTagEditor } from '../common/PlatformTagEditor';
|
||||
import { PlatformTextField } from '../common/PlatformTextField';
|
||||
import { PlatformToolModalShell } from '../common/PlatformToolModalShell';
|
||||
import { PlatformUploadPreviewCard } from '../common/PlatformUploadPreviewCard';
|
||||
import PuzzleHistoryAssetPickerDialog from '../unified-creation/shared/PuzzleHistoryAssetPickerDialog';
|
||||
import {
|
||||
@@ -513,7 +511,6 @@ function PuzzleLevelDetailDialog({
|
||||
onLevelChange: (nextLevel: PuzzleDraftLevel) => void;
|
||||
onStartTestRun?: (levelId: string) => void;
|
||||
}) {
|
||||
const platformTheme = useAuthUi()?.platformTheme ?? 'light';
|
||||
const [referenceImageSrc, setReferenceImageSrc] = useState('');
|
||||
const [referenceImageLabel, setReferenceImageLabel] = useState('');
|
||||
const [referenceImageError, setReferenceImageError] = useState<string | null>(
|
||||
@@ -622,164 +619,20 @@ function PuzzleLevelDetailDialog({
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof document === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
className={`platform-theme platform-theme--${platformTheme} platform-overlay fixed inset-0 z-[138] flex items-end justify-center p-3 backdrop-blur-sm sm:items-center sm:p-4`}
|
||||
onClick={(event) => {
|
||||
if (event.target === event.currentTarget) {
|
||||
onClose();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="关卡详情"
|
||||
className="platform-modal-shell platform-remap-surface flex max-h-[min(94vh,50rem)] w-full max-w-[56rem] flex-col overflow-hidden rounded-t-[1.75rem] shadow-[0_24px_80px_rgba(0,0,0,0.55)] sm:rounded-[1.75rem]"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3 border-b border-[var(--platform-subpanel-border)] px-5 py-4">
|
||||
<div className="min-w-0 truncate text-base font-semibold text-[var(--platform-text-strong)]">
|
||||
{level.levelName || '关卡详情'}
|
||||
</div>
|
||||
<PlatformModalCloseButton
|
||||
variant="platformIcon"
|
||||
label="关闭关卡详情"
|
||||
onClick={onClose}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="min-h-0 flex-1 overflow-y-auto px-5 py-4">
|
||||
<div className="puzzle-level-detail-list divide-y divide-[var(--platform-subpanel-border)]">
|
||||
<section className="grid gap-2 pb-4 sm:grid-cols-[7.5rem_minmax(0,1fr)] sm:items-center">
|
||||
<label
|
||||
htmlFor={`puzzle-level-name-${level.levelId}`}
|
||||
className="contents"
|
||||
>
|
||||
<PlatformFieldLabel variant="section">
|
||||
关卡名称
|
||||
</PlatformFieldLabel>
|
||||
</label>
|
||||
<PlatformTextField
|
||||
id={`puzzle-level-name-${level.levelId}`}
|
||||
value={level.levelName}
|
||||
disabled={isBusy}
|
||||
onChange={(event) =>
|
||||
onLevelChange({ ...level, levelName: event.target.value })
|
||||
}
|
||||
size="lg"
|
||||
density="roomy"
|
||||
aria-label="关卡名称"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section className="pt-4">
|
||||
<CreativeImageInputPanel
|
||||
className="puzzle-level-detail-image-editor"
|
||||
disabled={isBusy || generationProgress.isGenerating}
|
||||
isSubmitting={generationProgress.isGenerating}
|
||||
uploadedImageSrc={displayImageSrc}
|
||||
uploadedImageAlt={displayImageAlt}
|
||||
uploadedImageRefreshKey={`${imageRefreshKey}:${level.levelId}`}
|
||||
canRemoveMainImage={Boolean(effectiveReferenceImageSrc)}
|
||||
canToggleAiRedraw={Boolean(effectiveReferenceImageSrc)}
|
||||
canUploadPromptReferences={!effectiveReferenceImageSrc}
|
||||
mainImageMeta={
|
||||
shouldShowReferenceMeta ? (
|
||||
<PlatformUploadPreviewCard
|
||||
layout="inline"
|
||||
imageSrc={effectiveReferenceImageSrc}
|
||||
imageAlt="拼图参考图"
|
||||
caption={referenceImageLabel || '已选择参考图'}
|
||||
removeLabel="移除参考图"
|
||||
resolveAsset
|
||||
className="bg-white/72 py-3"
|
||||
imageShellClassName="rounded-[0.85rem] bg-[var(--platform-subpanel-fill)]"
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
mainImageInputId={`puzzle-level-reference-upload-${level.levelId}`}
|
||||
promptTextareaId={`puzzle-level-picture-description-${level.levelId}`}
|
||||
prompt={level.pictureDescription}
|
||||
promptLabel={
|
||||
effectiveReferenceImageSrc
|
||||
? '画面AI重绘要求(提示词)'
|
||||
: '画面描述'
|
||||
}
|
||||
promptRows={7}
|
||||
aiRedraw={aiRedraw}
|
||||
promptReferenceImages={promptReferenceImages}
|
||||
promptReferenceLimit={PUZZLE_LEVEL_PROMPT_REFERENCE_LIMIT}
|
||||
imageLimitHint="图片≤6MB"
|
||||
imageModelPicker={
|
||||
<PuzzleImageModelPicker
|
||||
value={imageModel}
|
||||
disabled={isBusy || generationProgress.isGenerating}
|
||||
onChange={setImageModel}
|
||||
/>
|
||||
}
|
||||
inputError={referenceImageError}
|
||||
submitLabel={hasFormalImage ? '重新生成画面' : '生成画面'}
|
||||
submitCostLabel={`消耗${PUZZLE_IMAGE_GENERATION_POINT_COST}泥点`}
|
||||
submitDisabled={
|
||||
isBusy ||
|
||||
generationProgress.isGenerating ||
|
||||
(!level.pictureDescription.trim() &&
|
||||
!effectiveReferenceImageSrc)
|
||||
}
|
||||
labels={{
|
||||
imageField: '画面图',
|
||||
uploadImage: '上传参考图',
|
||||
replaceImage: '更换参考图',
|
||||
emptyImageHint: '上传图片/填写画面描述',
|
||||
removeImage: '移除参考图',
|
||||
removeImageConfirmTitle: '移除参考图?',
|
||||
removeImageConfirmBody: '移除后可重新上传或选择历史图片。',
|
||||
promptReferenceUpload: '上传描述参考图',
|
||||
promptReferencePreviewAlt: '参考图预览',
|
||||
closePromptReferencePreview: '关闭参考图预览',
|
||||
previewMainImage: '查看关卡图片',
|
||||
closeMainImagePreview: '关闭关卡图片预览',
|
||||
history: '选择历史图片',
|
||||
}}
|
||||
onMainImageFileSelect={(file) => {
|
||||
void handleReferenceImageFile(file);
|
||||
}}
|
||||
onMainImageRemove={() => {
|
||||
setReferenceImageSrc('');
|
||||
setReferenceImageLabel('');
|
||||
setReferenceImageError(null);
|
||||
setAiRedraw(true);
|
||||
onLevelChange({ ...level, pictureReference: null });
|
||||
}}
|
||||
onAiRedrawChange={setAiRedraw}
|
||||
onPromptChange={(value) =>
|
||||
onLevelChange({
|
||||
...level,
|
||||
pictureDescription: value,
|
||||
})
|
||||
}
|
||||
onPromptReferenceFilesSelect={(files) => {
|
||||
void handlePromptReferenceImageFiles(files);
|
||||
}}
|
||||
onPromptReferenceRemove={(referenceId) => {
|
||||
setPromptReferenceImages((current) =>
|
||||
current.filter((image) => image.id !== referenceId),
|
||||
);
|
||||
setReferenceImageError(null);
|
||||
}}
|
||||
onHistoryClick={() => setIsHistoryPickerOpen(true)}
|
||||
onSubmit={() => setIsCostConfirmOpen(true)}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="shrink-0 space-y-3 border-t border-[var(--platform-subpanel-border)] bg-[var(--platform-page-fill)] px-5 py-4 pb-[calc(env(safe-area-inset-bottom,0px)+1rem)]">
|
||||
return (
|
||||
<PlatformToolModalShell
|
||||
open
|
||||
title={level.levelName || '关卡详情'}
|
||||
ariaLabel="关卡详情"
|
||||
onClose={onClose}
|
||||
closeLabel="关闭关卡详情"
|
||||
zIndexClassName="z-[138]"
|
||||
size="xl"
|
||||
panelClassName="!max-h-[min(94vh,50rem)] !max-w-[56rem]"
|
||||
titleClassName="truncate"
|
||||
footerClassName="!block shrink-0 space-y-3 bg-[var(--platform-page-fill)] pb-[calc(env(safe-area-inset-bottom,0px)+1rem)]"
|
||||
footer={
|
||||
<>
|
||||
{onStartTestRun && hasFormalImage ? (
|
||||
<PlatformActionButton
|
||||
disabled={isBusy}
|
||||
@@ -807,37 +660,160 @@ function PuzzleLevelDetailDialog({
|
||||
</div>
|
||||
</PlatformProgressBar>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<PlatformMudPointConfirmDialog
|
||||
open={isCostConfirmOpen}
|
||||
points={PUZZLE_IMAGE_GENERATION_POINT_COST}
|
||||
onClose={() => setIsCostConfirmOpen(false)}
|
||||
onConfirm={executeGeneration}
|
||||
confirmDisabled={isBusy || generationProgress.isGenerating}
|
||||
portal={false}
|
||||
overlayClassName="absolute z-20 bg-black/45"
|
||||
panelClassName="platform-remap-surface rounded-[1.5rem] shadow-[0_24px_80px_rgba(0,0,0,0.45)]"
|
||||
/>
|
||||
|
||||
{isHistoryPickerOpen ? (
|
||||
<PuzzleHistoryAssetPickerDialog
|
||||
isBusy={isBusy}
|
||||
onClose={() => setIsHistoryPickerOpen(false)}
|
||||
onSelect={(asset) => {
|
||||
setReferenceImageSrc(asset.imageSrc);
|
||||
setReferenceImageLabel(
|
||||
getPuzzleHistoryAssetReferenceLabel(asset.imageSrc),
|
||||
);
|
||||
setAiRedraw(true);
|
||||
setReferenceImageError(null);
|
||||
setIsHistoryPickerOpen(false);
|
||||
}}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className="puzzle-level-detail-list divide-y divide-[var(--platform-subpanel-border)]">
|
||||
<section className="grid gap-2 pb-4 sm:grid-cols-[7.5rem_minmax(0,1fr)] sm:items-center">
|
||||
<label
|
||||
htmlFor={`puzzle-level-name-${level.levelId}`}
|
||||
className="contents"
|
||||
>
|
||||
<PlatformFieldLabel variant="section">
|
||||
关卡名称
|
||||
</PlatformFieldLabel>
|
||||
</label>
|
||||
<PlatformTextField
|
||||
id={`puzzle-level-name-${level.levelId}`}
|
||||
value={level.levelName}
|
||||
disabled={isBusy}
|
||||
onChange={(event) =>
|
||||
onLevelChange({ ...level, levelName: event.target.value })
|
||||
}
|
||||
size="lg"
|
||||
density="roomy"
|
||||
aria-label="关卡名称"
|
||||
/>
|
||||
) : null}
|
||||
</section>
|
||||
|
||||
<section className="pt-4">
|
||||
<CreativeImageInputPanel
|
||||
className="puzzle-level-detail-image-editor"
|
||||
disabled={isBusy || generationProgress.isGenerating}
|
||||
isSubmitting={generationProgress.isGenerating}
|
||||
uploadedImageSrc={displayImageSrc}
|
||||
uploadedImageAlt={displayImageAlt}
|
||||
uploadedImageRefreshKey={`${imageRefreshKey}:${level.levelId}`}
|
||||
canRemoveMainImage={Boolean(effectiveReferenceImageSrc)}
|
||||
canToggleAiRedraw={Boolean(effectiveReferenceImageSrc)}
|
||||
canUploadPromptReferences={!effectiveReferenceImageSrc}
|
||||
mainImageMeta={
|
||||
shouldShowReferenceMeta ? (
|
||||
<PlatformUploadPreviewCard
|
||||
layout="inline"
|
||||
imageSrc={effectiveReferenceImageSrc}
|
||||
imageAlt="拼图参考图"
|
||||
caption={referenceImageLabel || '已选择参考图'}
|
||||
removeLabel="移除参考图"
|
||||
resolveAsset
|
||||
className="bg-white/72 py-3"
|
||||
imageShellClassName="rounded-[0.85rem] bg-[var(--platform-subpanel-fill)]"
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
mainImageInputId={`puzzle-level-reference-upload-${level.levelId}`}
|
||||
promptTextareaId={`puzzle-level-picture-description-${level.levelId}`}
|
||||
prompt={level.pictureDescription}
|
||||
promptLabel={
|
||||
effectiveReferenceImageSrc
|
||||
? '画面AI重绘要求(提示词)'
|
||||
: '画面描述'
|
||||
}
|
||||
promptRows={7}
|
||||
aiRedraw={aiRedraw}
|
||||
promptReferenceImages={promptReferenceImages}
|
||||
promptReferenceLimit={PUZZLE_LEVEL_PROMPT_REFERENCE_LIMIT}
|
||||
imageLimitHint="图片≤6MB"
|
||||
imageModelPicker={
|
||||
<PuzzleImageModelPicker
|
||||
value={imageModel}
|
||||
disabled={isBusy || generationProgress.isGenerating}
|
||||
onChange={setImageModel}
|
||||
/>
|
||||
}
|
||||
inputError={referenceImageError}
|
||||
submitLabel={hasFormalImage ? '重新生成画面' : '生成画面'}
|
||||
submitCostLabel={`消耗${PUZZLE_IMAGE_GENERATION_POINT_COST}泥点`}
|
||||
submitDisabled={
|
||||
isBusy ||
|
||||
generationProgress.isGenerating ||
|
||||
(!level.pictureDescription.trim() &&
|
||||
!effectiveReferenceImageSrc)
|
||||
}
|
||||
labels={{
|
||||
imageField: '画面图',
|
||||
uploadImage: '上传参考图',
|
||||
replaceImage: '更换参考图',
|
||||
emptyImageHint: '上传图片/填写画面描述',
|
||||
removeImage: '移除参考图',
|
||||
removeImageConfirmTitle: '移除参考图?',
|
||||
removeImageConfirmBody: '移除后可重新上传或选择历史图片。',
|
||||
promptReferenceUpload: '上传描述参考图',
|
||||
promptReferencePreviewAlt: '参考图预览',
|
||||
closePromptReferencePreview: '关闭参考图预览',
|
||||
previewMainImage: '查看关卡图片',
|
||||
closeMainImagePreview: '关闭关卡图片预览',
|
||||
history: '选择历史图片',
|
||||
}}
|
||||
onMainImageFileSelect={(file) => {
|
||||
void handleReferenceImageFile(file);
|
||||
}}
|
||||
onMainImageRemove={() => {
|
||||
setReferenceImageSrc('');
|
||||
setReferenceImageLabel('');
|
||||
setReferenceImageError(null);
|
||||
setAiRedraw(true);
|
||||
onLevelChange({ ...level, pictureReference: null });
|
||||
}}
|
||||
onAiRedrawChange={setAiRedraw}
|
||||
onPromptChange={(value) =>
|
||||
onLevelChange({
|
||||
...level,
|
||||
pictureDescription: value,
|
||||
})
|
||||
}
|
||||
onPromptReferenceFilesSelect={(files) => {
|
||||
void handlePromptReferenceImageFiles(files);
|
||||
}}
|
||||
onPromptReferenceRemove={(referenceId) => {
|
||||
setPromptReferenceImages((current) =>
|
||||
current.filter((image) => image.id !== referenceId),
|
||||
);
|
||||
setReferenceImageError(null);
|
||||
}}
|
||||
onHistoryClick={() => setIsHistoryPickerOpen(true)}
|
||||
onSubmit={() => setIsCostConfirmOpen(true)}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
</div>,
|
||||
document.body,
|
||||
|
||||
<PlatformMudPointConfirmDialog
|
||||
open={isCostConfirmOpen}
|
||||
points={PUZZLE_IMAGE_GENERATION_POINT_COST}
|
||||
onClose={() => setIsCostConfirmOpen(false)}
|
||||
onConfirm={executeGeneration}
|
||||
confirmDisabled={isBusy || generationProgress.isGenerating}
|
||||
portal={false}
|
||||
overlayClassName="absolute z-20 bg-black/45"
|
||||
panelClassName="platform-remap-surface rounded-[1.5rem] shadow-[0_24px_80px_rgba(0,0,0,0.45)]"
|
||||
/>
|
||||
|
||||
{isHistoryPickerOpen ? (
|
||||
<PuzzleHistoryAssetPickerDialog
|
||||
isBusy={isBusy}
|
||||
onClose={() => setIsHistoryPickerOpen(false)}
|
||||
onSelect={(asset) => {
|
||||
setReferenceImageSrc(asset.imageSrc);
|
||||
setReferenceImageLabel(
|
||||
getPuzzleHistoryAssetReferenceLabel(asset.imageSrc),
|
||||
);
|
||||
setAiRedraw(true);
|
||||
setReferenceImageError(null);
|
||||
setIsHistoryPickerOpen(false);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</PlatformToolModalShell>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -860,103 +836,23 @@ function PuzzlePublishDialog({
|
||||
onClose: () => void;
|
||||
onPublish: () => void;
|
||||
}) {
|
||||
const platformTheme = useAuthUi()?.platformTheme ?? 'light';
|
||||
const primaryLevel = editState.levels[0] ?? null;
|
||||
const formalImageSrc = primaryLevel
|
||||
? resolveLevelFormalImageSrc(primaryLevel)
|
||||
: '';
|
||||
|
||||
if (typeof document === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
className={`platform-theme platform-theme--${platformTheme} platform-overlay fixed inset-0 z-[140] flex items-end justify-center p-3 backdrop-blur-sm sm:items-center sm:p-4`}
|
||||
onClick={(event) => {
|
||||
if (event.target === event.currentTarget) {
|
||||
onClose();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="发布拼图作品"
|
||||
className="platform-modal-shell platform-remap-surface flex max-h-[min(90vh,42rem)] w-full max-w-3xl flex-col overflow-hidden rounded-t-[1.75rem] shadow-[0_24px_80px_rgba(0,0,0,0.55)] sm:rounded-[1.75rem]"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3 border-b border-[var(--platform-subpanel-border)] px-5 py-4">
|
||||
<div className="text-base font-semibold text-[var(--platform-text-strong)]">
|
||||
发布拼图作品
|
||||
</div>
|
||||
<PlatformModalCloseButton
|
||||
variant="platformIcon"
|
||||
label="关闭发布拼图作品"
|
||||
onClick={onClose}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="min-h-0 flex-1 overflow-y-auto px-5 py-4">
|
||||
<div className="grid gap-4 md:grid-cols-[minmax(0,1fr)_14rem]">
|
||||
<div className="space-y-3">
|
||||
<PlatformFieldLabel variant="section">
|
||||
发布检查
|
||||
</PlatformFieldLabel>
|
||||
{actionError ? (
|
||||
<PlatformStatusMessage tone="error" surface="platform">
|
||||
{actionError}
|
||||
</PlatformStatusMessage>
|
||||
) : publishReady ? (
|
||||
<div className="space-y-2">
|
||||
<PlatformStatusMessage tone="success" surface="platform">
|
||||
当前作品已满足发布条件。
|
||||
</PlatformStatusMessage>
|
||||
<PlatformStatusMessage
|
||||
tone="warning"
|
||||
surface="platform"
|
||||
className="font-semibold"
|
||||
>
|
||||
消耗 {PUZZLE_PUBLISH_POINT_COST} 泥点
|
||||
</PlatformStatusMessage>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{blockers.map((blocker, index) => (
|
||||
<PlatformStatusMessage
|
||||
key={`puzzle-publish-blocker-${index}-${blocker}`}
|
||||
tone="warning"
|
||||
surface="platform"
|
||||
>
|
||||
{blocker}
|
||||
</PlatformStatusMessage>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<PlatformFieldLabel variant="section">
|
||||
封面关卡
|
||||
</PlatformFieldLabel>
|
||||
<PlatformMediaFrame
|
||||
src={formalImageSrc}
|
||||
refreshKey={imageRefreshKey}
|
||||
alt={primaryLevel?.levelName || editState.workTitle}
|
||||
fallbackLabel="封面关卡"
|
||||
fallbackContent={<span className="sr-only">封面关卡</span>}
|
||||
aspect="square"
|
||||
surface="soft"
|
||||
className="rounded-[1.15rem]"
|
||||
/>
|
||||
<div className="text-sm font-black text-[var(--platform-text-strong)]">
|
||||
{editState.workTitle}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col-reverse gap-3 border-t border-[var(--platform-subpanel-border)] px-5 py-4 sm:flex-row sm:justify-end">
|
||||
return (
|
||||
<PlatformToolModalShell
|
||||
open
|
||||
title="发布拼图作品"
|
||||
onClose={onClose}
|
||||
closeLabel="关闭发布拼图作品"
|
||||
zIndexClassName="z-[140]"
|
||||
size="lg"
|
||||
panelClassName="!max-h-[min(90vh,42rem)]"
|
||||
footerClassName="flex-col-reverse sm:flex-row sm:justify-end"
|
||||
footer={
|
||||
<>
|
||||
<PlatformActionButton onClick={onClose} tone="ghost">
|
||||
取消
|
||||
</PlatformActionButton>
|
||||
@@ -968,10 +864,62 @@ function PuzzlePublishDialog({
|
||||
? '发布中...'
|
||||
: `发布到广场 · ${PUZZLE_PUBLISH_POINT_COST}泥点`}
|
||||
</PlatformActionButton>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className="grid gap-4 md:grid-cols-[minmax(0,1fr)_14rem]">
|
||||
<div className="space-y-3">
|
||||
<PlatformFieldLabel variant="section">发布检查</PlatformFieldLabel>
|
||||
{actionError ? (
|
||||
<PlatformStatusMessage tone="error" surface="platform">
|
||||
{actionError}
|
||||
</PlatformStatusMessage>
|
||||
) : publishReady ? (
|
||||
<div className="space-y-2">
|
||||
<PlatformStatusMessage tone="success" surface="platform">
|
||||
当前作品已满足发布条件。
|
||||
</PlatformStatusMessage>
|
||||
<PlatformStatusMessage
|
||||
tone="warning"
|
||||
surface="platform"
|
||||
className="font-semibold"
|
||||
>
|
||||
消耗 {PUZZLE_PUBLISH_POINT_COST} 泥点
|
||||
</PlatformStatusMessage>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{blockers.map((blocker, index) => (
|
||||
<PlatformStatusMessage
|
||||
key={`puzzle-publish-blocker-${index}-${blocker}`}
|
||||
tone="warning"
|
||||
surface="platform"
|
||||
>
|
||||
{blocker}
|
||||
</PlatformStatusMessage>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<PlatformFieldLabel variant="section">封面关卡</PlatformFieldLabel>
|
||||
<PlatformMediaFrame
|
||||
src={formalImageSrc}
|
||||
refreshKey={imageRefreshKey}
|
||||
alt={primaryLevel?.levelName || editState.workTitle}
|
||||
fallbackLabel="封面关卡"
|
||||
fallbackContent={<span className="sr-only">封面关卡</span>}
|
||||
aspect="square"
|
||||
surface="soft"
|
||||
className="rounded-[1.15rem]"
|
||||
/>
|
||||
<div className="text-sm font-black text-[var(--platform-text-strong)]">
|
||||
{editState.workTitle}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body,
|
||||
</PlatformToolModalShell>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user