资源工作台「替换素材」候选窗接上预览管线,补齐候选排序、禁用态可读性与删除素材入口
- 「替换素材」候选卡缩略图接入资源卡同一条预览管线:新增 ResourcePreviewMedia,复用同一份预览身份、同一条队列与 LRU 缓存,不再只渲染类型占位图标 - 预览 hook 暴露 sweepVisiblePreviews:弹窗内容挂在 portal 下、不在资源画本 observer 的 root 子树内,改为在「弹窗打开」与「候选区滚动」两个几何变化点显式按同一套视口几何判据兜底放行,不新增第二条加载通路 - 候选排序抽出纯函数 sortResourceReplacementCandidates:可替换(无硬门禁失败)的排最前,其余按后端原名次跟随且仍渲染但禁用;判据与弹窗禁用状态同源,不会出现「排最前却点不动」 - 抽出纯函数 projectResourcesByManifestAssetId,让弹窗素材映射与候选缩略图共用同一份资源投影口径 - 共享选择弹窗的禁用态不再整卡压透明度:变灰只压在缩略图与名称上,「分类不同」这类禁用原因保持全对比度可读(网页端美术画布不传禁用原因,视觉逐字不变) - 资源卡选中工具条末位新增「删除素材」,前置共享工具条同一套分隔线与前面的非破坏性动作隔开,只在 manifestAssetId 存在时才渲染 - 删除流程抽成 useResourceAssetDeleteFlow(读引用信息 → ResourceAssetDeleteDialog 二次确认 → delete_local_project_asset 的 deleteReferencedVersions 三分支),工具条与面板共用同一份实现,没有第二套删除 - 「编辑素材标签」面板底部只留右下角一个「添加」:点它把输入框内容(含未按回车的尾巴)落成 pill 并按现有写入路径保存,分类仍原样回传落盘原值;删除素材/取消/保存标签三个按钮移除,关闭仍走头部 × - 标签 pill 内的删除标签按钮保持不变 - 更新与补充用例:候选排序、排序与禁用同源、候选缩略图真链路与滚动兜底放行、工具条删除入口位置与 IPC 载荷、连带删除分支、面板无删除入口与「添加」语义、禁用态可读性类名契约、工具条动作清单
This commit is contained in:
@@ -186,4 +186,40 @@ describe('ImageCanvasProjectAssetPickerDialog', () => {
|
||||
|
||||
expect(screen.getByText('当前项目还没有已登记素材')).toBeTruthy();
|
||||
});
|
||||
|
||||
/**
|
||||
* 禁用候选的「变灰」不能压在禁用原因上。
|
||||
*
|
||||
* 「分类不同」这类小字是用户唯一能看到的解释,整卡 `opacity-60` 会让它几乎读不出来;
|
||||
* 视觉上的禁用感由缩略图与名称承担(这里用类名契约钉住分工,jsdom 不渲染真实对比度)。
|
||||
*
|
||||
* 变异验证:把 `opacity-60` 挪回整卡 `<button>` 上,本用例必须失败。
|
||||
*/
|
||||
it('keeps the disabled reason at full contrast instead of dimming the whole card', () => {
|
||||
render(
|
||||
<ImageCanvasProjectAssetPickerDialog
|
||||
open
|
||||
assets={ASSETS}
|
||||
selectedAssetIds={[]}
|
||||
assetBlockedReasons={{ 'asset-town': '分类不同' }}
|
||||
onCancel={() => {}}
|
||||
onConfirm={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
const option = screen.getByRole('option', { name: '选择参考图小镇背景' });
|
||||
const media = option.firstElementChild as HTMLElement;
|
||||
const reason = screen.getByText('分类不同');
|
||||
|
||||
// 整卡不再压透明度,只保留不可点的光标语义。
|
||||
expect(option.className).toContain('cursor-not-allowed');
|
||||
expect(option.className).not.toMatch(/opacity-\d/u);
|
||||
// 变灰只压在缩略图上。
|
||||
expect(media.className).toMatch(/opacity-\d/u);
|
||||
expect(reason.className).not.toMatch(/opacity-\d/u);
|
||||
expect(reason.className).toContain('font-medium');
|
||||
expect(reason.className).toContain('var(--platform-text-strong)');
|
||||
// 禁用项仍然渲染(不隐藏),原因与可点性都在。
|
||||
expect((option as HTMLButtonElement).disabled).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -245,13 +245,23 @@ export function ImageCanvasProjectAssetPickerDialog({
|
||||
selected
|
||||
? 'border-[var(--platform-accent)] bg-white shadow-sm'
|
||||
: 'border-[var(--platform-subpanel-border)] bg-white/70 hover:bg-white',
|
||||
blockedReason !== null ? 'cursor-not-allowed opacity-60' : '',
|
||||
// 禁用项的"变灰"只压在缩略图与名称上,**不压禁用原因**:
|
||||
// 「分类不同」这类小字压在整卡 `opacity-60` 下几乎读不出来,而它恰恰是
|
||||
// 用户唯一能看到的解释。视觉上的禁用感由缩略图与名称承担。
|
||||
blockedReason !== null ? 'cursor-not-allowed' : '',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
onClick={() => toggleAsset(asset.id)}
|
||||
>
|
||||
<span className="flex h-20 w-full items-center justify-center overflow-hidden bg-white/60">
|
||||
<span
|
||||
className={[
|
||||
'flex h-20 w-full items-center justify-center overflow-hidden bg-white/60',
|
||||
blockedReason !== null ? 'opacity-50' : '',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
{renderAssetMedia ? (
|
||||
renderAssetMedia(asset)
|
||||
) : projectAssetPickerCategory(asset) === 'image' ? (
|
||||
@@ -264,7 +274,14 @@ export function ImageCanvasProjectAssetPickerDialog({
|
||||
assetIcon(asset)
|
||||
)}
|
||||
</span>
|
||||
<span className="flex min-w-0 items-center gap-1 px-2 py-1.5">
|
||||
<span
|
||||
className={[
|
||||
'flex min-w-0 items-center gap-1 px-2 py-1.5',
|
||||
blockedReason !== null ? 'opacity-60' : '',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
<span className="min-w-0 flex-1 truncate text-xs font-bold text-[var(--platform-text-strong)]">
|
||||
{asset.label}
|
||||
</span>
|
||||
@@ -276,7 +293,7 @@ export function ImageCanvasProjectAssetPickerDialog({
|
||||
) : null}
|
||||
</span>
|
||||
{blockedReason !== null ? (
|
||||
<span className="block px-2 pb-1.5 text-[0.6875rem] leading-snug text-[var(--platform-text-base)]">
|
||||
<span className="block px-2 pb-1.5 text-[0.6875rem] leading-snug font-medium text-[var(--platform-text-strong)]">
|
||||
{blockedReason}
|
||||
</span>
|
||||
) : hint !== null ? (
|
||||
|
||||
Reference in New Issue
Block a user