继续收口视觉小说素材选择弹窗
视觉小说素材选择弹窗复用 PlatformToolModalShell 保留上传、AI生成、历史素材读取和选择回调 补充测试断言共享白底工具弹窗壳层 同步 PlatformUiKit 文档和 Hermes 决策记录
This commit is contained in:
@@ -113,6 +113,11 @@ test('visual novel upload-only asset picker uses PlatformEmptyState chrome', asy
|
||||
const uploadOnlyEmptyState =
|
||||
within(pickerDialog).getByText('选择本地文件上传');
|
||||
|
||||
expect(pickerDialog.className).toContain('platform-remap-surface');
|
||||
expect(pickerDialog.className).toContain(
|
||||
'shadow-[0_24px_80px_rgba(0,0,0,0.55)]',
|
||||
);
|
||||
expect(pickerDialog.className).toContain('!max-w-4xl');
|
||||
expect(uploadOnlyEmptyState.className).toContain('border-dashed');
|
||||
expect(uploadOnlyEmptyState.className).toContain('rounded-[1.35rem]');
|
||||
expect(uploadOnlyEmptyState.className).toContain('min-h-24');
|
||||
|
||||
@@ -51,6 +51,7 @@ import { PlatformModalCloseButton } from '../common/PlatformModalCloseButton';
|
||||
import { PlatformSegmentedTabs } from '../common/PlatformSegmentedTabs';
|
||||
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
|
||||
import { PlatformSubpanel } from '../common/PlatformSubpanel';
|
||||
import { PlatformToolModalShell } from '../common/PlatformToolModalShell';
|
||||
import {
|
||||
PlatformSelectField,
|
||||
PlatformTextField,
|
||||
@@ -409,7 +410,6 @@ function VisualNovelAssetPickerDialog({
|
||||
onSelect: (asset: VisualNovelAssetReference) => void;
|
||||
}) {
|
||||
const authUi = useAuthUi();
|
||||
const platformTheme = authUi?.platformTheme ?? 'light';
|
||||
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const [historyAssets, setHistoryAssets] = useState<
|
||||
VisualNovelAssetReference[]
|
||||
@@ -527,126 +527,100 @@ function VisualNovelAssetPickerDialog({
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof document === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
className={`platform-theme platform-theme--${platformTheme} platform-overlay fixed inset-0 z-[170] flex items-end justify-center p-3 backdrop-blur-sm sm:items-center sm:p-4`}
|
||||
onClick={(event) => {
|
||||
if (event.target === event.currentTarget) {
|
||||
onClose();
|
||||
}
|
||||
}}
|
||||
return (
|
||||
<PlatformToolModalShell
|
||||
open
|
||||
title={config.title}
|
||||
onClose={onClose}
|
||||
closeLabel="关闭"
|
||||
zIndexClassName="z-[170]"
|
||||
size="xl"
|
||||
panelClassName="!max-h-[min(92vh,42rem)] !max-w-4xl"
|
||||
>
|
||||
<section
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label={config.title}
|
||||
className="platform-modal-shell platform-remap-surface flex max-h-[min(92vh,42rem)] w-full max-w-4xl flex-col overflow-hidden rounded-t-[1.45rem] sm:rounded-[1.45rem]"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<header className="flex items-center justify-between gap-3 border-b border-[var(--platform-subpanel-border)] px-4 py-3">
|
||||
<h2 className="min-w-0 truncate text-base font-black text-[var(--platform-text-strong)]">
|
||||
{config.title}
|
||||
</h2>
|
||||
<PlatformModalCloseButton
|
||||
label="关闭"
|
||||
variant="platformIcon"
|
||||
className="h-9 w-9"
|
||||
onClick={onClose}
|
||||
title="关闭"
|
||||
/>
|
||||
</header>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto px-4 py-4">
|
||||
<div className="mb-4 flex flex-wrap gap-2">
|
||||
<PlatformActionButton
|
||||
tone="secondary"
|
||||
disabled={disabled || isUploading || isGeneratingImage}
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className="min-h-10"
|
||||
>
|
||||
{isUploading ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Upload className="h-4 w-4" />
|
||||
)}
|
||||
上传
|
||||
</PlatformActionButton>
|
||||
{config.imageGeneratorConfig && config.previewTone === 'image' ? (
|
||||
<PlatformActionButton
|
||||
disabled={disabled || isUploading || isGeneratingImage}
|
||||
onClick={() => {
|
||||
void handleGenerateImage();
|
||||
}}
|
||||
className="min-h-10"
|
||||
>
|
||||
{isGeneratingImage ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Sparkles className="h-4 w-4" />
|
||||
)}
|
||||
AI生成
|
||||
</PlatformActionButton>
|
||||
) : null}
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept={config.accept}
|
||||
disabled={disabled || isUploading || isGeneratingImage}
|
||||
onChange={(event) => {
|
||||
void handleUpload(event);
|
||||
}}
|
||||
className="hidden"
|
||||
aria-label={`上传${config.title}文件`}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-4 flex flex-wrap gap-2">
|
||||
<PlatformActionButton
|
||||
tone="secondary"
|
||||
disabled={disabled || isUploading || isGeneratingImage}
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className="min-h-10"
|
||||
>
|
||||
{isUploading ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Upload className="h-4 w-4" />
|
||||
)}
|
||||
上传
|
||||
</PlatformActionButton>
|
||||
{config.imageGeneratorConfig && config.previewTone === 'image' ? (
|
||||
<PlatformActionButton
|
||||
disabled={disabled || isUploading || isGeneratingImage}
|
||||
onClick={() => {
|
||||
void handleGenerateImage();
|
||||
}}
|
||||
className="min-h-10"
|
||||
>
|
||||
{isGeneratingImage ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Sparkles className="h-4 w-4" />
|
||||
)}
|
||||
AI生成
|
||||
</PlatformActionButton>
|
||||
) : null}
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept={config.accept}
|
||||
disabled={disabled || isUploading || isGeneratingImage}
|
||||
onChange={(event) => {
|
||||
void handleUpload(event);
|
||||
}}
|
||||
className="hidden"
|
||||
aria-label={`上传${config.title}文件`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{!config.historyKind ? (
|
||||
<PlatformEmptyState
|
||||
surface="dashed"
|
||||
size="inline"
|
||||
tone="base"
|
||||
className="mb-4 min-h-24"
|
||||
>
|
||||
选择本地文件上传
|
||||
</PlatformEmptyState>
|
||||
) : null}
|
||||
{!config.historyKind ? (
|
||||
<PlatformEmptyState
|
||||
surface="dashed"
|
||||
size="inline"
|
||||
tone="base"
|
||||
className="mb-4 min-h-24"
|
||||
>
|
||||
选择本地文件上传
|
||||
</PlatformEmptyState>
|
||||
) : null}
|
||||
|
||||
{error ? (
|
||||
<PlatformStatusMessage
|
||||
tone="error"
|
||||
surface="platform"
|
||||
size="md"
|
||||
className="mb-4 rounded-2xl"
|
||||
>
|
||||
{error}
|
||||
</PlatformStatusMessage>
|
||||
) : null}
|
||||
{error ? (
|
||||
<PlatformStatusMessage
|
||||
tone="error"
|
||||
surface="platform"
|
||||
size="md"
|
||||
className="mb-4 rounded-2xl"
|
||||
>
|
||||
{error}
|
||||
</PlatformStatusMessage>
|
||||
) : null}
|
||||
|
||||
{config.historyKind ? (
|
||||
<PlatformAssetPickerGrid
|
||||
items={historyAssets}
|
||||
isLoading={isLoadingHistory}
|
||||
error={null}
|
||||
loadingLabel="读取中..."
|
||||
emptyLabel="暂无历史素材"
|
||||
disabled={disabled}
|
||||
getKey={(asset) => asset.assetObjectId}
|
||||
getImageSrc={(asset) => asset.imageSrc}
|
||||
getImageAlt={(asset) => asset.ownerLabel || '历史素材'}
|
||||
getTitle={(asset) => asset.ownerLabel || '未记录账号'}
|
||||
getSubtitle={(asset) => formatHistoryAssetDate(asset.createdAt)}
|
||||
onSelect={onSelect}
|
||||
gridClassName="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4"
|
||||
emptyClassName="min-h-40 rounded-[1.15rem] bg-white/56"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
</div>,
|
||||
document.body,
|
||||
{config.historyKind ? (
|
||||
<PlatformAssetPickerGrid
|
||||
items={historyAssets}
|
||||
isLoading={isLoadingHistory}
|
||||
error={null}
|
||||
loadingLabel="读取中..."
|
||||
emptyLabel="暂无历史素材"
|
||||
disabled={disabled}
|
||||
getKey={(asset) => asset.assetObjectId}
|
||||
getImageSrc={(asset) => asset.imageSrc}
|
||||
getImageAlt={(asset) => asset.ownerLabel || '历史素材'}
|
||||
getTitle={(asset) => asset.ownerLabel || '未记录账号'}
|
||||
getSubtitle={(asset) => formatHistoryAssetDate(asset.createdAt)}
|
||||
onSelect={onSelect}
|
||||
gridClassName="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4"
|
||||
emptyClassName="min-h-40 rounded-[1.15rem] bg-white/56"
|
||||
/>
|
||||
) : null}
|
||||
</PlatformToolModalShell>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user