Skip to content

Commit c49644a

Browse files
author
DavidQ
committed
Move object tile delete to compact right-side action icon stack - PR_26133_016-object-tile-action-icon-cleanup
1 parent bf8c710 commit c49644a

5 files changed

Lines changed: 97 additions & 27 deletions

File tree

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# PR_26133_014 Playwright V8 Coverage Report
1+
# PR_26133_016 Playwright V8 Coverage Report
22

33
Coverage source: `docs/dev/reports/playwright_v8_coverage_report.txt`, refreshed by the final `npm run test:workspace-v2` run.
44

@@ -7,12 +7,12 @@ Coverage source: `docs/dev/reports/playwright_v8_coverage_report.txt`, refreshed
77
- Coverage is advisory only; no thresholds are enforced.
88
- Workspace Manager V2 entry point: 91%.
99
- Object Vector Studio V2 runtime coverage entries from the generated report:
10-
- `tools/object-vector-studio-v2/js/ToolStarterApp.js`: 92%, executed lines 3701/3701, executed functions 395/428.
10+
- `tools/object-vector-studio-v2/js/ToolStarterApp.js`: 92%, executed lines 3737/3737, executed functions 400/434.
1111
- The generated report lists `tests/playwright/tools/WorkspaceManagerV2.spec.mjs` as changed JS not collected by browser runtime coverage.
1212

1313
## Validation Context
1414

1515
- Main command: `npm run test:workspace-v2`.
1616
- Result: 47 passed.
17-
- Focused Object Vector Studio V2 layout, preview coordinate, animation-state, and asset-authoring scenarios passed.
18-
- Coverage includes the runtime paths for cleaned Object Details summary rendering and explicit selected-shape color labeling. Tag-row layout, Polygon Geometry spacing, and removal of duplicate/helper text are covered by Playwright layout/text assertions.
17+
- Focused Object Vector Studio V2 layout, preview coordinate, mouse-editing, animation-state, and asset-authoring scenarios passed as part of the workspace-v2 run.
18+
- Coverage includes the runtime paths for object tile rendering, compact icon controls, runtime lock/visibility controls, and targeted object tile deletion.

docs/dev/reports/playwright_workspace_v2_results.md

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
1-
# PR_26133_014 Workspace V2 Results
1+
# PR_26133_016 Workspace V2 Results
22

33
## Command Results
44

55
- `node --check tools/object-vector-studio-v2/js/ToolStarterApp.js`: passed.
66
- `node --check tests/playwright/tools/WorkspaceManagerV2.spec.mjs`: passed.
7-
- `npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --grep "Object Vector Studio V2 (layout shell|preview coordinates|animation states|asset authoring controls)"`: 4 passed.
87
- `npm run test:workspace-v2`: 47 passed.
98
- `git diff --check`: passed with LF-to-CRLF working-copy warnings for touched files.
109

1110
## Targeted Object Vector Studio V2 Verification
1211

13-
- Confirmed Object Tag input and Add button render inline with no visible `Tag` label text.
14-
- Confirmed Polygon Geometry section spacing is reduced to 5px, including section gap, point-list gap, and heading margins.
15-
- Confirmed Object Details no longer renders the helper text or duplicate `Selected Shape` summary text.
16-
- Confirmed Object Details keeps concise shape/group metadata without the duplicate selected-shape heading.
17-
- Confirmed selected shape color is labeled as `Fill Color`, `Stroke Color`, or `Transparent Color` so transparent/background state is not confused with the selected color.
18-
- Confirmed Object Vector Studio V2 geometry editing and animation detail states remain covered by the targeted workspace-v2 slice.
19-
- Confirmed targeted Object Vector Studio tests reported no console/page errors.
12+
- Confirmed object tile Delete renders as a textless X icon button at the far right of each object tile.
13+
- Confirmed object tile actions are stacked compactly in order: Show/Hide, Lock, Delete.
14+
- Confirmed object tile action buttons use the reduced 26px control size.
15+
- Confirmed Delete is disabled while the object is runtime-locked, while existing Show/Hide and Lock behavior remains intact.
16+
- Confirmed tile Delete removes only the targeted object tile; the Shield Pickup tile delete removed `object.asteroids.shield-pickup` while preserving `object.asteroids.object-1`.
17+
- Confirmed workspace-v2 Object Vector Studio V2 scenarios reported no console/page errors.
2018

