Skip to content

Commit d5f965f

Browse files
author
DavidQ
committed
Separate point rounding checkbox from point delete trash action - PR_26133_080-point-rounding-and-point-delete-ui-fix
1 parent 20d1991 commit d5f965f

5 files changed

Lines changed: 110 additions & 43 deletions

File tree

docs/dev/reports/playwright_v8_coverage_report.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Playwright V8 Coverage Report
22

3-
PR: PR_26133_079-independent-middle-joint-rounding
3+
PR: PR_26133_080-point-rounding-and-point-delete-ui-fix
44

55
Source: docs/dev/reports/playwright_v8_coverage_report.txt generated by the latest npm run test:workspace-v2 run.
66

@@ -21,7 +21,7 @@ Source: docs/dev/reports/playwright_v8_coverage_report.txt generated by the late
2121

2222
## Changed Runtime JS Files Covered
2323

24-
- (95%) tools/object-vector-studio-v2/js/ToolStarterApp.js - executed lines 6964/6964; executed functions 711/746
24+
- (95%) tools/object-vector-studio-v2/js/ToolStarterApp.js - executed lines 7002/7002; executed functions 711/749
2525

2626
## Changed JS Files Considered
2727

docs/dev/reports/playwright_workspace_v2_results.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
# Playwright Workspace V2 Results
22

3-
PR: PR_26133_079-independent-middle-joint-rounding
3+
PR: PR_26133_080-point-rounding-and-point-delete-ui-fix
44

55
## Validation
66

77
- PASS: npm run test:workspace-v2
88
- Result: 54 passed
9-
- Runtime: 5.8m
9+
- Runtime: 5.4m
1010
- Browser project: playwright
1111
- Workers: 1
1212

1313
## Targeted Checks Covered
1414

1515
- Shape Geometry point rows still render exactly one Round checkbox per row.
16-
- Checking one middle polyline point now renders only that point's round marker.
17-
- Other middle joints remain square/miter and are not globally rounded.
18-
- Start and end point rounding behavior remains independent.
19-
- No global Start/Joints/End or Joints point-style controls are used for point rounding.
16+
- The global Delete Point(s) action no longer renders.
17+
- Editable point rows render a row-end trash button for deleting only that point.
18+
- Rounding checkboxes update only point rounding and do not delete rows.
19+
- Row trash deletion preserves independent rounding state for remaining points.
20+
- Deleting a point that would violate minimum geometry count is visibly rejected.
2021

