继续收敛资源预览具体错误

保留模型 WebGL、素材 IPC 和图片读取预览正文

在降级卡面与导入预览中展示脱敏原因
This commit is contained in:
kdletters
2026-10-06 11:02:58 +08:00
parent 3fbdca6b8b
commit 86d68cffab
4 changed files with 50 additions and 9 deletions
@@ -3,6 +3,7 @@ import { useEffect, useRef, useState } from 'react';
import { SpriteImagePreview } from '../../features/ui-editor/components/SpriteImagePreview';
import { readEditorAssetPreview } from '../../services/editorAssetHost';
import { visibleClientErrorMessage } from '../../services/errorReporting';
import {
cancelLocalProjectResourcePreviewScope,
createProjectResourcePreviewRequestId,
@@ -17,6 +18,7 @@ export function ImageImporterPreview({
selected,
}: AssetImporterPreviewProps) {
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
const [previewError, setPreviewError] = useState('');
const scopeIdRef = useRef(createProjectResourcePreviewScopeId());
useEffect(() => {
@@ -29,6 +31,7 @@ export function ImageImporterPreview({
useEffect(() => {
if (!selected || selected.isDirectory) {
setPreviewUrl(null);
setPreviewError('');
return;
}
// A disk-only candidate deliberately has no formal asset identity. Keep
@@ -36,16 +39,24 @@ export function ImageImporterPreview({
// first when the user confirms the import.
if (selected.source === 'local' && !selected.asset) {
setPreviewUrl(null);
setPreviewError('');
return;
}
if (selected.previewUrl) {
setPreviewUrl(selected.previewUrl);
setPreviewError('');
return;
}
if (selected.source === 'remote' && selected.remoteAssetId) {
void readEditorAssetPreview(selected.remoteAssetId)
.then((preview) => setPreviewUrl(preview.previewUrl))
.catch(() => setPreviewUrl(null));
.then((preview) => {
setPreviewUrl(preview.previewUrl);
setPreviewError('');
})
.catch((error) => {
setPreviewUrl(null);
setPreviewError(visibleClientErrorMessage(error));
});
return;
}
if (
@@ -53,6 +64,7 @@ export function ImageImporterPreview({
/^https?:\/\//iu.test(selected.asset?.localPath ?? '')
) {
setPreviewUrl(selected.asset?.localPath ?? null);
setPreviewError('');
return;
}
void invoke<ImagePreviewResponse>('read_local_project_image_preview', {
@@ -62,7 +74,10 @@ export function ImageImporterPreview({
requestId: createProjectResourcePreviewRequestId(),
})
.then((value) => setPreviewUrl(value.dataUrl))
.catch(() => setPreviewUrl(null));
.catch((error) => {
setPreviewUrl(null);
setPreviewError(visibleClientErrorMessage(error));
});
}, [projectPath, selected]);
return previewUrl ? (
@@ -72,10 +87,14 @@ export function ImageImporterPreview({
className="max-h-72 max-w-full object-contain"
/>
) : (
<span className="text-xs text-(--platform-text-soft)">
<span
className="text-xs text-(--platform-text-soft)"
title={previewError || undefined}
role={previewError ? 'alert' : undefined}
>
{selected?.source === 'local' && !selected.asset
? '待导入,导入后可预览'
: (selected?.name ?? '选择一个文件')}
: previewError || selected?.name || '选择一个文件'}
</span>
);
}
@@ -27,6 +27,7 @@ import {
isGameCreationAppUiDesignDocAsset,
} from '../../../../../packages/shared/src/contracts/gameCreationApp';
import { resolveTauriInvoke } from '../../app/tauri';
import { visibleClientErrorMessage } from '../../services/errorReporting';
import {
cancelLocalProjectResourcePreviewScope,
createProjectResourcePreviewRequestId,
@@ -519,11 +520,13 @@ function ResourcePickerThumbnail({
const previewScopeIdRef = useRef(createProjectResourcePreviewScopeId());
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
const [failed, setFailed] = useState(false);
const [previewError, setPreviewError] = useState('');
const mediaType = asset?.mediaType.toLowerCase() ?? '';
useEffect(() => {
setPreviewUrl(null);
setFailed(false);
setPreviewError('');
if (!asset || !projectPath || !mediaType.startsWith('image/')) {
return;
}
@@ -539,8 +542,11 @@ function ResourcePickerThumbnail({
.then((result) => {
if (!cancelled) setPreviewUrl(result.dataUrl);
})
.catch(() => {
if (!cancelled) setFailed(true);
.catch((error) => {
if (!cancelled) {
setFailed(true);
setPreviewError(visibleClientErrorMessage(error));
}
});
return () => {
cancelled = true;
@@ -555,7 +561,14 @@ function ResourcePickerThumbnail({
return <img src={previewUrl} alt="" />;
}
if (failed) {
return <FileText size={18} aria-hidden="true" />;
return (
<span
aria-label={`素材预览失败:${previewError}`}
title={`素材预览失败:${previewError}`}
>
<FileText size={18} aria-hidden="true" />
</span>
);
}
if (mediaType.startsWith('video/')) {
return <Video size={18} aria-hidden="true" />;
@@ -1,5 +1,6 @@
import { useEffect, useRef, useState } from 'react';
import { visibleClientErrorMessage } from '../../services/errorReporting';
import { renderResourceModelThumbnail } from './resourceModelThumbnail';
type ResourceModelPreviewProps = {
@@ -42,6 +43,7 @@ export function ResourceModelPreview({
const [status, setStatus] = useState<'loading' | 'ready' | 'failed'>(
'loading',
);
const [previewError, setPreviewError] = useState('');
/**
* 渲染尺寸按**宿主真实几何**(含画布缩放后的视觉尺寸)取,并且跟着宿主的尺寸变化重渲。
*
@@ -104,6 +106,7 @@ export function ResourceModelPreview({
let disposed = false;
setThumbnail(null);
setStatus('loading');
setPreviewError('');
const request = {
sourceUrl,
mediaType,
@@ -119,9 +122,10 @@ export function ResourceModelPreview({
setThumbnail(dataUrl);
setStatus('ready');
})
.catch(() => {
.catch((error) => {
if (!disposed) {
setStatus('failed');
setPreviewError(visibleClientErrorMessage(error));
}
});
return () => {
@@ -134,6 +138,10 @@ export function ResourceModelPreview({
ref={hostRef}
className="game-resource-card-model-visual"
data-model-preview-status={status}
title={previewError ? `${fallbackLabel}:${previewError}` : fallbackLabel}
aria-label={
previewError ? `${fallbackLabel}:${previewError}` : fallbackLabel
}
// 渲染尺寸暴露到 DOM:缩略图糊 / 比例不对时,先看它和卡片实际几何是否一致。
data-model-render-size={
renderSize ? `${renderSize.width}x${renderSize.height}` : undefined
@@ -104,3 +104,4 @@ DirectProject 回合失败在确认不是客户端内部不可归类故障时,
- 新增发布尝试账本损坏回归:损坏记录现在阻止生成新的根幂等键,并返回账本 JSON 解析正文,避免静默重复发布。
- 首页建项/打开项目读取本地 revision 失败原先吞成 `null` 并继续进入项目;现保留降级进入行为,同时把 IPC/文件读取正文写入首页状态。
- 发布面板读取封面生成价格失败原先只清空价格并继续显示泛化消耗提示;现保留价格查询的脱敏正文并在确认弹窗中展示。
- 资源模型卡、素材引用选择器和本地图片导入预览原先失败只降级成图标/文件名;现保留脱敏后的 WebGL、IPC、远程读取原因,并通过卡面 title/aria 或提示文本展示,降级预览行为不变。