Files
Genarrative/apps/ai-game-creator-shell/src/view/project-development/resourceCanvasSectionMapping.ts
T
suzmii d1581892d7 修复 PR #316 review(前端 TS/TSX):资源工作台与资源画布
- 素材重命名(跨范围契约):`rename_local_project_asset` 现在要求 `expectedProjectId` + `expectedProjectRevision`,前端按删除 / 分类保存同一套 CAS 口径补齐——先 `get_local_game_project_revision` 读当前 revision 再连同项目身份提交,不用可能过期的本地缓存值
- 失败文案统一:新增 `projectAssetCommandErrorMessage`,把 `project-identity-conflict` / `project-revision-conflict` 翻成用户可读中文,重命名 / 删除 / 分类保存三条链路共用一份映射,其余错误原样透出
- index.tsx:资源面板下载按 `resolveDownloadablePanelEntries` 过滤,版本卡这类合成 path 的条目不再进入落盘链路
- index.tsx:`characterAnimationPanel` 纳入画布浮层开关判据(抽 `resolveResourceCanvasFloatingPanelOpen`),「生成动画」面板与快速编辑 / 信息浮层共用同一条「点外部 / Esc 关闭」规则
- index.tsx:去掉 `ResourceBookScene` 上重复的 `onWheel`,滚轮只由 manager 上的原生 `passive:false` 监听处理,平移 / 缩放不再被翻倍
- index.tsx:快速编辑的源资源在层 id 命中不到时回落到已正规化的 `asset:<id>`,重试不再静默什么都不做;取不到时给出可见失败
- ResourceCanvasPanelView.tsx:下载按钮按可下载条数判可用、不再在上传中显示下载转圈;上传中禁掉 × / Esc / 遮罩三条关闭路径;预览占位文案抽 helper
- GameRunVersionPicker.tsx:portal 菜单在滚动 / 缩放后重算位置;外部点击判定沿用共享 `useImageCanvasFloatingOptionDismiss` + `menuRef` 边界(review 该条已修,保留现状并补位置用例)
- ResourceCanvasGenerationPanelView.tsx:`submitting` 改 `finally` 收回,成功路径不再永久锁住面板
- ResourceAssetDeleteDialog.tsx / ResourceRenameDialog.tsx / ResourceClassificationPanel.tsx:在飞时 `closeOnEscape` / `closeOnBackdrop` 与头部 × 一起挡住
- resourceEditModel.ts / resourceCanvasToolbarModel.ts / useProjectResourceCardPreviews.ts / projectResourceLiveUpdateModel.ts:抽 `resourceBaseName`、`canonicalProjectedResourceMediaType` 参数收窄去掉强转、删未使用参数、补「IO 失败会 reject」的签名说明
- resourceCanvasHistoryModel.ts:`resourceCanvasSnapshotsEqual` 不再比较不可恢复的 `manuallyPlaced`,消除「压进历史但撤销是空操作」
- resourceCanvasSectionMapping.ts:删掉从未接线、且注释与实现相矛盾的 `LEGACY_RESOURCE_CANVAS_SECTION_FALLBACK`
- resourceCanvasChrome.css:`.game-resource-panel-grid` 补 `flex: 1 1 auto; min-height: 0`,卡片网格成为真正的滚动区
- 用例:重命名 CAS 载荷与读序、CAS 拒绝文案、版本卡下载门禁、上传中关闭路径、浮层判据、菜单跟随滚动、分区映射
2026-09-12 20:49:03 +08:00

