限制资源画布默认适配缩放
- 将资源画布初次适配与复位的默认最大缩放统一为 1.5,覆盖全部资源栏目。 - 保留用户主动缩放的通用上限,并补充跨栏目 viewport 记忆回归。 - 同步技术方案中的资源总览缩放与视口记忆描述。
This commit is contained in:
@@ -82,7 +82,6 @@ import {
|
||||
normalizeInfiniteResourceCanvasViewport,
|
||||
RESOURCE_CANVAS_DRAG_THRESHOLD,
|
||||
RESOURCE_CANVAS_FIT_PADDING,
|
||||
RESOURCE_CANVAS_INITIAL_FIT_MAX_SCALE,
|
||||
RESOURCE_CANVAS_SECTION_MIN_HEIGHT,
|
||||
RESOURCE_CANVAS_SECTION_MIN_WIDTH,
|
||||
RESOURCE_CANVAS_SECTION_ORDER,
|
||||
@@ -2264,10 +2263,6 @@ export default function ProjectDevelopmentView({
|
||||
bounds: resourceCanvasFitBounds,
|
||||
canvasSize: { width: measuredWidth, height: measuredHeight },
|
||||
padding: RESOURCE_CANVAS_FIT_PADDING,
|
||||
maxScale:
|
||||
activePageCategory === 'art'
|
||||
? RESOURCE_CANVAS_INITIAL_FIT_MAX_SCALE
|
||||
: undefined,
|
||||
}),
|
||||
);
|
||||
return;
|
||||
@@ -2872,10 +2867,6 @@ export default function ProjectDevelopmentView({
|
||||
bounds: resourceCanvasFitBounds,
|
||||
canvasSize,
|
||||
padding: RESOURCE_CANVAS_FIT_PADDING,
|
||||
maxScale:
|
||||
activePageCategory === 'art'
|
||||
? RESOURCE_CANVAS_INITIAL_FIT_MAX_SCALE
|
||||
: undefined,
|
||||
}),
|
||||
);
|
||||
}, [activePageCategory, resourceCanvasFitBounds, setResourceCanvasViewport]);
|
||||
|
||||
+1
-1
@@ -1353,7 +1353,7 @@ export function fitResourceCanvasViewportToContent({
|
||||
bounds,
|
||||
canvasSize,
|
||||
padding = RESOURCE_CANVAS_FIT_PADDING,
|
||||
maxScale = MAX_SCALE,
|
||||
maxScale = RESOURCE_CANVAS_INITIAL_FIT_MAX_SCALE,
|
||||
}: {
|
||||
bounds: { x: number; y: number; width: number; height: number };
|
||||
canvasSize: { width: number; height: number };
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
RESOURCE_CANVAS_CARD_WIDTH,
|
||||
RESOURCE_CANVAS_COLUMN_GAP,
|
||||
RESOURCE_CANVAS_DEPENDENCY_COLUMN_GAP,
|
||||
RESOURCE_CANVAS_INITIAL_FIT_MAX_SCALE,
|
||||
} from '../../src/view/project-development/resourceCanvasLayoutModel';
|
||||
import { normalizeProjectResourceGraph } from '../../src/view/project-development/resourceDependencyGraphModel';
|
||||
import { ResourceDependencyOverlay } from '../../src/view/project-development/ResourceDependencyOverlay';
|
||||
@@ -623,10 +624,12 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
.map(Number);
|
||||
const fittedViewport = readViewport();
|
||||
const fittedBounds = readFitBounds();
|
||||
expect(
|
||||
Math.abs(fittedBounds[2]! * fittedViewport[2]! - 468) < 1 ||
|
||||
Math.abs(fittedBounds[3]! * fittedViewport[2]! - 268) < 1,
|
||||
).toBe(true);
|
||||
const expectedDocumentFitScale = Math.min(
|
||||
RESOURCE_CANVAS_INITIAL_FIT_MAX_SCALE,
|
||||
468 / fittedBounds[2]!,
|
||||
268 / fittedBounds[3]!,
|
||||
);
|
||||
expect(fittedViewport[2]).toBeCloseTo(expectedDocumentFitScale, 8);
|
||||
|
||||
const viewportBeforeZoom = fittedViewport;
|
||||
const zoomWheel = new WheelEvent('wheel', {
|
||||
@@ -643,6 +646,15 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
});
|
||||
expect(zoomWheelResult).toBe(false);
|
||||
expect(readViewport()[2]).toBeGreaterThan(viewportBeforeZoom[2]!);
|
||||
const documentViewportAfterZoom = readViewport();
|
||||
fireEvent.click(within(outline).getByRole('button', { name: /美术资源/ }));
|
||||
expect(
|
||||
screen.getByRole('region', { name: '美术资源资源画布' }),
|
||||
).not.toBeNull();
|
||||
fireEvent.click(within(outline).getByRole('button', { name: /设计文档/ }));
|
||||
await waitFor(() =>
|
||||
expect(readViewport()).toEqual(documentViewportAfterZoom),
|
||||
);
|
||||
expect(
|
||||
screen.getByRole('region', { name: '设计文档资源画布' }),
|
||||
).not.toBeNull();
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
RESOURCE_CANVAS_COLUMN_GAP,
|
||||
RESOURCE_CANVAS_DEPENDENCY_COLUMN_GAP,
|
||||
RESOURCE_CANVAS_DEPENDENCY_ROW_GAP,
|
||||
RESOURCE_CANVAS_INITIAL_FIT_MAX_SCALE,
|
||||
type ResourceCanvasCardSize,
|
||||
resourceCanvasContentBounds,
|
||||
resourceCanvasImageCardSize,
|
||||
@@ -831,15 +832,23 @@ describe('resource canvas variable card geometry', () => {
|
||||
expect(viewport.x + bounds.x * viewport.scale).toBeCloseTo(16, 8);
|
||||
});
|
||||
|
||||
it('caps the initial fit without limiting explicit canvas zoom', () => {
|
||||
const viewport = fitResourceCanvasViewportToContent({
|
||||
bounds: { x: 0, y: 0, width: 180, height: 128 },
|
||||
canvasSize: { width: 800, height: 600 },
|
||||
maxScale: 1.5,
|
||||
});
|
||||
it.each(['document', 'art', 'audio', 'code', 'version'])(
|
||||
'caps the initial fit for every resource category (%s) while keeping explicit zoom overrides available',
|
||||
() => {
|
||||
const viewport = fitResourceCanvasViewportToContent({
|
||||
bounds: { x: 0, y: 0, width: 180, height: 128 },
|
||||
canvasSize: { width: 800, height: 600 },
|
||||
});
|
||||
const explicitViewport = fitResourceCanvasViewportToContent({
|
||||
bounds: { x: 0, y: 0, width: 180, height: 128 },
|
||||
canvasSize: { width: 800, height: 600 },
|
||||
maxScale: 2.5,
|
||||
});
|
||||
|
||||
expect(viewport.scale).toBe(1.5);
|
||||
});
|
||||
expect(viewport.scale).toBe(RESOURCE_CANVAS_INITIAL_FIT_MAX_SCALE);
|
||||
expect(explicitViewport.scale).toBe(2.5);
|
||||
},
|
||||
);
|
||||
|
||||
it('accepts card sizes directly on resource items for hook integration', () => {
|
||||
const wide = {
|
||||
|
||||
@@ -549,7 +549,7 @@ game-project/
|
||||
- 阶段三的聚类已落在 `reconcileResourceCanvasLayout` 的 dependency 自动坐标派生步骤。它先按资源分类过滤 reference edge,并把 task-flow 按固定分类切成仅在同类 source / target 同时存在时有效的聚合超边;每个 section 再用精确边与 flow 临时节点建无向邻接表,以迭代遍历生成弱连通组。task-flow 只以“流节点 -> 成员”的线性成员关联参与布局,绝不展开 source × target 资源组合,不绘制 SVG,也不把搜索后的 visible set 用作输入。`dependencyDepth` 仍唯一决定横向业务层级;相关簇按 `minDependencyDepth + minStableResourceId` 排序,孤立集合置于所有相关簇之后,簇间使用单一布局常量留白。每个相关簇内先按稳定资源 ID 建同层初始序,再做固定两轮左至右 / 右至左的中位数扫描:精确引用读取相邻层的上下游 rank,task-flow 读取另一端成员 rank 的中位数,平局按稳定资源 ID 收口。dependency 自动位置使用 `48px` 列间走线区和 `40px` 行间走线区;每个相关簇以最大层行数确定高度,资源较少的层增加确定性半差偏移而在簇内居中,菱形 / 分叉两侧因此保持均衡。type 模式仍使用原 `16px` 行列间距。跨分类 read model 关系不进入前端聚类、边界偏置或拓扑签名,但 Rust 深度与原始图真相不改。显示坐标必须遵守前后端共享的 `0..=1_000_000` 上限;超深依赖在最后合法列确定性饱和,保留原始 `dependencyDepth`,同列资源继续按稳定顺序纵向避让。若任一自动 `x / y` 无法在合法域内落槽,协调必须在 IPC 前失败关闭,不持续提交必然被 Rust 拒绝的坐标。算法保持 `O(V + E)` 图遍历,加固定轮数的层内稳定排序和现有有界占用索引;4096 资源不允许全量配对。旧的 `manuallyPlaced=true` 坐标先占位并原样保留,聚类只派生自动坐标;同类型拓扑身份签名只记录有界的资源 ID 端点 / 成员,以便深度未变但邻接变化时触发重派生。图边、cluster ID 和签名都不写 sidecar。
|
||||
- 中间主视窗提供 `resource-overview / asset-canvas / resource-editor / run` 四种状态。2026-08-10 起普通用户“新增资源”显示为禁用态且处理函数拒绝 create;所有现役资源从聚焦态“编辑资源”进入非破坏性派生。静态图片继续进入 refine 素材创作无限画布,SVG、视频、音频、文档/代码、Agent 回执和项目版本进入统一资源编辑壳并按能力分流;底层 create 合同仅保留兼容。编辑面板只替换中央区域,不覆盖右侧 Supervisor 或底部 Agent。`code-prototype` 任务完成前运行入口保持视觉不可用,但仍可点击查看“当前无可运行版本”,不能使用会阻断说明交互的原生 `disabled` 或 `aria-disabled`;完成后才允许进入运行表现层。切回资源总览只修改前端展示态,不伪造后端预览暂停结果。
|
||||
- 资源管理从当前 `GameCreationAppManifest`(包含可选 `versions`)、合法 Agent 文本回执、已导入附件和已完成任务明确登记的产物派生资源,固定按文档、项目版本、美术资源、音乐音效资源分区;未知任务产物不再兜底为版本,未完成任务或未在 `artifacts` 中登记的任意本地音频也不冒充正式资源。`按依赖 / 按类型` 使用各自前端排列,dependency 模式额外绘制当前 manifest 与资源投影可证明的依赖关系。排列与图层都不写回 manifest,不能推断或伪造缺失依赖。
|
||||
- 资源卡支持点击聚焦、搜索和类型筛选。2026-07-28 起完成两套二维坐标与本地 CAS sidecar;2026-07-31 起 dependency 模式增加不持久化的原生 SVG 关系图层。2026-08-03 mentor 决定暂缓资源总览卡片拖动,当前卡片不挂载 Pointer Down / Move / Up / Cancel 拖动入口,只允许自动布局和点击聚焦。聚焦态替换中央主视窗内容,保留左侧导航、右侧对话和底部 Agent 状态栏,退出后恢复搜索、布局模式、滚动位置与选中资源;不提供通用工具栏、工具侧边栏或可拖动标题栏。阶段四已补齐安全本地文档、扩展美术媒体与音频聚焦,正文独立滚动,视频 / 音频使用内置媒体控件,失败显示空态。2026-08-26 视觉验收修正:资源总览初次适配与复位最多以 `1.5` 倍缩放卡片,避免把低尺寸卡片位图插值放大成糊图;用户主动缩放仍沿用通用画布倍率。美术资源聚焦态改为视口级大预览,保留原始资源读取与元数据,不生成第二份缩略图,图片 / 视频预览按弹窗可用高度展示并允许正文滚动。该资源总览边界不限制后续素材创作无限画布内的图片图层移动/缩放、生成和正式回写。
|
||||
- 资源卡支持点击聚焦、搜索和类型筛选。2026-07-28 起完成两套二维坐标与本地 CAS sidecar;2026-07-31 起 dependency 模式增加不持久化的原生 SVG 关系图层。2026-08-03 mentor 决定暂缓资源总览卡片拖动,当前卡片不挂载 Pointer Down / Move / Up / Cancel 拖动入口,只允许自动布局和点击聚焦。聚焦态替换中央主视窗内容,保留左侧导航、右侧对话和底部 Agent 状态栏,退出后恢复搜索、布局模式、滚动位置与选中资源;不提供通用工具栏、工具侧边栏或可拖动标题栏。阶段四已补齐安全本地文档、扩展美术媒体与音频聚焦,正文独立滚动,视频 / 音频使用内置媒体控件,失败显示空态。2026-08-30 视觉验收修正:资源总览所有栏目初次适配与复位最多以 `1.5` 倍缩放卡片,避免单个低尺寸卡片被插值放大成糊图;用户主动缩放仍沿用通用画布倍率,并按“排序模式 + 栏目”保留当前会话内的平移和缩放。美术资源聚焦态改为视口级大预览,保留原始资源读取与元数据,不生成第二份缩略图,图片 / 视频预览按弹窗可用高度展示并允许正文滚动。该资源总览边界不限制后续素材创作无限画布内的图片图层移动/缩放、生成和正式回写。
|
||||
- 运行表现层首版直接嵌入当前项目的 loopback 游戏画面,并保留素材信息和数值微调面板;两个面板保持原有 `156px` 最小高度,没有真实数据时只让正文为空,不渲染预设字段、默认数值、未载入控件或自然语言功能占位,也不随空内容收缩。`preview.start` 启动本地 server 后把真实 URL 回写工作台,`preview.open` 只激活客户端内运行视图,不再调用系统浏览器;参数调整首版仍只保留本地 UI 草稿,不修改代码或 manifest。preview server 对 UTF-8 HTML 响应注入固定同源尺寸桥脚本;注入点通过真实 HTML tokenizer 边界定位,保守处理注释异常结束、DOCTYPE 引号、script escaped / double-escaped、raw-text、template、plaintext、foreign content 与重复 `src`,并支持省略 `</body>` / `</html>`。桥以 `ResizeObserver` 观察 `documentElement / body` 根布局,结合页面 load、窗口 resize 与字体就绪重新测量;页面可见时另以 `500ms` 低频兜底探测至多 `512` 个元素的实际边界,探测截断时保留 body / scroll 上界,并按连续测量排除随 viewport 同步变化的 `100vh / 100% / bottom / right` 自反馈。相同尺寸元组去重后才以固定版本 `postMessage` 上报,不订阅整页 `MutationObserver`。宿主同时校验消息 origin 和 `event.source`,以实际内容宽高与当前容器宽高计算不超过 `1` 的等比缩放;首次适配后仍接受内容宽高的真实变化,但仅 viewport 回灌或重复内容尺寸不更新 React 状态。容器 resize 后回到原生视口重新测量;放得下时保持 `1:1`,超出时完整缩小并居中,iframe 禁止横纵滚动条,不能以 `overflow: hidden` 直接裁掉超出内容。非 UTF-8 HTML 原样返回,不因适配桥破坏已有预览。
|
||||
- 右侧继续复用现有 Project Supervisor 会话、Runtime 澄清和确认链路;输入区展示 `严格审批 / 风险审批 / 无需审批` 独立面板。P0 只有严格审批可选;风险审批和无需审批保持视觉不可用但允许点击查看原因,不替代 Runtime 的逐动作权限、确认、sandbox 或 reconciliation 门禁。风险 Rank 算法记录在 `docs/project-memory/todos/【待解决】AI游戏创作高风险审批Rank-2026-07-20.md`,前端不得自行计算。
|
||||
- 底部状态栏默认展示策划、美术、程序 3 组,并允许在同一栏展开数值、音频、发布组;状态来自 manifest 与当前 Supervisor run 的 Runtime,悬停显示当前任务与进度。累计泥点必须等待后端计费归因投影;Agent.md 编辑和自定义 Skill 在来源审核、版本、权限、sandbox 与回滚合同完备前不向普通用户开放。
|
||||
|
||||
Reference in New Issue
Block a user