@reekon-tools/boldr-cad 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +12 -1
  2. package/dist/data/provider.d.ts +2 -1
  3. package/dist/data/sync.js +4 -1
  4. package/dist/data/sync.js.map +1 -1
  5. package/dist/editor/actions.js +10 -1
  6. package/dist/editor/actions.js.map +1 -1
  7. package/dist/editor/advance.d.ts +2 -0
  8. package/dist/editor/advance.js +23 -0
  9. package/dist/editor/advance.js.map +1 -0
  10. package/dist/editor/dimSize.d.ts +13 -0
  11. package/dist/editor/dimSize.js +50 -0
  12. package/dist/editor/dimSize.js.map +1 -0
  13. package/dist/editor/gestures.js +30 -7
  14. package/dist/editor/gestures.js.map +1 -1
  15. package/dist/editor/keypad.d.ts +8 -0
  16. package/dist/editor/keypad.js +15 -1
  17. package/dist/editor/keypad.js.map +1 -1
  18. package/dist/editor/laser.d.ts +9 -0
  19. package/dist/editor/laser.js +38 -12
  20. package/dist/editor/laser.js.map +1 -1
  21. package/dist/editor/ports.d.ts +9 -0
  22. package/dist/editor/ports.js +2 -0
  23. package/dist/editor/ports.js.map +1 -1
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.js +1 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/model/clockwise.d.ts +11 -0
  28. package/dist/model/clockwise.js +123 -0
  29. package/dist/model/clockwise.js.map +1 -0
  30. package/dist/model/settings.d.ts +2 -0
  31. package/dist/model/settings.js +1 -1
  32. package/dist/model/settings.js.map +1 -1
  33. package/dist/react/BottomBar.js +43 -3
  34. package/dist/react/BottomBar.js.map +1 -1
  35. package/dist/react/PlanCanvas.native.js +15 -9
  36. package/dist/react/PlanCanvas.native.js.map +1 -1
  37. package/dist/react/host.d.ts +6 -0
  38. package/dist/react/host.js +11 -1
  39. package/dist/react/host.js.map +1 -1
  40. package/dist/react/ports.js +2 -1
  41. package/dist/react/ports.js.map +1 -1
  42. package/dist/react/store.d.ts +1 -1
  43. package/dist/react/store.js.map +1 -1
  44. package/dist/render/draw.d.ts +7 -0
  45. package/dist/render/draw.js +17 -8
  46. package/dist/render/draw.js.map +1 -1
  47. package/dist/render/navigate.d.ts +11 -6
  48. package/dist/render/navigate.js +34 -20
  49. package/dist/render/navigate.js.map +1 -1
  50. package/dist/tools/dim.d.ts +6 -0
  51. package/dist/tools/dim.js +28 -4
  52. package/dist/tools/dim.js.map +1 -1
  53. package/package.json +1 -1
  54. package/src/data/provider.ts +2 -2
  55. package/src/data/sync.ts +4 -1
  56. package/src/editor/actions.ts +6 -1
  57. package/src/editor/advance.ts +23 -0
  58. package/src/editor/dimSize.ts +53 -0
  59. package/src/editor/gestures.ts +31 -9
  60. package/src/editor/keypad.ts +17 -1
  61. package/src/editor/laser.ts +38 -10
  62. package/src/editor/ports.ts +10 -0
  63. package/src/index.ts +1 -2
  64. package/src/model/clockwise.ts +121 -0
  65. package/src/model/settings.ts +3 -1
  66. package/src/react/BottomBar.tsx +46 -2
  67. package/src/react/PlanCanvas.native.tsx +25 -12
  68. package/src/react/host.ts +11 -1
  69. package/src/react/ports.ts +3 -2
  70. package/src/react/store.ts +1 -1
  71. package/src/render/draw.ts +20 -9
  72. package/src/render/navigate.ts +31 -20
  73. package/src/tools/dim.ts +28 -4
  74. package/src/ui/domPorts.ts +2 -0
