From 034c11d4e9bb2f5b34578a1fe406382362de02fc Mon Sep 17 00:00:00 2001 From: mes123456 Date: Fri, 13 Nov 2026 10:02:46 +0800 Subject: [PATCH] fix(web): route camera shortcuts to the active viewport Co-Authored-By: Claude Code --- tools/web/course/record-module-video.mjs | 6 +++--- web/app/src/app/App.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/web/course/record-module-video.mjs b/tools/web/course/record-module-video.mjs index 6493225a2..79ffe869f 100644 --- a/tools/web/course/record-module-video.mjs +++ b/tools/web/course/record-module-video.mjs @@ -232,16 +232,16 @@ function narrationChapters(module, stepIds) { case "M02:navigate": return (async () => { const canvas = page.locator(".viewport-area canvas.viewport-canvas").first(); const box = await canvas.boundingBox(); if (box) { await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2); await page.mouse.down({ button: "middle" }); await page.mouse.move(box.x + box.width / 2 + 60, box.y + box.height / 2 + 30, { steps: 10 }); await page.mouse.up({ button: "middle" }); } })(); case "M02:search": return (async () => { await domClick(page.getByRole("button", { name: "操作搜索" })); await page.waitForTimeout(300); await page.keyboard.type("移动"); await page.waitForTimeout(500); await page.keyboard.press("Escape"); })(); case "M03:edit": return (async () => { await actions.workspace(page, "Modeling"); await actions.enterEdit(page); })(); - case "M03:selectall": return domClick(page.getByRole("button", { name: "Select All", exact: true }).first()); + case "M03:selectall": return (async () => { await domClick(page.getByRole("button", { name: "Select All", exact: true }).first()); await page.waitForTimeout(700); })(); case "M03:extrude": return (async () => { await domClick(page.getByRole("button", { name: "EXTRUDE", exact: true })); await page.waitForTimeout(700); await page.getByRole("button", { name: "撤销", exact: true }).click().catch(() => undefined); })(); case "M04:open": return actions.openStudio(page); - case "M04:modifiers": return (async () => { await actions.workspace(page, "Layout"); await page.getByText("Modifiers", { exact: true }).first().scrollIntoViewIfNeeded().catch(() => undefined); await page.waitForTimeout(500); })(); + case "M04:modifiers": return (async () => { await actions.workspace(page, "Layout"); await page.getByText("Modifier Stack", { exact: true }).first().scrollIntoViewIfNeeded().catch(() => undefined); await page.waitForTimeout(500); })(); case "M05:shading": return actions.workspace(page, "Shading"); case "M05:assign": return actions.assignReMaterial(page); case "M06:uv": return actions.uv(page); case "M07:paint": return (async () => { await actions.assignReMaterial(page); await actions.importTexture(page); await actions.paint(page); })(); case "M08:render": return (async () => { await actions.workspace(page, "Rendering"); await page.locator('select[aria-label="PBR 渲染分辨率"]').selectOption("1024").catch(() => undefined); await page.waitForTimeout(400); const render = page.getByRole("button", { name: "渲染 PBR", exact: true }).first(); if (await render.count()) { await domClick(render); await page.waitForFunction(() => { const image = document.querySelector('.pbr-render-result img'); return image instanceof HTMLImageElement && image.naturalWidth >= 1024; }, null, { timeout: 180_000 }).catch(() => undefined); } })(); - case "M09:result": return actions.workspace(page, "Rendering"); + case "M09:result": return (async () => { await actions.workspace(page, "Rendering"); await page.waitForTimeout(600); const image = page.locator(".pbr-render-result img").first(); await image.scrollIntoViewIfNeeded().catch(() => undefined); })(); case "M10:keys": return actions.addKeyframes(page); case "M11:graph": return actions.compositor(page); case "M12:graph": return actions.geonodes(page); diff --git a/web/app/src/app/App.tsx b/web/app/src/app/App.tsx index a58cb2302..152cdbebb 100644 --- a/web/app/src/app/App.tsx +++ b/web/app/src/app/App.tsx @@ -6187,7 +6187,7 @@ export function App({localOnly=false,course}:{localOnly?:boolean;course?:Availab case "view.camera": case "view.cameraToView": case "view.cameraLock": { - const canvas = document.querySelector("canvas.viewport-canvas"); + const canvas = activeViewportCanvas(); const mapping: Record = { "view.camera": "CAMERA", "view.cameraToView": "CAMERA_TO_VIEW", "view.cameraLock": "CAMERA_LOCK" }; canvas?.dispatchEvent(new CustomEvent("viewport-command", { detail: mapping[action.id] })); break;