@@ -1575,7 +1575,6 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15751575 titles: {
15761576 add: title("#objectVectorStudioV2AddObjectButton"),
15771577 angle: title("#objectVectorStudioV2AngleSnapButton"),
1578- autoCenter: title("#objectVectorStudioV2AutoCenterButton"),
15791578 grid: title("#objectVectorStudioV2GridRenderButton"),
15801579 polygon: title("[data-shape-tool='polygon']"),
15811580 polyline: title("[data-shape-tool='polyline']"),
@@ -1584,7 +1583,6 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15841583 zoomIn: title("#objectVectorStudioV2ZoomInButton")
15851584 },
15861585 viewportIcons: {
1587- autoCenter: icon("#objectVectorStudioV2AutoCenterButton"),
15881586 down: icon("#objectVectorStudioV2PanDownButton"),
15891587 reset: icon("#objectVectorStudioV2ResetViewButton"),
15901588 up: icon("#objectVectorStudioV2PanUpButton"),
@@ -1633,7 +1631,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
16331631 snap: { iconKey: "snapGrid", iconName: "nf-md-grid_large" }
16341632 });
16351633 expect(iconStyleState.previewEditIcons).toMatchObject({
1636- copy: { iconKey: "copy", iconName: "nf-cod -copy" },
1634+ copy: { iconKey: "copy", iconName: "nf-fa -copy" },
16371635 paste: { iconKey: "paste", iconName: "nf-oct-paste" },
16381636 redo: { iconKey: "redo", iconName: "nf-md-redo" },
16391637 undo: { iconKey: "undo", iconName: "nf-md-undo" }
@@ -1694,7 +1692,6 @@ test.describe("Workspace Manager V2 bootstrap", () => {
16941692 triangle: "none"
16951693 });
16961694 expect(Object.fromEntries(Object.entries(iconStyleState.viewportIcons).map(([key, value]) => [key, value.iconKey]))).toEqual({
1697- autoCenter: "center",
16981695 down: "panDown",
16991696 reset: "reset",
17001697 up: "panUp",
@@ -1706,7 +1703,6 @@ test.describe("Workspace Manager V2 bootstrap", () => {
17061703 expect(iconStyleState.titles).toEqual({
17071704 add: "Add a schema-valid object to the loaded payload",
17081705 angle: "Snap Angle switches Rotate to a constrained dropdown using the selected 15, 30, 45, or 90 degree step.",
1709- autoCenter: "Disabled until a schema-valid object is selected.",
17101706 grid: "Show or hide the preview grid",
17111707 polygon: "Create a polygon shape on the selected object. Click to add points.\n\nDouble-click to finish.",
17121708 polyline: "Create a polyline shape on the selected object. Click to add points.\n\nDouble-click to finish.",
@@ -2137,7 +2133,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
21372133 await expect(page.locator("#objectVectorStudioV2RenderSurface")).toHaveAttribute("viewBox", "-1600 -1100 3200 2200");
21382134 await expect(page.locator("#objectVectorStudioV2RenderSurface [data-center-origin='0,0']")).toHaveCount(1);
21392135 await expect(page.locator("#objectVectorStudioV2RenderSurface [data-center-origin='0,0']")).toHaveAttribute("r", "9");
2140- await expect(page.locator("#objectVectorStudioV2ViewportControls button")).toHaveText(["Out", "In", "Up", "Down", "Left", "Right", "View", "Center", "Auto Center" ]);
2136+ await expect(page.locator("#objectVectorStudioV2ViewportControls button")).toHaveText(["Out", "In", "Up", "Down", "Left", "Right", "View", "Center"]);
21412137 await expect(page.locator("#objectVectorStudioV2CenterDotButton")).toHaveAttribute("aria-pressed", "true");
21422138 await page.locator("#objectVectorStudioV2PanRightButton").click();
21432139 await page.locator("#objectVectorStudioV2PanDownButton").click();
@@ -2192,22 +2188,37 @@ test.describe("Workspace Manager V2 bootstrap", () => {
21922188 await expect(page.locator("#objectVectorStudioV2ObjectTransform #objectVectorStudioV2MoveShapeButton")).toHaveCount(1);
21932189 const transformSummaryLayout = await page.locator("#objectVectorStudioV2ObjectTransform").evaluate((panel) => {
21942190 const scaleRow = panel.querySelector(".object-vector-studio-v2__scale-control-row");
2191+ const autoCenterRow = panel.querySelector(".object-vector-studio-v2__transform-control-row--auto-center");
2192+ const autoCenterButton = panel.querySelector("#objectVectorStudioV2AutoCenterButton");
21952193 const summary = panel.querySelector(".object-vector-studio-v2__transform-summary");
21962194 const summaryStyle = getComputedStyle(summary);
21972195 return {
2196+ autoCenterAfterScale: Boolean(scaleRow && autoCenterRow && (scaleRow.compareDocumentPosition(autoCenterRow) & Node.DOCUMENT_POSITION_FOLLOWING)),
2197+ autoCenterButtonText: autoCenterButton?.textContent.trim() || "",
2198+ autoCenterTitle: autoCenterButton?.title || "",
2199+ summaryAfterAutoCenter: Boolean(autoCenterRow && summary && (autoCenterRow.compareDocumentPosition(summary) & Node.DOCUMENT_POSITION_FOLLOWING)),
21982200 summaryAfterScale: Boolean(scaleRow && summary && (scaleRow.compareDocumentPosition(summary) & Node.DOCUMENT_POSITION_FOLLOWING)),
21992201 summaryCentered: summaryStyle.textAlign === "center",
22002202 summaryStartsWithoutTransform: !summary.textContent.trim().startsWith("Transform"),
2201- summaryTopAtBottom: summary.getBoundingClientRect().top >= scaleRow .getBoundingClientRect().bottom
2203+ summaryTopAtBottom: summary.getBoundingClientRect().top >= autoCenterRow .getBoundingClientRect().bottom
22022204 };
22032205 });
22042206 expect(transformSummaryLayout).toEqual({
2207+ autoCenterAfterScale: true,
2208+ autoCenterButtonText: "Auto Center",
2209+ autoCenterTitle: "Balance Center",
2210+ summaryAfterAutoCenter: true,
22052211 summaryAfterScale: true,
22062212 summaryCentered: true,
22072213 summaryStartsWithoutTransform: true,
22082214 summaryTopAtBottom: true
22092215 });
22102216 const transformIconState = await page.locator("#objectVectorStudioV2ObjectTransform").evaluate((panel) => ({
2217+ autoCenter: {
2218+ iconKey: panel.querySelector("#objectVectorStudioV2AutoCenterButton").dataset.ovsIconKey,
2219+ iconName: panel.querySelector("#objectVectorStudioV2AutoCenterButton").dataset.ovsIconName,
2220+ title: panel.querySelector("#objectVectorStudioV2AutoCenterButton").title
2221+ },
22112222 resize: {
22122223 iconKey: panel.querySelector("#objectVectorStudioV2ResizeShapeButton").dataset.ovsIconKey,
22132224 iconName: panel.querySelector("#objectVectorStudioV2ResizeShapeButton").dataset.ovsIconName,
@@ -2216,6 +2227,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
22162227 scaleActionRemoved: panel.querySelector("#objectVectorStudioV2ScaleShapeButton") === null
22172228 }));
22182229 expect(transformIconState).toEqual({
2230+ autoCenter: { iconKey: "center", iconName: "nf-fa-dot_circle_o", title: "Balance Center" },
22192231 resize: { iconKey: "resize", iconName: "nf-md-resize", title: "Resize Geometry" },
22202232 scaleActionRemoved: true
22212233 });
@@ -2371,6 +2383,19 @@ test.describe("Workspace Manager V2 bootstrap", () => {
23712383 selectIds: ["objectVectorStudioV2RotateSnapSelect", "objectVectorStudioV2SnapAngleStepSelect"],
23722384 visibleInputIds: ["objectVectorStudioV2RotateInput"],
23732385 visibleSelectIds: []
2386+ },
2387+ {
2388+ allOneLine: true,
2389+ axisLabels: [],
2390+ buttonId: "objectVectorStudioV2AutoCenterButton",
2391+ buttonText: "Auto Center",
2392+ buttonTitle: "Balance Center",
2393+ inputIds: [],
2394+ label: "Center",
2395+ rowType: "auto-center",
2396+ selectIds: [],
2397+ visibleInputIds: [],
2398+ visibleSelectIds: []
23742399 }
23752400 ]);
23762401 await expect(page.locator("#objectVectorStudioV2RotateInput")).toHaveAttribute("min", "-359");
@@ -5983,6 +6008,78 @@ test.describe("Workspace Manager V2 bootstrap", () => {
59836008 { rotation: 0, x: 0, y: 0 }
59846009 ]);
59856010
6011+ const selectedSetRotateBefore = await page.evaluate(() => {
6012+ const app = window.__objectVectorStudioV2App;
6013+ const indexes = app.selectedObject().shapes.map((shape, shapeIndex) => shapeIndex);
6014+ app.selectedShapeIndex = 0;
6015+ app.selectedShapeIndexes = new Set(indexes);
6016+ app.directSelectedShapeIndexes = new Set(indexes);
6017+ app.renderPayload();
6018+ const frame = app.activeFrame();
6019+ const bounds = app.shapeSetBounds(app.selectedObject(), indexes, { includeInvisible: false });
6020+ return {
6021+ bounds,
6022+ origins: indexes.map((shapeIndex) => {
6023+ const shape = app.effectiveShapeForFrame(app.selectedObject().shapes[shapeIndex], frame, shapeIndex);
6024+ const transform = app.ensureShapeTransform(shape);
6025+ return {
6026+ index: shapeIndex,
6027+ point: app.transformedPoint(transform.origin, transform),
6028+ rotation: transform.rotation
6029+ };
6030+ }),
6031+ pivot: {
6032+ x: Number((bounds.x + bounds.width / 2).toFixed(3)),
6033+ y: Number((bounds.y + bounds.height / 2).toFixed(3))
6034+ }
6035+ };
6036+ });
6037+ const selectionBoundsBeforeSelectedSetRotate = await page.locator("#objectVectorStudioV2RenderSurface [data-selection-bounds='0']").evaluate((box) => ({
6038+ height: Number(box.getAttribute("height")),
6039+ width: Number(box.getAttribute("width")),
6040+ x: Number(box.getAttribute("x")),
6041+ y: Number(box.getAttribute("y"))
6042+ }));
6043+ await page.locator("#objectVectorStudioV2RotateInput").fill("90");
6044+ await page.locator("#objectVectorStudioV2RotateShapeButton").click();
6045+ await expect(page.locator("#statusLog")).toHaveValue(/OK Rotated selected set \(4 shapes\) by 90 degrees\./);
6046+ const selectedSetRotateAfter = await page.evaluate(() => {
6047+ const app = window.__objectVectorStudioV2App;
6048+ const frame = app.activeFrame();
6049+ return app.selectedObject().shapes.map((shape, shapeIndex) => {
6050+ const effectiveShape = app.effectiveShapeForFrame(shape, frame, shapeIndex);
6051+ const transform = app.ensureShapeTransform(effectiveShape);
6052+ return {
6053+ index: shapeIndex,
6054+ point: app.transformedPoint(transform.origin, transform),
6055+ rotation: transform.rotation
6056+ };
6057+ });
6058+ });
6059+ const rotatePointAround = (point, pivot, degrees) => {
6060+ const radians = (degrees * Math.PI) / 180;
6061+ const relativeX = point.x - pivot.x;
6062+ const relativeY = point.y - pivot.y;
6063+ return {
6064+ x: pivot.x + relativeX * Math.cos(radians) - relativeY * Math.sin(radians),
6065+ y: pivot.y + relativeX * Math.sin(radians) + relativeY * Math.cos(radians)
6066+ };
6067+ };
6068+ selectedSetRotateBefore.origins.forEach((before) => {
6069+ const after = selectedSetRotateAfter.find((entry) => entry.index === before.index);
6070+ const expectedPoint = rotatePointAround(before.point, selectedSetRotateBefore.pivot, 90);
6071+ expect(after.point.x).toBeCloseTo(expectedPoint.x, 3);
6072+ expect(after.point.y).toBeCloseTo(expectedPoint.y, 3);
6073+ expect(after.rotation).toBe((before.rotation + 90) % 360);
6074+ });
6075+ const selectionBoundsAfterSelectedSetRotate = await page.locator("#objectVectorStudioV2RenderSurface [data-selection-bounds='0']").evaluate((box) => ({
6076+ height: Number(box.getAttribute("height")),
6077+ width: Number(box.getAttribute("width")),
6078+ x: Number(box.getAttribute("x")),
6079+ y: Number(box.getAttribute("y"))
6080+ }));
6081+ expect(selectionBoundsAfterSelectedSetRotate).not.toEqual(selectionBoundsBeforeSelectedSetRotate);
6082+
59866083 await page.evaluate(() => {
59876084 const app = window.__objectVectorStudioV2App;
59886085 app.selectedShapeIndex = 0;
0 commit comments