Skip to content

Commit 23f6bd4

Browse files
author
DavidQ
committed
Keep drawing hint screen sized restore Enter completion and revert text placement behavior - PR_26133_072-drawing-hint-enter-complete-and-text-restore
1 parent f4c25ab commit 23f6bd4

5 files changed

Lines changed: 133 additions & 59 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_071-preview-hint-stroke-width-and-text-placement-fixes
3+
PR: PR_26133_072-drawing-hint-enter-complete-and-text-restore
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 6410/6410; executed functions 649/680
24+
- (95%) tools/object-vector-studio-v2/js/ToolStarterApp.js - executed lines 6432/6432; executed functions 651/682
2525

2626
## Changed JS Files Considered
2727

docs/dev/reports/playwright_workspace_v2_results.md

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

3-
PR: PR_26133_071-preview-hint-stroke-width-and-text-placement-fixes
3+
PR: PR_26133_072-drawing-hint-enter-complete-and-text-restore
44

55
## Validation
66

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

1313
## Targeted Checks Covered
1414

15-
- Polygon drawing shows a cursor-following "Double-click / Enter to complete" hint with pointer-events disabled.
16-
- Polygon and Polyline keep existing multi-point completion behavior.
17-
- Stroke width 20 drawing previews use proportional dash spacing instead of the small default dash pattern.
18-
- Completed wide-stroke shapes render solid and immediately keep the active Stroke Width.
19-
- Text placement preview renders readable text instead of a wide stroked blob.
20-
- Committed text keeps transparent fill plus active stroke color, opacity, and width under current style rules.
15+
- Drawing hint keeps "Double-click / Enter to complete" and renders as a fixed-size HTML overlay outside SVG canvas scaling.
16+
- Hint follows the pointer with offset and keeps pointer-events disabled.
17+
- Hint size remains stable at 100%, 300%, and 50% zoom states.
18+
- Polygon and Polyline Enter completion remain covered through drawing helpers.
19+
- Polygon double-click completion is explicitly verified.
20+
- Text preview uses restored normal SVG text style attributes while avoiding dashed preview artifacts for wide stroke width.
2121

2222
## Console/Runtime Errors
2323

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 67 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,20 @@ async function mouseClickObjectVectorLogicalPoint(page, x, y, options = {}) {
114114
await page.mouse.click(point.x, point.y, options);
115115
}
116116

