Files
Genarrative/apps/ai-game-creator-shell/src/view/project-development/resourceCardReferenceDropModel.ts
T
suzmii 5467ed6d7b 自查修正:拖到对话落点逐值比较、替换面板锚进画布容器并与任务面板互斥
- 拖动落点改逐值比较 sameResourceCardReferenceDrop(条数 + 对话栏矩形四值),指针在对话栏上移动不再每帧 setState、不再每帧重渲染工作台。
- 替换面板从 stage 顶层移进画布容器 .game-resource-book-manager(本身 position: relative),top 3.2rem 排在任务开关下方;相对 stage 写死的偏移会在工具条换行时压上去,与刚修的任务开关是同一类隐患。
- 打开替换会话时收起「生成任务」面板:画布右上角同一时刻只留一块浮层(任务照旧在账本里推进)。
- 面板上方补一行「替换源素材:<显示名>」:非模态之后画布上的源素材光环会被空白点击清掉,源身份得在面板这一侧留一份可读说法。
- 测试:替换用例新增「面板锚在画布容器里、写明源素材、并与任务面板互斥」(20 passed)、落点模型新增逐值比较用例(6 passed)。
- 文档:decision-log 新增本批自查条目、待办文档补登记、验收用例 C9 注脚改为 nonModal 形态。
2026-09-21 06:21:19 +08:00

122 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { parseGameCreationAppAssetKind } from '../../../../../packages/shared/src/contracts/gameCreationApp';
import type { ResourceReference } from '../../features/project-workspace/resourceReferences';
import {
type ProjectResource,
projectResourceAssetCategory,
} from './resourceProjectionModel';
/** 对话栏在屏幕坐标里的矩形(拖动落点的唯一判据)。 */
export type ResourceCardReferenceDropRect = {
left: number;
top: number;
width: number;
height: number;
};
/**
* 量对话栏的落点矩形:没有元素、或元素还没量出尺寸(0 x 0,jsdom 与折叠态)时返回 null。
*
* 零面积矩形必须当「没有落点」:命中测试是 contains,一个 0 x 0 的矩形会把任何点都判成
* 在里面,于是画布内正常拖动松手也会被误判成「拖到对话」,排版写入被整段跳过。
*/
export function resourceCardReferenceDropRect(
element: Element | null,
): ResourceCardReferenceDropRect | null {
if (!element) return null;
const rect = element.getBoundingClientRect();
if (!(rect.width > 0) || !(rect.height > 0)) {
return null;
}
return {
left: rect.left,
top: rect.top,
width: rect.width,
height: rect.height,
};
}
export function resourceCardReferenceDropContains(
rect: ResourceCardReferenceDropRect,
clientX: number,
clientY: number,
) {
return (
clientX >= rect.left &&
clientX <= rect.left + rect.width &&
clientY >= rect.top &&
clientY <= rect.top + rect.height
);
}
/**
* 两次落点状态是否**逐值相同**。
*
* 指针在对话栏上移动时每帧都会算出一个新的落点对象;只看「有没有落点」会让每一帧都 `setState`
* 一次、把工作台整棵子树重渲染一遍(`pointermove` 热路径上这是实打实的浪费)。带上这一条,
* 只有条数或对话栏矩形真的变了才重新渲染。
*/
export function sameResourceCardReferenceDrop(
left: { count: number; rect: ResourceCardReferenceDropRect },
right: { count: number; rect: ResourceCardReferenceDropRect },
) {
return (
left.count === right.count &&
left.rect.left === right.rect.left &&
left.rect.top === right.rect.top &&
left.rect.width === right.rect.width &&
left.rect.height === right.rect.height
);
}
/**
* 拖到对话要插入的引用。
*
* 与资源卡工具条那枚「引用」按钮**同一口径**:只认已登记到 manifest 的素材
* (manifestAssetId),来源标 resource-card,kind / 分类 / 标签全部取自资源投影,
* 于是同一个素材无论从按钮还是从拖拽进来,草稿里的引用是同一枚(去重键也跟着走)。
* 未登记的卡(任务产物、附件、Agent 回执等)在这里被过滤掉,调用方按「一条都没有」给原因。
*
* 顺序即插入顺序:movedResourceIds 是本次拖动真正参与位移的那批(第 0 项是被按住的那张),
* 多选后整批引用、单选只引用按住的那张,与拖动位移的语义同源。
*/
export function resourceCardReferenceDropReferences(
movedResourceIds: readonly string[],
resources: readonly ProjectResource[],
): ResourceReference[] {
if (movedResourceIds.length === 0) {
return [];
}
const byId = new Map(resources.map((resource) => [resource.id, resource]));
const references: ResourceReference[] = [];
for (const resourceId of movedResourceIds) {
const resource = byId.get(resourceId);
if (!resource?.manifestAssetId) {
continue;
}
references.push({
type: 'resource',
resourceId: resource.manifestAssetId,
kind: parseGameCreationAppAssetKind(
resource.subtype,
'project-development.resource-card-reference-drop',
),
mediaType: resource.mediaType,
label: resource.label,
category: projectResourceAssetCategory(resource),
tags: resource.assetTags ?? [],
source: 'resource-card',
});
}
return references;
}
/**
* 落点提示文案。条数为 0 时不写「引用 0 项」,而是把「为什么一条也引用不了」说清楚:
* 用户此时已经拖到对话上了,静默什么都不发生会被读成功能坏了。
*/
export function resourceCardReferenceDropHintLabel(count: number) {
return count > 0
? `松手即可 @ 引用 ${count} 项素材`
: '这些素材还没登记为项目资源,不能 @ 引用';
}