118 lines
4.4 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 {
isLegacyProjectResourceCanvasSection,
isProjectResourceCanvasCategory,
type LegacyProjectResourceCanvasSection,
type PersistedProjectResourceCanvasSection,
type ProjectResourceCanvasCategory,
} from '../../../../../packages/shared/src/contracts/gameCreationApp';
/**
* 旧栏目值 → 现行分区集合。旧四 / 五栏目是「扩展名 + mediaType」口径,无法 1:1 归并到
* 6 类资产分类,因此按资源当前分区做保守归并:
*
* - `document` / `audio` / `version` 在新旧口径下同名,1:1 保留;
* - 旧 `art` 混合了 UI 交互、角色与对象、场景与环境与待归类,按资源当前分区落入其中之一;
* - 旧 `code` 在 6 类资产分类里没有对应栏目,统一落入「待归类」。
*/
export const LEGACY_RESOURCE_CANVAS_SECTION_TARGETS: Record<
LegacyProjectResourceCanvasSection,
readonly ProjectResourceCanvasCategory[]
> = {
document: ['document'],
audio: ['audio'],
version: ['version'],
art: ['ui-interaction', 'character', 'scene', 'unclassified'],
code: ['unclassified'],
};
/**
* 把磁盘上的 `section` 值归一到现行分区。
*
* 两条归并语义都由**资源当前分类**收口,因为"这张卡属于哪一栏"的唯一真源本来就是
* `resource.category`(协调时按它分组),持久化的 `section` 只是"它当初放在哪一栏"的记录:
*
* 1. **现行栏目值**:原样返回资源当前分类。旧实现里"现行值但不等于当前分类"直接判 null、
* 整条坐标被丢弃 —— 那等于把用户手摆的位置换成自动排布。资源分类会变(例如
* `kind:"UI"` 的资产从「待归类」被修到「UI 交互」),一改就静默丢掉落盘坐标,是用户可见的损失。
* 2. **旧栏目值**:落在该旧栏目的归并目标集合内就归并到资源当前分类,否则判 null。
*
* 两处都**只改写 `section`,x / y / manuallyPlaced 原样保留**;坐标是栏目内局部坐标
* (相邻的 `reconcileResourceCanvasLayout` 按 section 分组后再算栏目带原点),所以换栏目
* 保留 x / y 得到的正是"这张卡在新栏目里的同一个槽位"。
*
* 返回 `null` 只留给**真正无从归并**的取值:无法识别的分区值,以及旧栏目目标的集合
* 不含资源当前分类的情况(如旧 `code` 遇到已归为 `document` 的资源)。
*/
export function resolveResourceCanvasSection(
section: unknown,
resourceCategory: ProjectResourceCanvasCategory,
): ProjectResourceCanvasCategory | null {
if (isProjectResourceCanvasCategory(section)) {
return resourceCategory;
}
if (!isLegacyProjectResourceCanvasSection(section)) {
return null;
}
return LEGACY_RESOURCE_CANVAS_SECTION_TARGETS[section].includes(
resourceCategory,
)
? resourceCategory
: null;
}
export type NormalizedResourceCanvasPosition = {
resourceId: string;
section: ProjectResourceCanvasCategory;
x: number;
y: number;
manuallyPlaced: boolean;
};
export type NormalizedResourceCanvasPositionResult = {
position: NormalizedResourceCanvasPosition;
/**
* 该坐标的 `section` 是否被读时改写。改写后的布局与原布局逐项不同,
* 既有协调路径据此判定 `changed` 并写入一次新分区的 sidecar。
*/
changed: boolean;
};
/**
* 读时兼容映射:把一条持久化坐标归一到现行分区,原样保留 `x / y / manuallyPlaced`。
*
* 返回 `null` 表示资源不存在或该坐标确实无从归并,调用方不应把它放进任何分区;
* **分区取值与资源当前分类不一致不属于"无从归并"**,见 [`resolveResourceCanvasSection`]。
*/
export function normalizeResourceCanvasPosition(
position: {
resourceId: string;
section: PersistedProjectResourceCanvasSection;
x: number;
y: number;
manuallyPlaced: boolean;
},
categoryByResourceId: ReadonlyMap<string, ProjectResourceCanvasCategory>,
): NormalizedResourceCanvasPositionResult | null {
const resourceCategory = categoryByResourceId.get(position.resourceId);
if (resourceCategory === undefined) {
return null;
}
const section = resolveResourceCanvasSection(
position.section,
resourceCategory,
);
if (section === null) {
return null;
}
return {
position: {
resourceId: position.resourceId,
section,
x: position.x,
y: position.y,
manuallyPlaced: position.manuallyPlaced,
},
changed: section !== position.section,
};
}