2122
## Console/Runtime Errors
2223

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3994,12 +3994,15 @@ test.describe("Workspace Manager V2 bootstrap", () => {
39943994
sectionGap: Number.parseFloat(getComputedStyle(list.closest(".object-vector-studio-v2__edit-panel--polygon")).gap)
39953995
}));
39963996
expect(polygonPointListLayout).toEqual({ headingMarginBottom: 0, headingMarginTop: 0, listGap: 5, maxHeight: "none", overflowY: "visible", sectionGap: 5 });
3997-
await expect(page.locator("#objectVectorStudioV2ShapeGeometryDetails [data-polygon-side-action]")).toHaveText(["Add Point", "Delete Point(s)"]);
3997+
await expect(page.locator("#objectVectorStudioV2ShapeGeometryDetails [data-polygon-side-action]")).toHaveText(["Add Point"]);
3998+
await expect(page.locator("#objectVectorStudioV2ShapeGeometryDetails")).not.toContainText("Delete Point(s)");
39983999
await expect(page.locator("#objectVectorStudioV2ShapeGeometryDetails [data-polygon-point-round='true']")).toHaveCount(4);
4000+
await expect(page.locator("#objectVectorStudioV2ShapeGeometryDetails [data-polygon-point-delete='true']")).toHaveCount(4);
39994001
await expect(page.locator("#objectVectorStudioV2ShapeGeometryDetails [data-polygon-point-select='true']")).toHaveCount(0);
40004002
await expect.poll(() => page.locator("#objectVectorStudioV2ShapeGeometryDetails .object-vector-studio-v2__polygon-point-field").evaluateAll((rows) => rows.map((row) => row.querySelectorAll("input[type='checkbox']").length))).toEqual([1, 1, 1, 1]);
40014003
await page.locator("#objectVectorStudioV2ShapeGeometryDetails [data-polygon-point-round='true'][data-polygon-point-index='1']").check();
40024004
await expect(page.locator("#statusLog")).toHaveValue(/OK Updated point 2 rounding to round for shape row 0\./);
4005+
await expect(page.locator("#objectVectorStudioV2ShapeGeometryDetails .object-vector-studio-v2__polygon-point-field")).toHaveCount(4);
40034006
const roundedPointRender = await page.locator("#objectVectorStudioV2RenderSurface").evaluate((surface) => {
40044007
const shape = surface.querySelector("[data-shape-index='0']");
40054008
const marker = surface.querySelector("[data-point-style-caps='polygon'] [data-point-style-cap='point-1']");
@@ -4033,13 +4036,26 @@ test.describe("Workspace Manager V2 bootstrap", () => {
40334036
]);
40344037
await expect(page.locator("#statusLog")).toHaveValue(/OK Added point to shape row 0\./);
40354038
await expect.poll(() => page.evaluate(() => window.__objectVectorStudioV2App.selectedShape().geometry.points.length)).toBe(5);
4039+
await expect(page.locator("#objectVectorStudioV2ShapeGeometryDetails [data-polygon-point-delete='true']")).toHaveCount(5);
40364040
await expect.poll(() => page.evaluate(() => window.__objectVectorStudioV2App.schemaService.validatePayload(window.__objectVectorStudioV2App.currentPayload).ok)).toBe(true);
4037-
await page.locator("#objectVectorStudioV2ShapeGeometryDetails .object-vector-studio-v2__polygon-point-label").nth(2).click();
4038-
await page.locator("#objectVectorStudioV2ShapeGeometryDetails [data-polygon-side-action='delete']").click();
4041+
await page.locator("#objectVectorStudioV2ShapeGeometryDetails [data-polygon-point-delete='true'][data-polygon-point-index='2']").click();
40394042
await expect(page.locator("#objectVectorStudioV2ShapeGeometryDetails .object-vector-studio-v2__polygon-point-field")).toHaveCount(4);
4043+
await expect.poll(() => page.locator("#objectVectorStudioV2ShapeGeometryDetails .object-vector-studio-v2__polygon-point-field").evaluateAll((rows) => rows.map((row) => ({
4044+
label: row.querySelector(".object-vector-studio-v2__polygon-point-label").textContent.trim(),
4045+
rounded: row.querySelector("[data-polygon-point-round='true']").checked,
4046+
x: row.querySelector("[data-polygon-point-axis='x']").value,
4047+
y: row.querySelector("[data-polygon-point-axis='y']").value,
4048+
selected: row.dataset.polygonPointActionSelected === "true"
4049+
})))).toEqual([
4050+
{ label: "Point 1", rounded: false, x: "0", y: "-18", selected: false },
4051+
{ label: "Point 2", rounded: true, x: "14", y: "16", selected: false },
4052+
{ label: "Point 3", rounded: false, x: "0", y: "8", selected: false },
4053+
{ label: "Point 4", rounded: false, x: "-14", y: "16", selected: false }
4054+
]);
40404055
await expect.poll(() => page.locator("#objectVectorStudioV2ShapeGeometryDetails .object-vector-studio-v2__polygon-point-field").evaluateAll((rows) => rows.every((row) => row.dataset.polygonPointActionSelected !== "true"))).toBe(true);
4041-
await expect(page.locator("#statusLog")).toHaveValue(/OK Deleted 1 point from shape row 0\./);
4056+
await expect(page.locator("#statusLog")).toHaveValue(/OK Deleted point 3 from shape row 0\./);
40424057
await expect.poll(() => page.evaluate(() => window.__objectVectorStudioV2App.selectedShape().geometry.points.length)).toBe(4);
4058+
await expect.poll(() => page.evaluate(() => window.__objectVectorStudioV2App.selectedShape().style.pointRounding)).toEqual([false, true, false, false]);
40434059
await expect.poll(() => page.evaluate(() => window.__objectVectorStudioV2App.schemaService.validatePayload(window.__objectVectorStudioV2App.currentPayload).ok)).toBe(true);
40444060
await page.locator("#objectVectorStudioV2ShapeGeometryDetails [data-polygon-point-index='1'][data-polygon-point-axis='y']").fill("17");
40454061
await page.locator("#objectVectorStudioV2ShapeGeometryDetails [data-polygon-point-index='1'][data-polygon-point-axis='y']").dispatchEvent("change");
@@ -4186,11 +4202,9 @@ test.describe("Workspace Manager V2 bootstrap", () => {
41864202
expect(resizedPreviewScale.pointsOnVisibleGridLines).toBe(true);
41874203
await page.locator("#objectVectorStudioV2ResetViewButton").click();
41884204
await expect(page.locator("#objectVectorStudioV2RenderSurface")).toHaveAttribute("viewBox", "-1600 -1100 3200 2200");
4189-
await page.locator("#objectVectorStudioV2ShapeGeometryDetails .object-vector-studio-v2__polygon-point-label").nth(0).click({ modifiers: ["Control"] });
4190-
await page.locator("#objectVectorStudioV2ShapeGeometryDetails .object-vector-studio-v2__polygon-point-label").nth(1).click({ modifiers: ["Control"] });
4191-
await page.locator("#objectVectorStudioV2ShapeGeometryDetails [data-polygon-side-action='delete']").click();
4205+
await page.locator("#objectVectorStudioV2ShapeGeometryDetails [data-polygon-point-delete='true'][data-polygon-point-index='0']").click();
41924206
await expect(page.locator("#objectVectorStudioV2ShapeGeometryDetails .object-vector-studio-v2__polygon-point-field")).toHaveCount(4);
4193-
await expect(page.locator("#objectVectorStudioV2ShapeGeometryDetails [data-polygon-side-action='delete']")).toHaveAttribute("aria-invalid", "true");
4207+
await expect(page.locator("#objectVectorStudioV2ShapeGeometryDetails [data-polygon-point-delete='true'][data-polygon-point-index='0']")).toHaveAttribute("aria-invalid", "true");
41944208
await expect.poll(() => page.locator("#objectVectorStudioV2ShapeGeometryDetails .object-vector-studio-v2__polygon-point-field").evaluateAll((rows) => rows.every((row) => row.dataset.polygonPointActionSelected !== "true"))).toBe(true);
41954209
await expect(page.locator("#statusLog")).toHaveValue(/FAIL Delete point rejected for shape row 0: polygon must keep at least 4 points\./);
41964210

tools/object-vector-studio-v2/js/ToolStarterApp.js

Lines changed: 66 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2211,6 +2211,7 @@ export class ToolStarterApp {
22112211
const pointRounding = this.shapePointRoundingValues(shape);
22122212
shape.geometry.points.forEach((point, index) => {
22132213
grid.append(this.createPolygonPointRow(point, index, {
2214+
deletable: editablePolygon,
22142215
rounded: pointRounding[index] === true,
22152216
selectable: editablePolygon
22162217
}));
@@ -2289,7 +2290,7 @@ export class ToolStarterApp {
22892290
});
22902291
}
22912292

2292-
createPolygonPointRow(point, index, { rounded = false, selectable = true } = {}) {
2293+
createPolygonPointRow(point, index, { deletable = true, rounded = false, selectable = true } = {}) {
22932294
const row = document.createElement("div");
22942295
row.className = "object-vector-studio-v2__polygon-point-field";
22952296
row.dataset.polygonPointIndex = String(index);
@@ -2298,7 +2299,7 @@ export class ToolStarterApp {
22982299
row.setAttribute("role", "button");
22992300
row.setAttribute("tabindex", "0");
23002301
row.setAttribute("aria-pressed", "false");
2301-
row.title = "Select this point row for Add Point or Delete Point(s).";
2302+
row.title = "Select this point row for Add Point.";
23022303
row.addEventListener("click", (event) => this.handlePolygonPointRowSelection(event, row));
23032304
row.addEventListener("keydown", (event) => {
23042305
if (event.key === "Enter" || event.key === " ") {
@@ -2344,24 +2345,36 @@ export class ToolStarterApp {
23442345
roundCheckbox.addEventListener("change", () => this.updateSelectedShapePointRounding(index, roundCheckbox.checked));
23452346
roundLabel.append(roundCaption, roundCheckbox);
23462347
row.append(roundLabel);
2348+
if (deletable) {
2349+
const deleteButton = document.createElement("button");
2350+
deleteButton.type = "button";
2351+
deleteButton.className = "object-vector-studio-v2__polygon-point-delete";
2352+
deleteButton.dataset.polygonPointDelete = "true";
2353+
deleteButton.dataset.polygonPointIndex = String(index);
2354+
deleteButton.setAttribute("aria-label", `Delete point ${index + 1}`);
2355+
deleteButton.title = `Delete point ${index + 1}`;
2356+
this.applyIconGlyph(deleteButton, "delete");
2357+
deleteButton.addEventListener("pointerdown", (event) => event.stopPropagation());
2358+
deleteButton.addEventListener("click", (event) => {
2359+
event.preventDefault();
2360+
event.stopPropagation();
2361+
this.deletePolygonPointRow(index, deleteButton);
2362+
});
2363+
row.append(deleteButton);
2364+
}
23472365
return row;
23482366
}
23492367

23502368
createPolygonSideActions() {
23512369
const actions = document.createElement("div");
23522370
actions.className = "object-vector-studio-v2__polygon-side-actions";
2353-
[
2354-
["add", "Add Point", "add", () => this.addPolygonSideRow()],
2355-
["delete", "Delete Point(s)", "delete", () => this.deletePolygonPointRows()]
2356-
].forEach(([action, label, iconKey, handler]) => {
2357-
const button = document.createElement("button");
2358-
button.type = "button";
2359-
button.dataset.polygonSideAction = action;
2360-
button.textContent = label;
2361-
this.applyIconGlyph(button, iconKey);
2362-
button.addEventListener("click", handler);
2363-
actions.append(button);
2364-
});
2371+
const button = document.createElement("button");
2372+
button.type = "button";
2373+
button.dataset.polygonSideAction = "add";
2374+
button.textContent = "Add Point";
2375+
this.applyIconGlyph(button, "add");
2376+
button.addEventListener("click", () => this.addPolygonSideRow());
2377+
actions.append(button);
23652378
return actions;
23662379
}
23672380

@@ -6159,7 +6172,7 @@ export class ToolStarterApp {
61596172
});
61606173
}
61616174

6162-
deletePolygonPointRows() {
6175+
deletePolygonPointRow(pointIndex, sourceButton = null) {
61636176
const selected = this.selectedShape();
61646177
if (!selected || !["polygon", "polyline"].includes(shapeGeometryTool(selected))) {
61656178
this.statusLog.write("WARN Delete point skipped: no editable point-list shape is selected.");
@@ -6171,33 +6184,38 @@ export class ToolStarterApp {
61716184
}
61726185
const geometry = this.readCurrentPolygonGeometry(selected);
61736186
if (!geometry.ok) {
6187+
this.markPolygonPointDeleteInvalid(sourceButton, geometry.error);
61746188
this.statusLog.write(`FAIL Delete point rejected for shape row ${this.selectedShapeIndex}: ${geometry.error}`);
61756189
return;
61766190
}
6177-
const checkedIndexes = this.checkedPolygonPointIndexes();
6178-
if (!checkedIndexes.length) {
6179-
const message = "select at least one polygon point to delete.";
6180-
this.markPolygonSideActionInvalid("delete", message);
6181-
this.clearPolygonPointSelections();
6191+
const normalizedIndex = Number(pointIndex);
6192+
if (!Number.isInteger(normalizedIndex) || normalizedIndex < 0 || normalizedIndex >= geometry.value.points.length) {
6193+
const message = `point ${normalizedIndex + 1} is not available.`;
6194+
this.markPolygonPointDeleteInvalid(sourceButton, message);
61826195
this.statusLog.write(`FAIL Delete point rejected for shape row ${this.selectedShapeIndex}: ${message}`);
61836196
return;
61846197
}
6185-
const checkedSet = new Set(checkedIndexes);
6186-
const nextPoints = geometry.value.points.filter((_, index) => !checkedSet.has(index));
6187-
const nextPointRounding = this.currentPointRoundingRows(geometry.value.points.length).filter((_, index) => !checkedSet.has(index));
61886198
const minPointCount = shapeGeometryTool(selected) === "polyline" ? 2 : 4;
6199+
if (geometry.value.points.length - 1 < minPointCount) {
6200+
const message = `${shapeGeometryTool(selected)} must keep at least ${minPointCount} points.`;
6201+
this.markPolygonPointDeleteInvalid(sourceButton, message);
6202+
this.statusLog.write(`FAIL Delete point rejected for shape row ${this.selectedShapeIndex}: ${message}`);
6203+
return;
6204+
}
6205+
const nextPoints = geometry.value.points.filter((_, index) => index !== normalizedIndex);
6206+
const nextPointRounding = this.currentPointRoundingRows(geometry.value.points.length).filter((_, index) => index !== normalizedIndex);
61896207
if (nextPoints.length < minPointCount) {
61906208
const message = `${shapeGeometryTool(selected)} must keep at least ${minPointCount} points.`;
6191-
this.markPolygonSideActionInvalid("delete", message);
6192-
this.clearPolygonPointSelections();
6209+
this.markPolygonPointDeleteInvalid(sourceButton, message);
61936210
this.statusLog.write(`FAIL Delete point rejected for shape row ${this.selectedShapeIndex}: ${message}`);
61946211
return;
61956212
}
61966213
this.rebuildPolygonPointList(nextPoints, nextPointRounding);
61976214
this.clearPolygonPointSelections();
61986215
this.clearPolygonSideActionValidity();
6216+
this.clearPolygonPointDeleteValidity();
61996217
this.applyShapeGeometryEdits({
6200-
okMessage: `OK Deleted ${checkedIndexes.length} point${checkedIndexes.length === 1 ? "" : "s"} from shape row ${this.selectedShapeIndex}.`
6218+
okMessage: `OK Deleted point ${normalizedIndex + 1} from shape row ${this.selectedShapeIndex}.`
62016219
});
62026220
}
62036221

@@ -6220,6 +6238,23 @@ export class ToolStarterApp {
62206238
});
62216239
}
62226240

6241+
markPolygonPointDeleteInvalid(sourceButton, message) {
6242+
if (!sourceButton) {
6243+
return;
6244+
}
6245+
sourceButton.dataset.validationState = "invalid";
6246+
sourceButton.setAttribute("aria-invalid", "true");
6247+
sourceButton.title = message;
6248+
}
6249+
6250+
clearPolygonPointDeleteValidity() {
6251+
this.elements.shapeGeometryDetails.querySelectorAll("[data-polygon-point-delete='true']").forEach((button) => {
6252+
delete button.dataset.validationState;
6253+
button.removeAttribute("aria-invalid");
6254+
button.title = `Delete point ${Number(button.dataset.polygonPointIndex) + 1}`;
6255+
});
6256+
}
6257+
62236258
currentPointRoundingRows(expectedCount = null) {
62246259
const checkboxes = Array.from(this.elements.shapeGeometryDetails.querySelectorAll("[data-polygon-point-round='true']"));
62256260
const pointRounding = checkboxes
@@ -6297,6 +6332,11 @@ export class ToolStarterApp {
62976332
if (roundCheckbox) {
62986333
roundCheckbox.setAttribute("aria-label", `Round point ${index + 1}`);
62996334
}
6335+
const deleteButton = row.querySelector("[data-polygon-point-delete='true']");
6336+
if (deleteButton) {
6337+
deleteButton.setAttribute("aria-label", `Delete point ${index + 1}`);
6338+
deleteButton.title = `Delete point ${index + 1}`;
6339+
}
63006340
});
63016341
}
63026342

tools/object-vector-studio-v2/styles/toolStarter.css

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1674,7 +1674,7 @@ textarea:hover {
16741674

16751675
.object-vector-studio-v2__polygon-point-field {
16761676
display: grid;
1677-
grid-template-columns: 62px minmax(0, 1fr) minmax(0, 1fr) max-content;
1677+
grid-template-columns: 62px minmax(0, 1fr) minmax(0, 1fr) max-content 24px;
16781678
align-items: center;
16791679
gap: 6px;
16801680
}
@@ -1741,6 +1741,18 @@ textarea:hover {
17411741
accent-color: var(--tool-starter-accent);
17421742
}
17431743

1744+
.object-vector-studio-v2__polygon-point-delete {
1745+
width: 24px;
1746+
height: 24px;
1747+
min-height: 24px;
1748+
padding: 0 !important;
1749+
color: var(--tool-starter-danger);
1750+
}
1751+
1752+
.object-vector-studio-v2__polygon-point-delete[data-ovs-icon]::before {
1753+
font-size: 0.92rem;
1754+
}
1755+
17441756
.object-vector-studio-v2__polygon-side-actions {
17451757
display: flex;
17461758
flex-wrap: wrap;

0 commit comments

Comments
 (0)