diff --git a/src/viewport-three/viewport-host.ts b/src/viewport-three/viewport-host.ts index cffb2abf..412c2ab6 100644 --- a/src/viewport-three/viewport-host.ts +++ b/src/viewport-three/viewport-host.ts @@ -5099,13 +5099,18 @@ export class ViewportHost { return; } + if (this.currentTransformSession.kind === "active") { + if (this.currentTransformSession.sourcePanelId !== this.panelId) { + return; + } + + event.preventDefault(); + this.transformCommitHandler?.(this.currentTransformSession); + return; + } + const transformHandle = this.pickTransformHandle(event); - const interactionSession = - this.currentTransformSession.kind === "active" - ? this.currentTransformSession.sourcePanelId === this.panelId - ? this.currentTransformSession - : null - : this.getDisplayedTransformSession(); + const interactionSession = this.getDisplayedTransformSession(); if (transformHandle !== null && interactionSession !== null) { event.preventDefault(); @@ -5164,21 +5169,6 @@ export class ViewportHost { return; } - if (this.currentTransformSession.kind === "active") { - if (this.currentTransformSession.sourcePanelId !== this.panelId) { - return; - } - - if ( - this.currentTransformSession.source !== "gizmo" || - this.currentTransformSession.sourcePanelId === this.panelId - ) { - event.preventDefault(); - this.transformCommitHandler?.(this.currentTransformSession); - return; - } - } - if (this.toolMode === "create" && this.creationPreview !== null) { const previewCenter = this.getCreationPreviewCenter( event, diff --git a/tests/e2e/transform-gizmo-commit.e2e.ts b/tests/e2e/transform-gizmo-commit.e2e.ts new file mode 100644 index 00000000..cc981e2f --- /dev/null +++ b/tests/e2e/transform-gizmo-commit.e2e.ts @@ -0,0 +1,168 @@ +import { expect, test } from "@playwright/test"; + +import { createBoxBrush } from "../../src/document/brushes"; +import { createEmptySceneDocument } from "../../src/document/scene-document"; +import { + getEditorStoreSnapshot, + getViewportCanvas, + replaceSceneDocument +} from "./viewport-test-helpers"; + +test("confirming a live scale transform over the gizmo commits the current preview", async ({ + page +}) => { + const pageErrors: string[] = []; + const consoleErrors: string[] = []; + + page.on("pageerror", (error) => { + pageErrors.push(error.message); + }); + + page.on("console", (message) => { + if (message.type() === "error") { + consoleErrors.push(message.text()); + } + }); + + await page.goto("/"); + await page.evaluate((storageKey) => { + window.localStorage.removeItem(storageKey); + }, "webeditor3d.scene-document-draft"); + await page.reload(); + + test.skip( + (await page.getByText("Viewport Unavailable").count()) > 0, + "WebGL is unavailable in this Playwright environment." + ); + + const brush = createBoxBrush({ + id: "brush-transform-gizmo-commit", + name: "Transform Gizmo Commit Fixture", + center: { + x: 0, + y: 1, + z: 0 + }, + size: { + x: 2, + y: 2, + z: 2 + } + }); + + await replaceSceneDocument(page, { + ...createEmptySceneDocument({ name: "Transform Gizmo Commit Scene" }), + brushes: { + [brush.id]: brush + } + }); + + await page.evaluate(({ brushId, target }) => { + const store = (window as Window & { + __webeditor3dEditorStore?: { + getState(): { + viewportPanels: { + topLeft: { + cameraState: { + target: { x: number; y: number; z: number }; + perspectiveOrbit: { radius: number; theta: number; phi: number }; + orthographicZoom: number; + }; + }; + }; + }; + setSelection(selection: { kind: "brushes"; ids: string[] }): void; + setViewportPanelViewMode(panelId: "topLeft", viewMode: "top"): void; + setViewportPanelCameraState( + panelId: "topLeft", + cameraState: { + target: { x: number; y: number; z: number }; + perspectiveOrbit: { radius: number; theta: number; phi: number }; + orthographicZoom: number; + } + ): void; + }; + }).__webeditor3dEditorStore; + + if (store === undefined) { + throw new Error("Editor store debug hook is unavailable."); + } + + const topLeftCameraState = store.getState().viewportPanels.topLeft.cameraState; + + store.setSelection({ + kind: "brushes", + ids: [brushId] + }); + store.setViewportPanelViewMode("topLeft", "top"); + store.setViewportPanelCameraState("topLeft", { + ...topLeftCameraState, + target, + orthographicZoom: 8 + }); + }, { brushId: brush.id, target: brush.center }); + + const viewportCanvas = getViewportCanvas(page); + await expect(viewportCanvas).toHaveCount(1); + + const canvasBox = await viewportCanvas.boundingBox(); + + if (canvasBox === null) { + throw new Error("Viewport canvas is missing."); + } + + const centerX = canvasBox.x + canvasBox.width * 0.5; + const centerY = canvasBox.y + canvasBox.height * 0.5; + + await page.mouse.move(centerX, centerY); + await page.keyboard.press("S"); + await expect(page.getByTestId("viewport-transform-preview-topLeft")).toContainText("scale"); + + await page.mouse.move(canvasBox.x + canvasBox.width * 0.72, centerY); + + const previewSnapshot = await getEditorStoreSnapshot(page); + expect(previewSnapshot.viewportTransientState.transformSession.kind).toBe("active"); + + if (previewSnapshot.viewportTransientState.transformSession.kind !== "active") { + throw new Error("Expected an active scale transform session."); + } + + expect(previewSnapshot.viewportTransientState.transformSession.operation).toBe("scale"); + + if (previewSnapshot.viewportTransientState.transformSession.preview.kind !== "brush") { + throw new Error("Expected a brush transform preview."); + } + + const previewSizeX = + previewSnapshot.viewportTransientState.transformSession.preview.size.x; + + expect(previewSizeX).not.toBe(brush.size.x); + + await page.mouse.click(centerX, centerY); + + const finalSnapshot = await getEditorStoreSnapshot(page); + expect(finalSnapshot.viewportTransientState.transformSession.kind).toBe("none"); + + const committedSize = await page.evaluate((brushId) => { + const store = (window as Window & { + __webeditor3dEditorStore?: { + getState(): { + document: { + brushes: Record; + }; + }; + }; + }).__webeditor3dEditorStore; + + if (store === undefined) { + throw new Error("Editor store debug hook is unavailable."); + } + + return store.getState().document.brushes[brushId]?.size ?? null; + }, brush.id); + + expect(committedSize).not.toBeNull(); + expect(committedSize?.x).toBeCloseTo(previewSizeX, 5); + expect(pageErrors).toEqual([]); + expect(consoleErrors).toEqual([]); +});