117+
async function doubleClickObjectVectorLogicalPoint(page, x, y) {
118+
const point = await objectVectorLogicalClientPoint(page, x, y);
119+
await page.evaluate((clientPoint) => {
120+
const surface = document.querySelector("#objectVectorStudioV2RenderSurface");
121+
surface.dispatchEvent(new MouseEvent("dblclick", {
122+
bubbles: true,
123+
button: 0,
124+
cancelable: true,
125+
clientX: clientPoint.x,
126+
clientY: clientPoint.y
127+
}));
128+
}, point);
129+
}
130+
117131
async function dragObjectVectorLogicalPoints(page, start, end) {
118132
const startPoint = await objectVectorLogicalClientPoint(page, start.x, start.y);
119133
const endPoint = await objectVectorLogicalClientPoint(page, end.x, end.y);
@@ -3487,30 +3501,52 @@ test.describe("Workspace Manager V2 bootstrap", () => {
34873501
await page.locator('[data-shape-tool="polygon"]').click();
34883502
await expect(page.locator("#statusLog")).toHaveValue(/OK Drawing mode selected: Polygon\. Click to add points\.\n\nDouble-click to finish\./);
34893503
await moveObjectVectorLogicalPoint(page, { x: -30, y: -20 });
3490-
await expect(page.locator("#objectVectorStudioV2RenderSurface .object-vector-studio-v2__drawing-hint")).toHaveText("Double-click / Enter to complete");
3491-
const polygonHintState = await page.locator("#objectVectorStudioV2RenderSurface .object-vector-studio-v2__drawing-hint").evaluate((hint) => {
3492-
const text = hint.querySelector("text");
3504+
await expect(page.locator("#objectVectorStudioV2WorkArea .object-vector-studio-v2__drawing-hint")).toHaveText("Double-click / Enter to complete");
3505+
const polygonHintState = await page.locator("#objectVectorStudioV2WorkArea .object-vector-studio-v2__drawing-hint").evaluate((hint) => {
34933506
const app = window.__objectVectorStudioV2App;
3507+
const rect = hint.getBoundingClientRect();
34943508
return {
3495-
hintX: Number(text.getAttribute("x")),
3496-
hintY: Number(text.getAttribute("y")),
3509+
fontSize: getComputedStyle(hint).fontSize,
3510+
height: Math.round(rect.height),
3511+
hintX: Math.round(rect.left),
3512+
hintY: Math.round(rect.top),
34973513
pointerEvents: getComputedStyle(hint).pointerEvents,
3498-
pointerX: app.drawingPreviewPoint.x * 10,
3499-
pointerY: app.drawingPreviewPoint.y * 10,
3500-
textPointerEvents: getComputedStyle(text).pointerEvents
3514+
pointerX: Math.round(app.drawingHintClientPoint.x),
3515+
pointerY: Math.round(app.drawingHintClientPoint.y)
35013516
};
35023517
});
35033518
expect(polygonHintState.pointerEvents).toBe("none");
3504-
expect(polygonHintState.textPointerEvents).toBe("none");
35053519
expect(polygonHintState.hintX).toBeGreaterThan(polygonHintState.pointerX);
35063520
expect(polygonHintState.hintY).toBeGreaterThan(polygonHintState.pointerY);
35073521
await moveObjectVectorLogicalPoint(page, { x: -25, y: -15 });
3508-
const movedPolygonHint = await page.locator("#objectVectorStudioV2RenderSurface .object-vector-studio-v2__drawing-hint text").evaluate((text) => ({
3509-
x: Number(text.getAttribute("x")),
3510-
y: Number(text.getAttribute("y"))
3522+
const movedPolygonHint = await page.locator("#objectVectorStudioV2WorkArea .object-vector-studio-v2__drawing-hint").evaluate((hint) => ({
3523+
x: Math.round(hint.getBoundingClientRect().left),
3524+
y: Math.round(hint.getBoundingClientRect().top)
35113525
}));
35123526
expect(movedPolygonHint.x).toBeGreaterThan(polygonHintState.hintX);
35133527
expect(movedPolygonHint.y).toBeGreaterThan(polygonHintState.hintY);
3528+
const zoomHintStates = await page.locator("#objectVectorStudioV2WorkArea .object-vector-studio-v2__drawing-hint").evaluate((hint) => {
3529+
const app = window.__objectVectorStudioV2App;
3530+
const collect = (zoom) => {
3531+
app.viewport.zoom = zoom;
3532+
app.updateViewport();
3533+
app.renderWorkSurface();
3534+
const nextHint = document.querySelector("#objectVectorStudioV2WorkArea .object-vector-studio-v2__drawing-hint");
3535+
const rect = nextHint.getBoundingClientRect();
3536+
return {
3537+
fontSize: getComputedStyle(nextHint).fontSize,
3538+
height: Math.round(rect.height),
3539+
zoom
3540+
};
3541+
};
3542+
const states = [collect(0.1), collect(0.3), collect(0.05)];
3543+
app.viewport.zoom = 0.1;
3544+
app.updateViewport();
3545+
app.renderWorkSurface();
3546+
return states;
3547+
});
3548+
expect(zoomHintStates.map((state) => state.fontSize)).toEqual(["12px", "12px", "12px"]);
3549+
expect(new Set(zoomHintStates.map((state) => state.height)).size).toBe(1);
35143550
await page.locator('[data-shape-tool="polyline"]').click();
35153551
await expect(page.locator("#statusLog")).toHaveValue(/OK Drawing mode selected: Polyline\. Click to add points\.\n\nDouble-click to finish\./);
35163552
await page.locator('[data-shape-tool="rectangle"]').click();
@@ -3688,19 +3724,21 @@ test.describe("Workspace Manager V2 bootstrap", () => {
36883724
await clickObjectVectorLogicalPoint(page, 70, 60);
36893725
await moveObjectVectorLogicalPoint(page, { x: 76, y: 66 });
36903726
const textPreview = await page.locator("#objectVectorStudioV2RenderSurface text.object-vector-studio-v2__drawing-preview").evaluate((preview) => ({
3691-
fill: preview.style.fill || preview.getAttribute("fill"),
3692-
stroke: preview.style.stroke,
3693-
strokeWidth: preview.style.strokeWidth,
3727+
dash: preview.style.strokeDasharray,
3728+
fill: preview.getAttribute("fill"),
3729+
stroke: preview.getAttribute("stroke"),
3730+
strokeWidth: Number(preview.getAttribute("stroke-width")),
36943731
text: preview.textContent.trim(),
36953732
tool: preview.dataset.drawingPreviewTool
36963733
}));
3697-
expect(textPreview.fill).toMatch(/#6fd3ff|rgb\(111, 211, 255\)/);
3698-
expect(textPreview).toMatchObject({
3699-
stroke: "none",
3734+
expect(textPreview).toEqual({
3735+
dash: "none",
3736+
fill: "#00000000",
3737+
stroke: "#6fd3ff",
3738+
strokeWidth: 20,
37003739
text: "Text",
37013740
tool: "text"
37023741
});
3703-
expect(["0", "0px"]).toContain(textPreview.strokeWidth);
37043742
await clickObjectVectorLogicalPoint(page, 76, 66);
37053743
const committedTextStyle = await page.evaluate(() => ({ ...window.__objectVectorStudioV2App.selectedShape().style }));
37063744
expect(committedTextStyle).toMatchObject({
@@ -3710,6 +3748,16 @@ test.describe("Workspace Manager V2 bootstrap", () => {
37103748
strokeWidth: 20
37113749
});
37123750

3751+
const shapeCountBeforeDoubleClick = await page.evaluate(() => window.__objectVectorStudioV2App.selectedObject().shapes.length);
3752+
await page.locator('[data-shape-tool="polygon"]').click();
3753+
await clickObjectVectorLogicalPoint(page, 80, -40);
3754+
await clickObjectVectorLogicalPoint(page, 95, -40);
3755+
await clickObjectVectorLogicalPoint(page, 95, -25);
3756+
await clickObjectVectorLogicalPoint(page, 80, -25);
3757+
await doubleClickObjectVectorLogicalPoint(page, 80, -25);
3758+
await expect.poll(async () => page.evaluate(() => window.__objectVectorStudioV2App.selectedObject().shapes.length)).toBe(shapeCountBeforeDoubleClick + 1);
3759+
await expect(page.locator("#statusLog")).toHaveValue(/OK Created polygon shape on Drawing Flow from double-click\./);
3760+
37133761
expect(pageErrors).toEqual([]);
37143762
expect(consoleErrors).toEqual([]);
37153763
} finally {

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

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,7 @@ export class ToolStarterApp {
489489
this.isPaintDragging = false;
490490
this.activeDrawing = null;
491491
this.drawingPreviewPoint = null;
492+
this.drawingHintClientPoint = null;
492493
this.previewPointerEdit = null;
493494
this.transformInputValues = new Map();
494495
this.stateControlStateId = "";
@@ -2441,6 +2442,7 @@ export class ToolStarterApp {
24412442
renderWorkSurface() {
24422443
const object = this.selectedObject();
24432444
this.elements.renderSurface.classList.toggle("is-drawing-mode", Boolean(this.activeDrawing));
2445+
this.removeDrawingHint();
24442446
this.elements.renderSurface.replaceChildren();
24452447
this.renderSvgGrid();
24462448
if (!object) {
@@ -2698,34 +2700,52 @@ export class ToolStarterApp {
26982700
renderDrawingHint() {
26992701
const drawing = this.activeDrawing;
27002702
if (!drawing || !["polygon", "polyline"].includes(drawing.tool) || !this.drawingPreviewPoint) {
2703+
this.removeDrawingHint();
27012704
return;
27022705
}
2703-
const point = {
2704-
x: Number(this.scaleDrawingValue(this.drawingPreviewPoint.x, OBJECT_PREVIEW_DRAWING_SCALE)),
2705-
y: Number(this.scaleDrawingValue(this.drawingPreviewPoint.y, OBJECT_PREVIEW_DRAWING_SCALE))
2706+
const workArea = this.elements.renderSurface.closest(".object-vector-studio-v2__work-area");
2707+
if (!workArea) {
2708+
return;
2709+
}
2710+
const clientPoint = this.drawingHintClientPoint || this.clientPointFromDrawingPoint(this.drawingPreviewPoint);
2711+
if (!clientPoint) {
2712+
this.removeDrawingHint();
2713+
return;
2714+
}
2715+
const workAreaBounds = workArea.getBoundingClientRect();
2716+
const hint = document.createElement("div");
2717+
hint.className = "object-vector-studio-v2__drawing-hint";
2718+
hint.dataset.drawingHintTool = drawing.tool;
2719+
hint.textContent = "Double-click / Enter to complete";
2720+
hint.style.left = `${Math.round(clientPoint.x - workAreaBounds.left + 12)}px`;
2721+
hint.style.top = `${Math.round(clientPoint.y - workAreaBounds.top + 16)}px`;
2722+
workArea.append(hint);
2723+
}
2724+
2725+
removeDrawingHint() {
2726+
this.elements.renderSurface.closest(".object-vector-studio-v2__work-area")
2727+
?.querySelector(".object-vector-studio-v2__drawing-hint")
2728+
?.remove();
2729+
}
2730+
2731+
clientPointFromDrawingPoint(point) {
2732+
const bounds = this.elements.renderSurface.getBoundingClientRect();
2733+
if (!bounds.width || !bounds.height) {
2734+
return null;
2735+
}
2736+
const viewWidth = DEFAULT_VIEWPORT.width / this.viewport.zoom;
2737+
const viewHeight = DEFAULT_VIEWPORT.height / this.viewport.zoom;
2738+
const drawingX = point.x * OBJECT_PREVIEW_DRAWING_SCALE;
2739+
const drawingY = point.y * OBJECT_PREVIEW_DRAWING_SCALE;
2740+
return {
2741+
x: bounds.left + ((drawingX - this.viewport.x + viewWidth / 2) / viewWidth) * bounds.width,
2742+
y: bounds.top + ((drawingY - this.viewport.y + viewHeight / 2) / viewHeight) * bounds.height
27062743
};
2707-
const unitsPerPixel = this.svgUnitsPerCssPixel();
2708-
const group = document.createElementNS(SVG_NS, "g");
2709-
const text = document.createElementNS(SVG_NS, "text");
2710-
group.classList.add("object-vector-studio-v2__drawing-hint");
2711-
group.dataset.drawingHintTool = drawing.tool;
2712-
group.setAttribute("pointer-events", "none");
2713-
text.classList.add("object-vector-studio-v2__drawing-hint-text");
2714-
text.setAttribute("x", this.formatViewportNumber(point.x + 12 * unitsPerPixel));
2715-
text.setAttribute("y", this.formatViewportNumber(point.y + 16 * unitsPerPixel));
2716-
text.textContent = "Double-click / Enter to complete";
2717-
group.append(text);
2718-
this.elements.renderSurface.append(group);
27192744
}
27202745

27212746
applyDrawingPreviewPresentation(element, previewShape) {
27222747
if (shapeGeometryTool(previewShape) === "text") {
2723-
element.style.fill = previewShape.style.stroke || "#ffffff";
2724-
element.style.fillOpacity = String(previewShape.style.strokeOpacity ?? 1);
2725-
element.style.stroke = "none";
27262748
element.style.strokeDasharray = "none";
2727-
element.style.strokeWidth = "0";
2728-
element.style.paintOrder = "normal";
27292749
return;
27302750
}
27312751
element.style.strokeDasharray = this.drawingPreviewDashArray(previewShape.style.strokeWidth);
@@ -3430,6 +3450,7 @@ export class ToolStarterApp {
34303450
this.previewPointerEdit = null;
34313451
this.activeDrawing = this.createDrawingState(tool);
34323452
this.drawingPreviewPoint = null;
3453+
this.drawingHintClientPoint = null;
34333454
this.renderWorkSurface();
34343455
const objectHint = this.selectedObject() ? "" : " Select a schema-valid object before committing geometry.";
34353456
const drawingHelp = ["polygon", "polyline"].includes(tool)
@@ -3472,6 +3493,7 @@ export class ToolStarterApp {
34723493
const tool = this.activeDrawing.tool;
34733494
this.activeDrawing = null;
34743495
this.drawingPreviewPoint = null;
3496+
this.drawingHintClientPoint = null;
34753497
this.activeTool = "select";
34763498
const selectButton = this.elements.toolToggles.find((candidate) => candidate.dataset.shapeTool === "select") || null;
34773499
this.setActiveToolButton(selectButton);
@@ -3517,6 +3539,7 @@ export class ToolStarterApp {
35173539
event.stopPropagation();
35183540
const point = this.snapCanvasPoint(this.pointerPreviewPoint(event));
35193541
this.drawingPreviewPoint = point;
3542+
this.drawingHintClientPoint = { x: event.clientX, y: event.clientY };
35203543
if (drawing.flow === "points") {
35213544
if (!drawing.points.length) {
35223545
drawing.style = this.drawingStyleFromActivePalette();
@@ -3558,6 +3581,7 @@ export class ToolStarterApp {
35583581
}
35593582
drawing.preview = point;
35603583
this.drawingPreviewPoint = point;
3584+
this.drawingHintClientPoint = { x: event.clientX, y: event.clientY };
35613585
this.renderWorkSurface();
35623586
}
35633587

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

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,6 +1107,7 @@ textarea:hover {
11071107
}
11081108

11091109
.object-vector-studio-v2__work-area {
1110+
position: relative;
11101111
min-height: 0;
11111112
flex: 0 0 auto;
11121113
display: grid;
@@ -1445,18 +1446,19 @@ textarea:hover {
14451446
}
14461447

14471448
.object-vector-studio-v2__drawing-hint {
1448-
pointer-events: none;
1449-
}
1450-
1451-
.object-vector-studio-v2__drawing-hint-text {
1452-
fill: var(--tool-starter-text);
1449+
position: absolute;
1450+
z-index: 6;
1451+
max-width: min(260px, calc(100% - 24px));
1452+
border: 1px solid rgba(221, 214, 254, 0.45);
1453+
border-radius: 6px;
1454+
background: rgba(2, 6, 23, 0.92);
1455+
color: var(--tool-starter-text);
14531456
font-family: "0xProto Nerd Font", "Cascadia Mono", monospace;
14541457
font-size: 12px;
1455-
paint-order: stroke;
1456-
stroke: rgba(2, 6, 23, 0.95);
1457-
stroke-linejoin: round;
1458-
stroke-width: 4px;
1459-
vector-effect: non-scaling-stroke;
1458+
line-height: 1.2;
1459+
padding: 4px 7px;
1460+
white-space: nowrap;
1461+
pointer-events: none;
14601462
}
14611463

14621464
.object-vector-studio-v2__snap-targets {

0 commit comments

Comments
 (0)