package/src/react/host.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { applyReading, laser } from '../editor/laser';
1
+ import { applyReading, laser, readingOverwrites } from '../editor/laser';
2
2
  import { store } from '../model/store';
3
3
  import { closeSurveyRun, setSurveyCursor, takeSurveyPoint } from '../tools/survey';
4
4
  import { notify } from './store';
@@ -33,6 +33,16 @@ export function laserReading(inches: number, capture?: unknown): boolean {
33
33
  return true;
34
34
  }
35
35
 
36
+ /**
37
+ * Whether a reading of `inches`, passed to laserReading now, would replace a length or diameter already measured on
38
+ * the selected line, wall edge or curve (see readingOverwrites), for a host that warns first: hold the reading, ask,
39
+ * and pass it on only once the person agrees. Nothing changes by asking. False when no drawing is open to take it.
40
+ */
41
+ export function laserReadingOverwrites(inches: number): boolean {
42
+ if (!editorHost.open || !store.project || !Number.isFinite(inches) || inches <= 0) return false;
43
+ return readingOverwrites(inches);
44
+ }
45
+
36
46
  /**
37
47
  * A point measured by a templating device (BOLDR's T1R), in inches, on the plan's axes: x to the right, y UP. Turning
38
48
  * the device's own frame into that is the host's job, and it's where the handedness goes: a device whose angle turns
@@ -59,8 +59,8 @@ function hostRequest(spec: KeypadSpec): HostKeypadRequest {
59
59
  };
60
60
  }
61
61
 
62
- /** A slider above the bar (an image's opacity or rotation, the tiles' size): opened, swapped for another, or closed by its button again. */
63
- function toggleImagePop(kind: 'opacity' | 'rotate' | 'tilesize'): void {
62
+ /** A slider above the bar (an image's opacity or rotation, the tiles' or measurement tiles' size): opened, swapped for another, or closed by its button again. */
63
+ function toggleImagePop(kind: 'opacity' | 'rotate' | 'tilesize' | 'dimsize'): void {
64
64
  ui.imgPop = ui.imgPop === kind ? null : kind;
65
65
  ui.menu = false;
66
66
  // (a toast would sit over the slider: it's done with once the slider opens)
@@ -153,6 +153,7 @@ export const reactPorts: EditorPorts = {
153
153
  toggleImageOpacity() { toggleImagePop('opacity'); },
154
154
  toggleImageRotation() { toggleImagePop('rotate'); },
155
155
  toggleTileSize() { toggleImagePop('tilesize'); },
156
+ toggleDimSize() { toggleImagePop('dimsize'); },
156
157
  editRoomName(roomId) { ui.drawer = 'rooms'; ui.renameRoom = roomId; notify(); },
157
158
  setCursor(cursor, where = 'plan') { cursorTargets[where]?.(cursor); },
158
159
  interacting: setInteracting,
@@ -59,7 +59,7 @@ export const ui = sessionState(() => ({
59
59
  * what's open above the bar: the selected image's opacity or turn, or the link tiles' size (sliders), or Open's
60
60
  * question (here, or side by side)
61
61
  */
62
- imgPop: null as null | 'opacity' | 'rotate' | 'tilesize' | 'open',
62
+ imgPop: null as null | 'opacity' | 'rotate' | 'tilesize' | 'dimsize' | 'open',
63
63
  /** the tool each shared place on the rail draws with (the last picked; editor/mode.ts TOOL_GROUPS), and the menu of
64
64
  a group's tools, open beside its place at x, y */
65
65
  picked: { shapes: 'line', marks: 'arrow' } as Record<string, string>,
@@ -41,7 +41,8 @@ export function rr(c: C2D, x: number, y: number, w: number, h: number, r: number
41
41
  }
42
42
 
43
43
  export interface ChipOpts {
44
- bg: string; fg: string; border?: [string, number?] | null; font?: string; pad?: number; h?: number; minW?: number;
44
+ /** `r`: its corners' radius (5) */
45
+ bg: string; fg: string; border?: [string, number?] | null; font?: string; pad?: number; h?: number; minW?: number; r?: number;
45
46
  kind?: HitChip['kind']; id?: string; rot?: number;
46
47
  /**
47
48
  * 'link': a measurement of the job sets it. The chip is then Figma's linked measurement (2795:77149): a white badge
@@ -80,13 +81,22 @@ export function chipSize(c: C2D, text: string, { font = '600 12px', pad = 7, h =
80
81
  return { w: Math.max(minW, c.measureText(text).width + room), h };
81
82
  }
82
83
 
84
+ /**
85
+ * A measurement tile's size — a dimension's chip: its font, padding, height and corners — times the file's measurement
86
+ * tile size (settings.chipPct, editor/dimSize.ts). At 100%, chip()'s own: 12 px, 7, 22, 5.
87
+ */
88
+ export function dimChip(): Pick<ChipOpts, 'font' | 'pad' | 'h' | 'r'> {
89
+ const k = settings.chipPct / 100;
90
+ return k === 1 ? {} : { font: '600 ' + 12 * k + 'px', pad: 7 * k, h: 22 * k, r: 5 * k };
91
+ }
92
+
83
93
  /** Rounded label on the canvas; registers a hit region when `kind` is given. */
84
94
  export function chip(c: C2D, text: string, cx: number, cy: number, opts: ChipOpts): number {
85
95
  const { bg, fg, border, kind, id, rot = 0, mark } = opts;
86
96
  const { w, h } = chipSize(c, text, opts), x = cx - w / 2, y = cy - h / 2;
87
97
  c.save();
88
98
  if (rot) { c.translate(cx, cy); c.rotate(rot); c.translate(-cx, -cy); }
89
- rr(c, x, y, w, h, 5);
99
+ rr(c, x, y, w, h, opts.r ?? 5);
90
100
  c.fillStyle = bg; c.fill();
91
101
  if (border) { c.lineWidth = border[1] || 1; c.strokeStyle = border[0]; c.stroke(); }
92
102
  c.fillStyle = fg; c.textAlign = 'center'; c.textBaseline = 'middle';
@@ -627,7 +637,7 @@ function drawDims(c: C2D, T: Xform, sk: Sketch): void {
627
637
  }
628
638
  c.restore();
629
639
  }
630
- const text = wrap(fmtAng(val)), opts: ChipOpts = { bg, fg, border, kind: 'dim', id, mark };
640
+ const text = wrap(fmtAng(val)), opts: ChipOpts = { bg, fg, border, kind: 'dim', id, mark, ...dimChip() };
631
641
  // (left where it was drawn: 50 px out along the bisector, or as far as its corners stay clear of the corner — a
632
642
  // linked chip is wider)
633
643
  const sz = chipSize(c, text, opts), out = Math.max(50, Math.hypot(sz.w, sz.h) / 2 + 28);
@@ -636,7 +646,7 @@ function drawDims(c: C2D, T: Xform, sk: Sketch): void {
636
646
  continue;
637
647
  }
638
648
  if (d.type === 'diam' && 'o' in A) {
639
- drawDiameter(c, T, A.o, A.r, d, d.rad ? 'R ' + wrap(fmtLen(val / 2)) : 'Ø ' + wrap(fmtLen(val)), bad ? TH.red : ref ? TH.chipline : TH.blue, { bg, fg, border, kind: 'dim', id, mark }, ref);
649
+ drawDiameter(c, T, A.o, A.r, d, d.rad ? 'R ' + wrap(fmtLen(val / 2)) : 'Ø ' + wrap(fmtLen(val)), bad ? TH.red : ref ? TH.chipline : TH.blue, { bg, fg, border, kind: 'dim', id, mark, ...dimChip() }, ref);
640
650
  continue;
641
651
  }
642
652
  if (!('a' in A)) continue;
@@ -650,7 +660,7 @@ function drawDims(c: C2D, T: Xform, sk: Sketch): void {
650
660
  c.save(); if (ref) c.setLineDash([3, 3]); c.strokeStyle = bad ? TH.red : ref ? TH.chipline : TH.blue; c.lineWidth = 1.2;
651
661
  c.beginPath(); c.moveTo(ax, ay); c.lineTo(bx, by); c.stroke(); c.restore();
652
662
  }
653
- chip(c, wrap(fmtLen(val)), mx, my, { bg, fg, border, kind: 'dim', id, mark });
663
+ chip(c, wrap(fmtLen(val)), mx, my, { bg, fg, border, kind: 'dim', id, mark, ...dimChip() });
654
664
  }
655
665
  }
656
666
 
@@ -664,14 +674,15 @@ function drawDims(c: C2D, T: Xform, sk: Sketch): void {
664
674
  */
665
675
  function drawOpeningDims(c: C2D, T: Xform, sk: Sketch): void {
666
676
  if (!settings.layers.dims || ed.mode !== 'draft') return;
667
- const ROW = 26 / T.s;
677
+ // (the rows, and how far apart along them, grow with the measurement tiles: editor/dimSize.ts)
678
+ const k = settings.chipPct / 100, ROW = (26 * k) / T.s;
668
679
  for (const id in sk.openings) {
669
680
  const o = sk.openings[id], g = openingFrame(sk, id);
670
681
  if (!g) continue;
671
682
  const l = sk.lines[o.line], L = hyp(sk.points[l.b].x - g.A.x, sk.points[l.b].y - g.A.y);
672
683
  const side = outsideSide(sk, o.line);
673
684
  // automatic rows start 34 px past the wall face (clear of the H/V badges); a dragged dimension keeps its own offset
674
- const k0 = g.t / 2 + 34 / T.s;
685
+ const k0 = g.t / 2 + (34 + 11 * (k - 1)) / T.s;
675
686
  const kRow = (row: number) => side * (k0 + row * ROW);
676
687
  /** screen point at `s` along the wall, `k` (signed, world) off its centerline */
677
688
  const P = (s: number, k: number) => S(T, g.A.x + g.ux * s + g.nx * k, g.A.y + g.uy * s + g.ny * k);
@@ -699,14 +710,14 @@ function drawOpeningDims(c: C2D, T: Xform, sk: Sketch): void {
699
710
  };
700
711
  const label = (s0: number, s1: number, part: string, text: string, k: number, bg: string = TH.blue, fg = '#fff', border: [string, number] | null = null) => {
701
712
  const [mx, my] = P((s0 + s1) / 2, k);
702
- chip(c, text, mx, my, { bg, fg, border: odimSel(part) ? [TH.yellow, 2] : border, kind: 'odim', id });
713
+ chip(c, text, mx, my, { bg, fg, border: odimSel(part) ? [TH.yellow, 2] : border, kind: 'odim', id, ...dimChip() });
703
714
  if (hits && hits.length) hits[hits.length - 1].part = part;
704
715
  };
705
716
  // automatic placement packs dimensions into rows so they don't overlap each other or the width chip;
706
717
  // spans that only touch end to end share a row (a chained string)
707
718
  const rows: [number, number][][] = [o.wOff == null ? [[g.s0, g.s1]] : []];
708
719
  const place = (a: number, b: number) => {
709
- const lo = Math.min(a, b), hi = Math.max(a, b), gap = 64 / T.s;
720
+ const lo = Math.min(a, b), hi = Math.max(a, b), gap = (64 * k) / T.s;
710
721
  const clash = ([x, y]: [number, number]) => (lo < y - 1e-6 && hi > x + 1e-6) || Math.abs((lo + hi) / 2 - (x + y) / 2) < gap;
711
722
  for (let r = 0; ; r++) {
712
723
  rows[r] = rows[r] || [];
@@ -59,46 +59,57 @@ export interface UiView extends NavView { seq: number }
59
59
  /** What the drawing on screen was drawn at: its view, the latest of the fingers' views it includes, the screen size. */
60
60
  export interface DrawnAt { view: NavView; seq: number; W: number; H: number }
61
61
 
62
- /** The fingers on the screen and what they're doing: navigating or not, where they were last, whether a pen has been seen. */
63
- export interface NavState { fingers: Record<number, { x: number; y: number }>; navigating: boolean; last: Pinch | null; pen: boolean }
62
+ /**
63
+ * The pointers on the screen and what they're doing: the fingers that count (a finger a Pencil landed on doesn't — see
64
+ * navTouch), navigating or not, where they were last, and any Pencil down.
65
+ */
66
+ export interface NavState { fingers: Record<number, { x: number; y: number }>; navigating: boolean; last: Pinch | null; pens: number[] }
64
67
 
65
68
  export const NAV_DOWN = 0, NAV_MOVE = 1, NAV_UP = 2, NAV_CANCEL = 3;
66
69
 
67
70
  /**
68
71
  * One touch event, as the UI thread sees it: the fingers after it, and the view (moved if `moved`). Fingers navigate —
69
- * pan, and zoom about their middle — once two are down, or any one once a pen has been seen or when the drawing is
70
- * view only (editor/gestures.ts decides the same on its side), until the last one lifts. A pen or a mouse never
71
- * navigates. `type` is NAV_DOWN… ; `allIds`, every pointer still down (on a down event: a finger that went without
72
- * a word, a lost touch-up, is dropped so it can't hold a gesture open).
72
+ * pan, and zoom about their middle — once two are down, or a finger lands while a Pencil is down or when the drawing is
73
+ * view only (editor/gestures.ts decides the same on its side), until the last one lifts. A finger already down and not
74
+ * navigating when a Pencil lands is set aside until it lifts. A pen or a mouse never navigates. `type` is NAV_DOWN… ;
75
+ * `kind` is the event's pointer, for a touch that doesn't say what it is itself; `allIds`, every pointer still down (on
76
+ * a down event: a finger that went without a word, a lost touch-up, is dropped so it can't hold a gesture open).
73
77
  */
74
78
  export function navTouch(
75
- st: NavState, type: number, kind: string, changed: { id: number; x: number; y: number }[], allIds: number[] | null,
79
+ st: NavState, type: number, kind: string, changed: { id: number; x: number; y: number; kind?: string }[], allIds: number[] | null,
76
80
  readOnly: boolean, view: UiView, drawn: DrawnAt | null,
77
81
  ): { st: NavState; view: UiView; moved: boolean } {
78
82
  'worklet';
79
- const pen = st.pen || kind === 'pen';
80
- if (kind !== 'touch') return { st: { ...st, pen }, view, moved: false };
83
+ const lost = (id: number) => type === NAV_DOWN && !!allIds && !allIds.includes(id);
81
84
  const fingers: Record<number, { x: number; y: number }> = {};
82
- for (const k in st.fingers) {
83
- const id = Number(k);
84
- if (type !== NAV_DOWN || !allIds || allIds.includes(id)) fingers[id] = st.fingers[id];
85
- }
85
+ for (const k in st.fingers) if (!lost(Number(k))) fingers[Number(k)] = st.fingers[Number(k)];
86
+ let pens = st.pens.filter(id => !lost(id)), landed = false, moved = false;
86
87
  for (const t of changed) {
87
- if (type === NAV_UP || type === NAV_CANCEL) delete fingers[t.id];
88
- else fingers[t.id] = { x: t.x, y: t.y };
88
+ const k = t.kind ?? kind;
89
+ if (type === NAV_UP || type === NAV_CANCEL) {
90
+ delete fingers[t.id];
91
+ pens = pens.filter(id => id !== t.id);
92
+ } else if (type === NAV_DOWN && k === 'pen') {
93
+ if (!pens.includes(t.id)) pens = [...pens, t.id];
94
+ // (fingers down and not navigating are let go of: their moves and lifts are no finger's from here)
95
+ if (!st.navigating) for (const f in fingers) delete fingers[f];
96
+ } else if (type === NAV_DOWN ? k === 'touch' : t.id in fingers) {
97
+ fingers[t.id] = { x: t.x, y: t.y };
98
+ if (type === NAV_DOWN) landed = true; else moved = true;
99
+ }
89
100
  }
90
101
  const pts = Object.keys(fingers).map(k => fingers[Number(k)]);
91
- if (!pts.length) return { st: { fingers, navigating: false, last: null, pen }, view, moved: false };
102
+ if (!pts.length) return { st: { fingers, navigating: false, last: null, pens }, view, moved: false };
92
103
  let navigating = st.navigating, v = view;
93
104
  if (!navigating) {
94
- if (pts.length < 2 && !(type === NAV_DOWN && (pen || readOnly))) return { st: { fingers, navigating: false, last: null, pen }, view, moved: false };
105
+ if (pts.length < 2 && !(landed && (pens.length > 0 || readOnly))) return { st: { fingers, navigating: false, last: null, pens }, view, moved: false };
95
106
  navigating = true;
96
107
  // start from the view the drawing on screen was drawn at, unless the fingers' own is newer
97
108
  if (drawn && v.seq <= drawn.seq) v = { s: drawn.view.s, cx: drawn.view.cx, cy: drawn.view.cy, seq: drawn.seq };
98
109
  }
99
- const now = pinchOf(pts), was = st.last, next = { fingers, navigating, last: now, pen };
100
- // a finger came or went: carry on from here, no jump
101
- if (type !== NAV_MOVE || !now || !was || was.n !== now.n || !drawn) return { st: next, view: v, moved: false };
110
+ const now = pinchOf(pts), was = st.last, next = { fingers, navigating, last: now, pens };
111
+ // a finger came or went: carry on from here, no jump (and a Pencil's move alone moves no view)
112
+ if (type !== NAV_MOVE || !moved || !now || !was || was.n !== now.n || !drawn) return { st: next, view: v, moved: false };
102
113
  const to = navStep(v, was, now, drawn.W, drawn.H);
103
114
  return { st: next, view: { s: to.s, cx: to.cx, cy: to.cy, seq: v.seq + 1 }, moved: true };
104
115
  }
package/src/tools/dim.ts CHANGED
@@ -23,6 +23,7 @@ import { fmtAng, fmtLen } from '../units';
23
23
  import { addOpeningDim } from './openings';
24
24
  import type { PEv } from './registry';
25
25
  import { keypadFresh, keypadOpen, keypadSetReading } from '../editor/keypad';
26
+ import { advanceAfterReading } from '../editor/advance';
26
27
  import { cornerLines } from '../editor/selection';
27
28
  import { closeKeypad, kpTyped, openKeypad, showConflict, toast, updateCtx, updateHint, type KeypadSpec } from '../editor/ports';
28
29
  import { noScaleYet, pendingLabel, startingValue } from '../editor/scaleGate';
@@ -177,7 +178,11 @@ export function dimLine(lid: string, side?: 1 | -1): void {
177
178
  onPress: () => { face = ch.face!; spec.sub = lengthSub(sk, lid, face); show(); if (keypadFresh() && !noScaleYet()) keypadSetReading(len()); },
178
179
  })) : undefined,
179
180
  onClose: () => { ed.sel = was; updateCtx(); requestDraw(); },
180
- onDone: (v, capture) => { if (v > 0) addLength(v, capture !== undefined ? { capture } : {}); },
181
+ onDone: (v, capture) => {
182
+ if (!(v > 0)) return;
183
+ // a laser reading's (it comes with its record) moves on to the next line clockwise; a typed one stays
184
+ if (addLength(v, capture !== undefined ? { capture } : {}) && capture !== undefined) advanceAfterReading(lid, side);
185
+ },
181
186
  // (or a length of the job's, from its list: the line takes it, linked to it)
182
187
  measurements: offersJobMeasurements() ? {
183
188
  kind: 'length', pick: t => {
@@ -188,21 +193,32 @@ export function dimLine(lid: string, side?: 1 | -1): void {
188
193
  },
189
194
  } : undefined,
190
195
  };
191
- /** The line's length dimension, driving, measured where the keypad says (`extra`: what it's from). */
192
- function addLength(v: number, extra: Partial<Constraint>): void {
196
+ /** The line's length dimension, driving, measured where the keypad says (`extra`: what it's from). Whether it took. */
197
+ function addLength(v: number, extra: Partial<Constraint>): boolean {
193
198
  pushHistory();
194
199
  const r = tryAddConstraint({ type: 'dist', a: l.a, b: l.b, kind: 'al', value: v, line: lid, driving: true, off: face ? (face * 30) / V().s : 0, ...(face ? { face } : {}), ...extra });
195
200
  // a measurement of the job drives a line, or nothing: not one whose length the drawing already sets
201
+ let took = r.ok;
196
202
  if (r.ok && r.ref && extra.measurementRef) {
197
203
  delete SK().cons[r.id!];
198
204
  dropHistory();
199
205
  toast(LENGTH_SET_ALREADY, { warn: true });
206
+ took = false;
200
207
  } else if (!r.ok) dropHistory();
201
208
  afterEdit();
209
+ return took;
202
210
  }
203
211
  openKeypad(spec);
204
212
  }
205
213
 
214
+ /**
215
+ * Whether giving dimension `c` the value `v` replaces a value measured there: a driving one, entered (not a default
216
+ * nobody measured), changed by it. Not one linked to a measurement of the job: that's asked about anyway
217
+ * (askLinkedChange). What a host warns about before a reading overwrites (laserReadingOverwrites).
218
+ */
219
+ export const replacesMeasured = (c: Constraint | undefined, v: number): boolean =>
220
+ !!c && c.driving !== false && !c.auto && !c.measurementRef && changesValue(c, v);
221
+
206
222
  /** Why a measurement of the job can't drive a line the drawing already sets the length of. */
207
223
  export const LENGTH_SET_ALREADY = 'The drawing already sets that line’s length. Free it first (make one of its dimensions a reference), then pick the measurement.';
208
224
  /** Why a measurement of the job can't drive an angle the drawing already sets. */
@@ -587,7 +603,15 @@ export function editDim(id: string, done?: () => void, modeless = false, pick?:
587
603
  label: ch.label!, on: onChoice(withFace(c, face), ch),
588
604
  onPress: () => { face = onChoice(c, ch) ? null : { ...ch, l2: face?.l2 ?? ch.l2 }; if (keypadFresh()) keypadSetReading(now()); },
589
605
  })),
590
- onDone: (v, capture) => { setDimValue(id, v * k, face ?? undefined, { capture }); done?.(); },
606
+ onDone: (v, capture) => {
607
+ const took = setDimValue(id, v * k, face ?? undefined, { capture });
608
+ done?.();
609
+ // a laser reading's (it comes with its record) on a line's length or a curve's diameter moves on to the next
610
+ // line clockwise; a typed one, or one the drawing refused or asked about, stays
611
+ const of = c.type === 'dist' ? c.line : c.type === 'diam' ? c.c : undefined;
612
+ if (took && capture !== undefined && of) advanceAfterReading(of, pick?.face || undefined);
613
+ },
614
+ overwrites: v => replacesMeasured(SK().cons[id], v * k),
591
615
  // a line's length or an angle: or one of the job's, from its list — the dimension takes it, linked to it (a
592
616
  // dimension already linked to another, re-linked)
593
617
  measurements: ((c.type === 'dist' && c.line) || isAng) && offersJobMeasurements() ? {
@@ -67,6 +67,8 @@ export const domPorts: EditorPorts = {
67
67
  toggleImageRotation: anchor => toggleRotation(anchor as HTMLElement | undefined),
68
68
  // (this interface has no link tiles: no host links)
69
69
  toggleTileSize: () => {},
70
+ // (the harness's page has no slider for it: its hosts' editor does)
71
+ toggleDimSize: () => {},
70
72
  editRoomName,
71
73
  setCursor: cursor => { cvs.cv.style.cursor = cursor; },
72
74
  // (this page's chrome stays put while a gesture moves)