2119
## Scope Checks
2220

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1704,10 +1704,36 @@ test.describe("Workspace Manager V2 bootstrap", () => {
17041704
rectangleUsesPreviewDrawingScale: true,
17051705
unitGridSpacingRemoved: true
17061706
});
1707+
const objectTileActionLayout = await page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1']").evaluate((tile) => {
1708+
const controls = Array.from(tile.querySelectorAll("[data-object-control]"));
1709+
const rects = controls.map((control) => control.getBoundingClientRect());
1710+
const deleteButton = tile.querySelector("[data-object-control='delete']");
1711+
const deleteRect = deleteButton.getBoundingClientRect();
1712+
const tileRect = tile.getBoundingClientRect();
1713+
return {
1714+
allTextEmpty: controls.every((control) => control.textContent.trim() === ""),
1715+
controlOrder: controls.map((control) => control.dataset.objectControl),
1716+
deleteAtFarRight: Math.abs(tileRect.right - deleteRect.right) <= 12,
1717+
deleteTitle: deleteButton.title,
1718+
iconSizes: rects.map((rect) => Math.round(Math.max(rect.width, rect.height))),
1719+
stacked: rects.every((rect, index) => index === 0
1720+
|| (Math.abs(rect.left - rects[index - 1].left) <= 1 && rect.top > rects[index - 1].top))
1721+
};
1722+
});
1723+
expect(objectTileActionLayout).toEqual({
1724+
allTextEmpty: true,
1725+
controlOrder: ["visibility", "lock", "delete"],
1726+
deleteAtFarRight: true,
1727+
deleteTitle: "Delete this object",
1728+
iconSizes: [26, 26, 26],
1729+
stacked: true
1730+
});
17071731
await expect(page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-object-control='visibility']")).toHaveText("");
17081732
await expect(page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-object-control='lock']")).toHaveText("");
1733+
await expect(page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-object-control='delete']")).toHaveText("");
17091734
await expect(page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-object-control='visibility'] .object-vector-studio-v2__tile-icon--eye")).toHaveCount(1);
17101735
await expect(page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-object-control='lock'] .object-vector-studio-v2__tile-icon--lock")).toHaveCount(1);
1736+
await expect(page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-object-control='delete'] .object-vector-studio-v2__tile-icon--delete")).toHaveCount(1);
17111737
await page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-object-control='visibility']").click();
17121738
await expect(page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1']")).toHaveClass(/is-hidden/);
17131739
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-shape-id]")).toHaveCount(0);
@@ -1716,11 +1742,13 @@ test.describe("Workspace Manager V2 bootstrap", () => {
17161742
await page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-object-control='lock']").click();
17171743
await expect(page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1']")).toHaveClass(/is-locked/);
17181744
await expect(page.locator("#objectVectorStudioV2RenameObjectButton")).toBeDisabled();
1745+
await expect(page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-object-control='delete']")).toBeDisabled();
17191746
await page.locator('[data-shape-tool="line"]').click();
17201747
await expect(page.locator("#objectVectorStudioV2ObjectsCount")).toHaveText("(18 obj, 2 shapes)");
17211748
await expect(page.locator("#statusLog")).toHaveValue(/WARN Create line blocked: object Asteroids Ship is locked for this runtime session\./);
17221749
await page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-object-control='lock']").click();
17231750
await expect(page.locator("#objectVectorStudioV2RenameObjectButton")).toBeEnabled();
1751+
await expect(page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-object-control='delete']")).toBeEnabled();
17241752

17251753
const forceLeftPanelScroll = async () => page.evaluate(() => {
17261754
const leftPanel = document.querySelector(".tool-starter__panel--left");
@@ -2081,11 +2109,11 @@ test.describe("Workspace Manager V2 bootstrap", () => {
20812109
await expect(page.locator("#objectVectorStudioV2JsonDetails")).toContainText('"shapes": []');
20822110
await expect(page.locator("#statusLog")).toHaveValue(/OK Added object Shield Pickup with object\/game\/name id object\.asteroids\.shield-pickup\./);
20832111

2084-
await page.locator("#objectVectorStudioV2DeleteObjectButton").click();
2112+
await page.locator('[data-object-id="object.asteroids.shield-pickup"] [data-object-control="delete"]').click();
20852113
await expect(page.locator("#objectVectorStudioV2ObjectsCount")).toHaveText("(18 obj, 2 shapes)");
20862114
await expect(page.locator('[data-object-id="object.asteroids.shield-pickup"]')).toHaveCount(0);
20872115
await expect(page.locator('[data-object-id="object.asteroids.object-1"]')).toHaveAttribute("aria-pressed", "true");
2088-
await expect(page.locator("#statusLog")).toHaveValue(/OK Deleted object Shield Pickup\./);
2116+
await expect(page.locator("#statusLog")).toHaveValue(/OK Deleted object Shield Pickup from object tile delete\./);
20892117

20902118
const leftAccordionLayout = await page.locator(".tool-starter__panel--left > .accordion-v2").evaluateAll((sections) => (
20912119
sections.map((section) => {

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

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -940,7 +940,8 @@ export class ToolStarterApp {
940940
controls.className = "object-vector-studio-v2__object-tile-controls";
941941
controls.append(
942942
this.createObjectTileControl(object, "visibility"),
943-
this.createObjectTileControl(object, "lock")
943+
this.createObjectTileControl(object, "lock"),
944+
this.createObjectTileControl(object, "delete")
944945
);
945946
tile.append(selectButton, controls);
946947
tile.addEventListener("click", () => {
@@ -980,17 +981,29 @@ export class ToolStarterApp {
980981
button.className = "object-vector-studio-v2__object-tile-control";
981982
button.type = "button";
982983
const isVisibility = kind === "visibility";
983-
const isActive = isVisibility ? !this.isObjectHidden(object.id) : this.isObjectLocked(object.id);
984+
const isDelete = kind === "delete";
985+
const isLocked = this.isObjectLocked(object.id);
986+
const isActive = isVisibility ? !this.isObjectHidden(object.id) : !isDelete && isLocked;
984987
button.dataset.objectControl = kind;
985988
button.dataset.objectControlId = object.id;
986-
button.setAttribute("aria-pressed", String(isActive));
987-
button.setAttribute("aria-label", isVisibility ? `${isActive ? "Hide" : "Show"} object ${object.name}` : `${isActive ? "Unlock" : "Lock"} object ${object.name}`);
988-
button.append(this.createIconSpan(isVisibility ? "eye" : "lock", isActive));
989-
button.title = isVisibility ? "Toggle object visibility" : "Toggle runtime object lock";
989+
if (isDelete) {
990+
button.classList.add("object-vector-studio-v2__object-tile-control--delete");
991+
button.setAttribute("aria-label", `Delete object ${object.name}`);
992+
button.append(this.createIconSpan("delete", true));
993+
button.title = isLocked ? "Unlock object before deleting" : "Delete this object";
994+
button.disabled = isLocked;
995+
} else {
996+
button.setAttribute("aria-pressed", String(isActive));
997+
button.setAttribute("aria-label", isVisibility ? `${isActive ? "Hide" : "Show"} object ${object.name}` : `${isActive ? "Unlock" : "Lock"} object ${object.name}`);
998+
button.append(this.createIconSpan(isVisibility ? "eye" : "lock", isActive));
999+
button.title = isVisibility ? "Toggle object visibility" : "Toggle runtime object lock";
1000+
}
9901001
button.addEventListener("click", (event) => {
9911002
event.stopPropagation();
9921003
if (isVisibility) {
9931004
this.toggleObjectVisibility(object.id);
1005+
} else if (isDelete) {
1006+
this.deleteObjectById(object.id, "object tile delete");
9941007
} else {
9951008
this.toggleObjectLock(object.id);
9961009
}
@@ -2618,6 +2631,31 @@ export class ToolStarterApp {
26182631
this.commitPayloadUpdate(nextPayload, selectedObjectId, selectedShapeId, `OK Deleted object ${selected.name}.`, "Delete object failed schema validation");
26192632
}
26202633

2634+
deleteObjectById(objectId, sourceLabel) {
2635+
const selected = this.currentPayload?.objects.find((object) => object.id === objectId);
2636+
if (!selected) {
2637+
this.statusLog.write(`WARN Delete object skipped: object id ${objectId || "unknown"} is not available.`);
2638+
return;
2639+
}
2640+
if (this.isObjectLocked(selected.id)) {
2641+
this.statusLog.write(`WARN Delete object blocked: object ${selected.name} is locked for this runtime session.`);
2642+
return;
2643+
}
2644+
2645+
const nextPayload = this.cloneCurrentPayload();
2646+
nextPayload.objects = nextPayload.objects.filter((object) => object.id !== selected.id);
2647+
const selectedObjectStillExists = nextPayload.objects.some((object) => object.id === this.selectedObjectId);
2648+
const nextSelectedObject = selectedObjectStillExists
2649+
? nextPayload.objects.find((object) => object.id === this.selectedObjectId)
2650+
: nextPayload.objects[0] || null;
2651+
const selectedObjectId = nextSelectedObject?.id || "";
2652+
const selectedShapeStillExists = nextSelectedObject?.shapes.some((shape) => shape.id === this.selectedShapeId);
2653+
const selectedShapeId = selectedShapeStillExists ? this.selectedShapeId : nextSelectedObject ? sortedShapes(nextSelectedObject)[0]?.id || "" : "";
2654+
this.hiddenObjectIds.delete(selected.id);
2655+
this.lockedObjectIds.delete(selected.id);
2656+
this.commitPayloadUpdate(nextPayload, selectedObjectId, selectedShapeId, `OK Deleted object ${selected.name} from ${sourceLabel}.`, "Delete object failed schema validation");
2657+
}
2658+
26212659
flattenSelectedObject() {
26222660
const selected = this.selectedObject();
26232661
if (!selected) {

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -960,25 +960,31 @@ textarea:hover {
960960
}
961961

962962
.object-vector-studio-v2__object-tile-controls {
963-
align-self: end;
963+
align-self: center;
964+
justify-self: end;
964965
position: relative;
965966
z-index: 2;
966967
display: flex;
967968
flex-direction: column;
968969
flex-wrap: nowrap;
969-
justify-content: end;
970-
gap: 4px;
970+
justify-content: center;
971+
align-items: center;
972+
gap: 2px;
971973
}
972974

973975
.object-vector-studio-v2__object-tile-control {
974976
position: relative;
975977
z-index: 3;
976-
width: 34px;
977-
height: 34px;
978-
min-height: 34px;
978+
width: 26px;
979+
height: 26px;
980+
min-height: 26px;
979981
padding: 0 !important;
980982
}
981983

984+
.object-vector-studio-v2__object-tile-control--delete {
985+
color: var(--tool-starter-danger);
986+
}
987+
982988
.object-vector-studio-v2__object-tile-shapes {
983989
grid-column: 1 / -1;
984990
display: grid;

0 commit comments

Comments
 (0)