合并 #283 资源画本画布实现

合入 feat/game-agent-canvas-resource-workbench-v2 的完整画布实现:资源画本控制器/模型/视口/布局、转场层、资源分页缓存与自动聚焦修复、资源总览卡片预览懒加载
保留本分支已有的聊天素材引用改动(引用输入区、@ 引用按钮、运行画面点选)
解决 index.tsx 状态声明与 pitfalls.md 文档冲突;styles.css 去重后仅保留一份画本视觉层
This commit is contained in:
2026-09-09 15:43:32 +08:00
21 changed files with 3280 additions and 1149 deletions
@@ -1,8 +1,34 @@
# Game Agent 资源自由画板与快速编辑
## 2026-09-05 资源画本独立转场
- `resourceBookModel` 立即提交目标 `main / child + category`,`entering / returning-main` 只表示尚未收尾的视觉过程,不承载待提交的栏目。缩略入口、大纲、下一页与资源定位共用分类导航入口;栏目互切直接到达目标,不经过总览。滚轮不切换栏目。
- 真实 `ResourceCard` 仍由唯一的 `ResourceBookScene` 挂载,保持预览请求、播放器和交互的单一所有者。转场前 `resourceBookController.begin` 捕获当前可见标题与资源卡的屏幕矩形和表现;目标 DOM 提交后的 layout effect 调用 `play`。
- `ResourceBookTransitionLayer` 是 manager 内未变换的独立层。快照仅复制表现,不挂载 React 业务组件,移除重复 ID、业务 data 属性及可访问控件身份,整层 `inert / aria-hidden / pointer-events:none`。播放中的视频优先捕获当前帧;跨源禁止读取时保留已有 poster,不启动第二个播放器。
- FLIP 只动画快照的 transform/opacity,统一时长 420ms,所有 `Animation.finished` 完成后删除快照并恢复目标真实节点;动画创建失败、零尺寸或不支持 Web Animations 时直接完成。`prefers-reduced-motion` 不创建动画,不等待定时器。
- 再次导航先读取当前动画快照的实际位置,再取消旧动画并重新建立目标;token 隔离旧 promise,过期完成和取消均不能提交新状态。窗口尺寸变化、排序或异步布局更新时直接收尾到当前目标,避免使用过期几何。
- 缩放、适应内容和空白拖动先结束视觉转场再执行用户输入;转场中的资源卡 pointer-down 不启动持久化拖拽。普通滚轮只平移当前视图,Ctrl/Meta 滚轮以指针为锚点缩放,均不切换栏目。
- viewport 只属于用户交互状态,按主画布以及 `sortMode/category` 隔离保存。转场不插值也不持久化 viewport;测量主画布缩略入口时先逆变换回布局坐标,避免缩放后的 DOMRect 被二次缩放。只有真实非零测量才能标记首次 fit 完成。
验收使用 `resourceBookController.test.ts`、`resourceBookModel.test.ts`、`resourceBookViewport.test.ts` 和 `appSurface.test.ts`;页面 `.suite.ts` 由 `appSurface.test.ts` 注册,不能作为独立 Vitest 文件运行。真实浏览器需覆盖总览/栏目往返、连续改选、滚轮平移、缩放、尺寸变化、减少动画偏好,以及快照图片和字体的正确比例。jsdom 不模拟真实插值,不能代替浏览器验收。
### 2026-09-05 子画布无限平移与初始可读比例
子画布改为固定视口加可无限平移的 world 层:视口本身保持 `overflow: hidden`,平移和缩放只作用于 `.game-resource-book-scene-world`,不再让内容容器的边界限制拖动范围。首次 fit(包括隐藏分页画布提前完成的 fit)统一保证比例不低于 `1:1`,避免子画布展开后卡片缩成不可读的小块;用户后续主动缩放或平移的视口仍按每个分类独立保留。新增 viewport 回归测试覆盖初始可读比例和超出内容边界双向平移。
返回主画布时立即提交主画布布局,由独立快照层完成缩回;world/main viewport 不播放附加动画。
总览和栏目视图同样使用固定铺满 manager 的交互视口,只有内部 world 层承担平移和缩放,内容层不裁剪缩略入口。空白拖动允许双向越过全部内容边界;移走内容后仍可在原视口继续拖动。普通滚轮/触控板平移,Shift 滚轮横向平移,Ctrl/Meta 滚轮以指针为锚点缩放;wheel 监听阻止浏览器滚动或页面缩放,但不切换栏目。总览/栏目往返保留总览原有位置和比例,只有主动适应内容才复位。
## 2026-09-04 资源画本 UI 阶段补充
资源工作台采用“主画布 + 子画布”的统一画本模型。主画布展示各子画布的缩略入口,内部按资源类型显示有限层叠卡片,最多三层,更多资源以虚化卡片提示。主画布预览和子画布展开态复用同一套卡片视觉与标题栏结构,共享元素快照从当前屏幕位置过渡到目标位置。子画布标题栏提供缩回主画布的按钮;子画布互切直接到达目标,不强制经过主画布。
本阶段先实现前端 UI、导航状态和转场表现,资源真实跨画布移动、主画布正式资源登记、上传/生成任务、版本绑定以及 UI 编辑器导入与 UI 包工作流继续沿用现有权威链路,待 UI 编辑器完善后再接入。临时画本视图状态不得替代 manifest、草稿或其它后端业务真相。
## 目标
资源管理在空项目中展示现有按类型式分区展览。任一栏目出现资源后,“按依赖”和“按类型”共用同一套栏目分页画布:固定五个栏目各自拥有一个铺满资源管理区域的独立画布,空栏目也保留可打开的空画布;栏目大纲悬浮在画布左侧中间,只显示图标和栏目名称,不占用画布布局宽度;顶部显示当前栏目标题,底部显示下一页标题。普通滚轮切换栏目,按住 Ctrl/Meta 的滚轮以指针为锚点缩放当前画布。两种模式唯一差异是“按依赖”在当前栏目内绘制引导线。资源详情是叠加在资源画板之上的非模态卡片;打开、切换和关闭详情都不得卸载背景工具栏、资源卡、依赖连线或重置 viewport、搜索和排序模式。
资源管理在空项目中展示现有按类型式分区展览。任一栏目出现资源后,“按依赖”和“按类型”共用同一套栏目分页画布:固定五个栏目各自拥有一个铺满资源管理区域的独立画布,空栏目也保留可打开的空画布;栏目大纲悬浮在画布左侧中间,只显示图标和栏目名称,不占用画布布局宽度;顶部显示当前栏目标题,底部显示下一页标题。普通滚轮平移当前视图,按住 Ctrl/Meta 的滚轮以指针为锚点缩放当前画布。两种模式唯一差异是“按依赖”在当前栏目内绘制引导线。资源详情是叠加在资源画板之上的非模态卡片;打开、切换和关闭详情都不得卸载背景工具栏、资源卡、依赖连线或重置 viewport、搜索和排序模式。
图片资源从“编辑资源”进入持续精修草稿后,点击任意图片直接在图片下方显示快速编辑卡。每次快速编辑创建新的候选图,不覆盖来源图层;生成任务立即在画布中创建占位并进入任务侧栏。用户显式选择候选图“设为最终图”后,保持原资产 ID 不变,以事务方式切换 manifest 指向的正式 PNG;精修草稿和其它候选图继续保留。
@@ -87,12 +113,12 @@
## 操作边界
- 栏目顺序固定为 `设计文档 -> 美术资源 -> 音乐音效 -> 游戏代码 -> 项目版本`。完全空项目显示全部栏目的分区展览;任一栏目出现资源后,分页大纲以左侧垂直居中的悬浮 Dock 展示全部栏目。常态缩小、降低不透明度并移除容器与选中项背景,只露出栏目文字;悬停或键盘聚焦时平滑恢复完整尺寸,显示栏目图标、Dock 背景和选中态视觉强调。默认停留在该顺序中的第一个非空栏目,空栏目仍可打开空画布。
- 普通滚轮向下切到下一栏目、向上切到上一栏目并循环;持续滚动时将离散切页意图加入有界队列,浏览器合并形成的单个大幅滚轮事件也要按输入强度拆分为多个切页意图。同一节流窗口内的待处理步数必须合并为一次目标栏目切换,不能逐页挂载并加载中间栏目的资源,以免资源渲染阻塞后续滚轮输入;逻辑目标栏目在事件处理中同步推进,可见栏目和 viewport 通过 React transition 异步提交,渲染尚未完成时到达的新滚轮仍基于最新逻辑目标继续计算,渲染过程不得把目标栏目回写成旧页面。同时限制切页频率和最长排队距离,避免触控板惯性长时间自动翻页。点击大纲、底部“下一页”标题或自动定位资源属于显式切页,必须先取消尚未执行的滚轮队列,不能在显式切页后继续跳转;Ctrl/Meta 缩放、触控板缩放手势、排序切换和开始拖动画布或资源卡也必须取消待处理切页,缩放后的下一次普通滚轮应立即建立新的翻页意图。切页前必须终止旧栏目的画布拖动和 pointer capture,避免旧 viewport 写入新栏目。
- 滚轮不切换栏目。点击大纲、底部“下一页”标题、总览入口或自动定位资源属于显式切页;切页前必须终止旧栏目的画布拖动和 pointer capture,避免旧 viewport 写入新栏目。
- 每种排序模式下的每个栏目画布都保留独立 viewport;首次进入该“排序模式 + 栏目”组合时按当前内容适配视口,离开后再返回则恢复该组合上次的平移和缩放。空白处拖拽平移画布,资源卡拖拽移动卡片并更新依赖线,Ctrl/Meta 缩放只作用于当前组合,不能牵动其它排序模式或栏目。
- 依赖画布复用 `@genarrative/image-canvas-core` 的 viewport 计算,并复用现有资源卡片、布局和依赖连线模型。
- 非空状态不使用资源分区滚动条、分区缩放或分区高度操作作为主要导航;栏目通过大纲、底部下一页标题和滚轮切换。
- 非空状态不使用资源分区滚动条、分区缩放或分区高度操作作为主要导航;栏目通过大纲、总览入口和底部下一页标题切换。
- 当前栏目画布背景是无限的:用户可以将 viewport 沿 x/y 任意方向平移,画布不以资源 extent 作为导航边界,也不显示可见画布边缘。资源卡片的持久化布局坐标允许落在 `-1_000_000..=1_000_000`,用于支撑元素位于世界原点左上方;超出该范围仍拒绝写入,避免持久化非法布局。这与 viewport 能否继续平移是两层独立语义。搜索、详情卡和临时隐藏不得改变 viewport。
- 普通滚轮切换栏目,指针拖动空白平移画布,Ctrl/Meta 缩放、复位以及容器 resize 后都必须保持同一套 viewport 数据流。只有“排序模式 + 栏目”组合首次获得可测量容器尺寸或用户显式复位时才重新适配内容;返回已访问组合、图片尺寸测量、布局拖动或资源 extent 变化只归一化并保留该组合的当前 viewport,不得意外重置用户已经完成的平移和缩放。普通平移不夹取 x/y;缩放仍受共享画布的最小/最大比例限制。初次 fit 与显式复位只使用资源卡真实包围盒,不把导航最小尺寸、原点空区或额外布局 gap 算入,并以 `16px` 紧凑留白在共享缩放上限内尽量铺满视口。
- 指针拖动空白平移画布,普通滚轮平移当前视图,Ctrl/Meta 缩放、复位以及容器 resize 后都必须保持同一套 viewport 数据流。只有“排序模式 + 栏目”组合首次获得可测量容器尺寸或用户显式复位时才重新适配内容;返回已访问组合、图片尺寸测量、布局拖动或资源 extent 变化只归一化并保留该组合的当前 viewport,不得意外重置用户已经完成的平移和缩放。普通平移不夹取 x/y;缩放仍受共享画布的最小/最大比例限制。初次 fit 与显式复位只使用资源卡真实包围盒,不把导航最小尺寸、原点空区或额外布局 gap 算入,并以 `16px` 紧凑留白在共享缩放上限内尽量铺满视口。
- 资源卡拖动使用 `5px` 阈值区分点击与移动;移动期间按当前 scale 乐观换算世界坐标、显示拖动态并同步依赖线,释放时提交一次 `manuallyPlaced=true` 布局 CAS,取消则回滚预览且不提交。拖动后的释放点击不打开详情。
- 依赖模式只在当前栏目画布内显示两端都属于该栏目的合法精确引用;装饰 SVG 与视觉隐藏的关系说明消费同一组可见边,搜索隐藏任一端点时两者同步移除。依赖线的 viewport 测量按动画帧合并,平移和缩放只更新已挂载观察器消费的最新 viewport,不得在每次输入时重建 ResizeObserver、scroll 或 resize 监听。任务流仍只参与同类型布局聚类,不绘线也不进入关系说明。
- 资源详情卡包含元数据、媒体预览和“编辑资源”操作,但不使用全屏 backdrop、不声明 `aria-modal=true`、不把 `focusedResource` 作为背景工具栏渲染条件。角色资源同时显示“生成动画”时,两个业务操作按钮必须使用一致样式,不能依赖 DOM 中的首按钮位置。桌面端允许继续操作背景画板;窄屏可以使用有边界的贴边卡,但背景组件必须保持挂载。
@@ -117,7 +143,7 @@
## 验收
1. 完全空项目在按依赖与按类型下都显示相同的分区展览;任一栏目出现资源后,两种模式都切换为栏目分页画布,大纲以左侧垂直居中的悬浮 Dock 覆盖在全宽画布上,包含全部栏目的图标和文字、不显示数量,空栏目仍可打开空画布。
2. 栏目顺序为 `设计文档 -> 美术资源 -> 音乐音效 -> 游戏代码 -> 项目版本`,游戏代码不再固定在首位;普通滚轮可循环切换栏目,点击大纲和底部下一页标题也可切页。
2. 栏目顺序为 `设计文档 -> 美术资源 -> 音乐音效 -> 游戏代码 -> 项目版本`,游戏代码不再固定在首位;点击大纲、总览入口和底部下一页标题切页,滚轮不切换栏目。
3. 在当前栏目内拖拽空白可以无限平移画布,拖拽资源卡可以移动卡片并同步依赖线,Ctrl/Meta + 滚轮以指针位置为锚点缩放,复位按钮可以适配当前栏目内容;普通平移不会因资源 extent、图片测量或窗口 resize 被拉回,连续缩放仍停在共享画布的最小/最大比例范围内。
4. 按依赖模式显示当前栏目内同类型两端资源的合法精确引用,并具有与当前可见连线一致的无障碍关系说明;按类型模式不显示引导线;搜索隐藏任一端点后连线和说明同时消失。
5. 点击任意资源后“按依赖 / 按类型 / 复位”等画布级动作仍保持挂载和原状态;顶部不出现手动新建入口。详情为非模态独立卡片,背景画板 viewport、搜索、排序、卡片和连线不卸载、不重置。