@reekon-tools/boldr-cad 1.4.0 → 1.5.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.
- package/README.md +13 -3
- package/dist/app/edit.d.ts +3 -3
- package/dist/app/edit.js +26 -1
- package/dist/app/edit.js.map +1 -1
- package/dist/app/state.d.ts +3 -5
- package/dist/app/state.js +0 -1
- package/dist/app/state.js.map +1 -1
- package/dist/data/sync.d.ts +1 -1
- package/dist/data/sync.js +7 -2
- package/dist/data/sync.js.map +1 -1
- package/dist/editor/actions.d.ts +8 -0
- package/dist/editor/actions.js +149 -169
- package/dist/editor/actions.js.map +1 -1
- package/dist/editor/elevation.d.ts +4 -1
- package/dist/editor/elevation.js +16 -17
- package/dist/editor/elevation.js.map +1 -1
- package/dist/editor/gestures.d.ts +21 -0
- package/dist/editor/gestures.js +92 -14
- package/dist/editor/gestures.js.map +1 -1
- package/dist/editor/keys.js +2 -2
- package/dist/editor/keys.js.map +1 -1
- package/dist/editor/linkActions.d.ts +22 -3
- package/dist/editor/linkActions.js +95 -13
- package/dist/editor/linkActions.js.map +1 -1
- package/dist/editor/mode.js +4 -3
- package/dist/editor/mode.js.map +1 -1
- package/dist/editor/project.js +1 -2
- package/dist/editor/project.js.map +1 -1
- package/dist/editor/scaleGate.d.ts +13 -0
- package/dist/editor/scaleGate.js +27 -0
- package/dist/editor/scaleGate.js.map +1 -0
- package/dist/editor/selection.js +13 -9
- package/dist/editor/selection.js.map +1 -1
- package/dist/editor/tooltips.js +9 -0
- package/dist/editor/tooltips.js.map +1 -1
- package/dist/editor/view3d.d.ts +4 -1
- package/dist/editor/view3d.js +6 -2
- package/dist/editor/view3d.js.map +1 -1
- package/dist/export/pdf.d.ts +5 -2
- package/dist/export/pdf.js +12 -8
- package/dist/export/pdf.js.map +1 -1
- package/dist/export/plan.d.ts +1 -1
- package/dist/export/plan.js +14 -9
- package/dist/export/plan.js.map +1 -1
- package/dist/model/demo.d.ts +1 -0
- package/dist/model/demo.js +2 -1
- package/dist/model/demo.js.map +1 -1
- package/dist/model/elevation.d.ts +1 -1
- package/dist/model/elevation.js +3 -3
- package/dist/model/elevation.js.map +1 -1
- package/dist/model/history.js +4 -1
- package/dist/model/history.js.map +1 -1
- package/dist/model/links.d.ts +4 -1
- package/dist/model/links.js +9 -2
- package/dist/model/links.js.map +1 -1
- package/dist/model/measurement.d.ts +2 -2
- package/dist/model/measurement.js +3 -3
- package/dist/model/measurement.js.map +1 -1
- package/dist/model/openings.d.ts +10 -0
- package/dist/model/openings.js +18 -0
- package/dist/model/openings.js.map +1 -1
- package/dist/model/planGeom.d.ts +37 -2
- package/dist/model/planGeom.js +72 -9
- package/dist/model/planGeom.js.map +1 -1
- package/dist/model/project.js +5 -2
- package/dist/model/project.js.map +1 -1
- package/dist/model/rooms.d.ts +18 -5
- package/dist/model/rooms.js +178 -73
- package/dist/model/rooms.js.map +1 -1
- package/dist/model/scale.d.ts +30 -0
- package/dist/model/scale.js +120 -0
- package/dist/model/scale.js.map +1 -0
- package/dist/model/settings.d.ts +0 -1
- package/dist/model/settings.js +1 -1
- package/dist/model/settings.js.map +1 -1
- package/dist/model/sketch.d.ts +21 -1
- package/dist/model/sketch.js +58 -4
- package/dist/model/sketch.js.map +1 -1
- package/dist/model/stroke.js +2 -2
- package/dist/model/types.d.ts +38 -7
- package/dist/react/BottomBar.d.ts +2 -1
- package/dist/react/BottomBar.js +94 -25
- package/dist/react/BottomBar.js.map +1 -1
- package/dist/react/Drawer.js +3 -2
- package/dist/react/Drawer.js.map +1 -1
- package/dist/react/Duck.js.map +1 -1
- package/dist/react/KeypadSheet.js.map +1 -1
- package/dist/react/MarkupPanel.js +1 -1
- package/dist/react/PlanCanvas.native.js +54 -7
- package/dist/react/PlanCanvas.native.js.map +1 -1
- package/dist/react/Popovers.js +14 -3
- package/dist/react/Popovers.js.map +1 -1
- package/dist/react/Rail.js +1 -1
- package/dist/react/Sheet.d.ts +2 -2
- package/dist/react/Sheet.js.map +1 -1
- package/dist/react/barScroll.d.ts +23 -0
- package/dist/react/barScroll.js +47 -0
- package/dist/react/barScroll.js.map +1 -0
- package/dist/react/corners.d.ts +6 -2
- package/dist/react/corners.js.map +1 -1
- package/dist/react/motion.d.ts +1 -1
- package/dist/react/motion.js +1 -1
- package/dist/react/motion.js.map +1 -1
- package/dist/render/draw.d.ts +21 -4
- package/dist/render/draw.js +184 -149
- package/dist/render/draw.js.map +1 -1
- package/dist/render/elevation.js +3 -3
- package/dist/render/elevation.js.map +1 -1
- package/dist/render/figmaIcons.d.ts +6 -0
- package/dist/render/figmaIcons.js +16 -1
- package/dist/render/figmaIcons.js.map +1 -1
- package/dist/render/hit.d.ts +8 -1
- package/dist/render/hit.js +23 -4
- package/dist/render/hit.js.map +1 -1
- package/dist/render/icons.js +27 -20
- package/dist/render/icons.js.map +1 -1
- package/dist/render/lens.d.ts +27 -0
- package/dist/render/lens.js +41 -0
- package/dist/render/lens.js.map +1 -0
- package/dist/render/linkTile.d.ts +20 -2
- package/dist/render/linkTile.js +476 -246
- package/dist/render/linkTile.js.map +1 -1
- package/dist/render/snap.d.ts +5 -1
- package/dist/render/snap.js +3 -1
- package/dist/render/snap.js.map +1 -1
- package/dist/render/svgPath.d.ts +29 -0
- package/dist/render/svgPath.js +114 -0
- package/dist/render/svgPath.js.map +1 -0
- package/dist/render/tileIcons.d.ts +18 -0
- package/dist/render/tileIcons.js +67 -0
- package/dist/render/tileIcons.js.map +1 -0
- package/dist/render/view.d.ts +18 -1
- package/dist/render/view.js +31 -4
- package/dist/render/view.js.map +1 -1
- package/dist/skia/SkiaDrawCtx.d.ts +8 -0
- package/dist/skia/SkiaDrawCtx.js +27 -5
- package/dist/skia/SkiaDrawCtx.js.map +1 -1
- package/dist/solver/equations.d.ts +6 -0
- package/dist/solver/equations.js +23 -6
- package/dist/solver/equations.js.map +1 -1
- package/dist/solver/gauss-newton.js +6 -1
- package/dist/solver/gauss-newton.js.map +1 -1
- package/dist/tools/annotate.d.ts +0 -2
- package/dist/tools/annotate.js +1 -22
- package/dist/tools/annotate.js.map +1 -1
- package/dist/tools/chamfer.js +4 -0
- package/dist/tools/chamfer.js.map +1 -1
- package/dist/tools/dim.d.ts +3 -3
- package/dist/tools/dim.js +26 -65
- package/dist/tools/dim.js.map +1 -1
- package/dist/tools/fillet.js +4 -0
- package/dist/tools/fillet.js.map +1 -1
- package/dist/tools/freehand.js +1 -4
- package/dist/tools/freehand.js.map +1 -1
- package/dist/tools/line.js +15 -11
- package/dist/tools/line.js.map +1 -1
- package/dist/tools/modify.js +6 -1
- package/dist/tools/modify.js.map +1 -1
- package/dist/tools/registry.d.ts +5 -0
- package/dist/tools/registry.js +6 -1
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/select.d.ts +19 -0
- package/dist/tools/select.js +116 -17
- package/dist/tools/select.js.map +1 -1
- package/dist/tools/shapes.d.ts +1 -1
- package/dist/tools/shapes.js +12 -5
- package/dist/tools/shapes.js.map +1 -1
- package/dist/view3d/build.js +9 -8
- package/dist/view3d/build.js.map +1 -1
- package/package.json +9 -9
- package/src/app/edit.ts +25 -1
- package/src/app/state.ts +3 -5
- package/src/data/sync.ts +7 -2
- package/src/editor/actions.ts +126 -112
- package/src/editor/elevation.ts +13 -16
- package/src/editor/gestures.ts +83 -13
- package/src/editor/keys.ts +2 -2
- package/src/editor/linkActions.ts +90 -13
- package/src/editor/mode.ts +4 -3
- package/src/editor/project.ts +1 -2
- package/src/editor/scaleGate.ts +32 -0
- package/src/editor/selection.ts +13 -7
- package/src/editor/tooltips.ts +8 -0
- package/src/editor/view3d.ts +6 -2
- package/src/export/pdf.ts +7 -4
- package/src/export/plan.ts +14 -10
- package/src/model/demo.ts +2 -1
- package/src/model/elevation.ts +4 -4
- package/src/model/history.ts +4 -1
- package/src/model/links.ts +8 -2
- package/src/model/measurement.ts +3 -3
- package/src/model/openings.ts +18 -0
- package/src/model/planGeom.ts +70 -10
- package/src/model/project.ts +6 -2
- package/src/model/rooms.ts +151 -63
- package/src/model/scale.ts +105 -0
- package/src/model/settings.ts +1 -2
- package/src/model/sketch.ts +48 -4
- package/src/model/stroke.ts +2 -2
- package/src/model/types.ts +38 -7
- package/src/react/BottomBar.tsx +114 -29
- package/src/react/Drawer.tsx +14 -10
- package/src/react/Duck.tsx +3 -3
- package/src/react/KeypadSheet.tsx +2 -2
- package/src/react/MarkupPanel.tsx +1 -1
- package/src/react/PlanCanvas.native.tsx +60 -6
- package/src/react/Popovers.tsx +24 -9
- package/src/react/Rail.tsx +1 -1
- package/src/react/Sheet.tsx +3 -3
- package/src/react/barScroll.ts +57 -0
- package/src/react/corners.ts +6 -3
- package/src/react/motion.tsx +6 -6
- package/src/render/draw.ts +156 -116
- package/src/render/elevation.ts +5 -4
- package/src/render/figmaIcons.ts +17 -1
- package/src/render/hit.ts +21 -4
- package/src/render/icons.ts +27 -20
- package/src/render/lens.ts +52 -0
- package/src/render/linkTile.ts +436 -190
- package/src/render/snap.ts +7 -2
- package/src/render/svgPath.ts +95 -0
- package/src/render/tileIcons.ts +75 -0
- package/src/render/view.ts +37 -5
- package/src/skia/SkiaDrawCtx.ts +26 -4
- package/src/solver/equations.ts +22 -6
- package/src/solver/gauss-newton.ts +4 -1
- package/src/styles.css +3 -2
- package/src/tools/annotate.ts +1 -18
- package/src/tools/chamfer.ts +3 -0
- package/src/tools/dim.ts +26 -52
- package/src/tools/fillet.ts +3 -0
- package/src/tools/freehand.ts +1 -3
- package/src/tools/line.ts +12 -11
- package/src/tools/modify.ts +5 -1
- package/src/tools/registry.ts +10 -1
- package/src/tools/select.ts +99 -18
- package/src/tools/shapes.ts +9 -4
- package/src/ui/ctx.ts +4 -3
- package/src/ui/drawer.ts +8 -2
- package/src/view3d/build.ts +11 -10
package/dist/editor/actions.js
CHANGED
|
@@ -2,13 +2,13 @@ import { afterEdit, holdFaces, solveSketch, tryAddConstraint, undo } from '../ap
|
|
|
2
2
|
import { ed } from '../app/state';
|
|
3
3
|
import { CONS_LABEL, measure } from '../model/constraints';
|
|
4
4
|
import { dropHistory, pushHistory } from '../model/history';
|
|
5
|
-
import { createsCycle, edgePoint, facingEdges, nearerEdge } from '../model/openings';
|
|
5
|
+
import { createsCycle, edgePoint, facingEdges, flipDoor, nearerEdge } from '../model/openings';
|
|
6
|
+
import { roomSideOf } from '../model/elevation';
|
|
6
7
|
import { cabOfPoint } from '../model/faces';
|
|
7
8
|
import { settings } from '../model/settings';
|
|
8
9
|
import { connectedGeometry, curveInfo, deleteSel, joinPoints } from '../model/sketch';
|
|
9
10
|
import { page, planSK, replaceSK, saveSoon, SK } from '../model/store';
|
|
10
11
|
import { fitView, requestDraw } from '../render/view';
|
|
11
|
-
import { convertNote } from '../tools/annotate';
|
|
12
12
|
import { cornerAngle, dimCurve, dimEdges, dimLine, dimPointLine, dimPoints, dimTwoLines, editDim, faceChoices, onChoice, setDimFace, toggleRef } from '../tools/dim';
|
|
13
13
|
import { edgeLabel, edgeOf } from '../model/planGeom';
|
|
14
14
|
import { offsetPrompt, splitLine } from '../tools/modify';
|
|
@@ -17,13 +17,15 @@ import { chamferPrompt } from '../tools/chamfer';
|
|
|
17
17
|
import { addOpeningDim, editOpeningDim, toggleOpeningDim } from '../tools/openings';
|
|
18
18
|
import { setTool, tools } from '../tools/registry';
|
|
19
19
|
import { fmtLen } from '../units';
|
|
20
|
-
import { elevOpen, fitState, OPENING_SIZES, openElevation,
|
|
20
|
+
import { elevOpen, fitState, OPENING_SIZES, openElevation, overflow, restoreFit, TOO_SHORT, wallAction, wallBar, wallDelete, wallPlus } from './elevation';
|
|
21
21
|
import { pushedOut } from '../model/facesolve';
|
|
22
|
-
import { editRoomName, openKeypad, openPalette, toast, toggleImageOpacity, toggleImageRotation, toggleTileSize, updateCtx, updateTopBar } from './ports';
|
|
22
|
+
import { dismissPopovers, editRoomName, openKeypad, openPalette, toast, toggleImageOpacity, toggleImageRotation, toggleTileSize, updateCtx, updateTopBar } from './ports';
|
|
23
23
|
import { cornerLines, edgePair, selInfo } from './selection';
|
|
24
24
|
import { restackImage } from './project';
|
|
25
25
|
import { imageById } from '../model/images';
|
|
26
|
-
import { canOpenLink, canSaveDim, linkSizeVisible, openDimLink, openSelectedLink, saveDimToGroup, selectedLink, toggleLeader, unlinkDim } from './linkActions';
|
|
26
|
+
import { canExpand, canOpenLink, canSaveDim, linkSizeVisible, openDimLink, openSelectedLink, saveDimToGroup, selectedLink, toggleExpand, toggleLeader, unlinkDim } from './linkActions';
|
|
27
|
+
/** A bar button shown as its icon alone, no name under it (the lead, and Lock). */
|
|
28
|
+
export const iconAlone = (it) => !!(it.lead || it.bare);
|
|
27
29
|
/** Points a Lock on the current selection would pin (the origin is always fixed). */
|
|
28
30
|
function lockTargets() {
|
|
29
31
|
const sk = SK(), s = selInfo(), ids = new Set(s.points);
|
|
@@ -33,24 +35,30 @@ function lockTargets() {
|
|
|
33
35
|
return [...ids].filter(id => sk.points[id] && !sk.points[id].origin && !sk.points[id].anchor);
|
|
34
36
|
}
|
|
35
37
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
+
* The bar leads with one button, its icon alone, set off by a divider (Figma's bars, 2528:101128: the user's call,
|
|
39
|
+
* 2026-10-27): Open, when what's selected is a link to something of the job's; otherwise Lock — an open padlock (white)
|
|
40
|
+
* when free, a closed one (red, "Locked") when locked. A link that can be locked too (an expanded photo) has Lock next,
|
|
41
|
+
* with a divider of its own. Lock is its icon alone wherever it is, locked or not (the user's call, 2026-10-30: "only
|
|
42
|
+
* display the icon - no text needed below it").
|
|
38
43
|
*/
|
|
39
|
-
function
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
out.push('sep');
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
44
|
+
function arrangeLead(items) {
|
|
45
|
+
const find = (acts) => items.find((it) => typeof it === 'object' && 'act' in it && acts.includes(it.act));
|
|
46
|
+
const open = find(['linkOpen', 'dimLinkOpen']), lock = find(['lock', 'imgLock']);
|
|
47
|
+
if (lock) {
|
|
48
|
+
const sk = SK();
|
|
49
|
+
const locked = lock.act === 'imgLock' ? selImages().length > 0 && selImages().every(u => u.locked) : (() => { const l = lockTargets(); return l.length > 0 && l.every(id => sk.points[id].fixed); })();
|
|
50
|
+
Object.assign(lock, { ic: locked ? 'lock' : 'unlock', label: locked ? 'Locked' : 'Lock', on: undefined, locked, bare: true });
|
|
51
|
+
}
|
|
52
|
+
const lead = open ?? lock, out = [];
|
|
53
|
+
if (lead) {
|
|
54
|
+
lead.lead = true;
|
|
55
|
+
out.push(lead, 'sep');
|
|
56
|
+
if (open && lock)
|
|
57
|
+
out.push(lock, 'sep');
|
|
58
|
+
}
|
|
59
|
+
// the rest, with no divider doubled, first or last
|
|
60
|
+
for (const e of items)
|
|
61
|
+
if (e !== lead && !(open && e === lock) && !(e === 'sep' && (!out.length || out[out.length - 1] === 'sep')))
|
|
54
62
|
out.push(e);
|
|
55
63
|
while (out[out.length - 1] === 'sep')
|
|
56
64
|
out.pop();
|
|
@@ -67,32 +75,27 @@ const PLACING = new Set(['door', 'window', 'wdoor', 'wwindow', 'cabinet']);
|
|
|
67
75
|
function ctxItems() {
|
|
68
76
|
const items = [], sk = SK(), s = selInfo();
|
|
69
77
|
const add = (ic, label, act, o = {}) => items.push(Object.assign({ ic, label, act }, o));
|
|
70
|
-
/** To wall (all the selected lines and curves) — or To line + Thickness when they're all walls already. */
|
|
71
|
-
const wallItems = () => {
|
|
72
|
-
if (elevOpen())
|
|
73
|
-
return;
|
|
74
|
-
const kinds = [...s.lines.map(id => sk.lines[id].kind), ...s.curves.map(id => curveInfo(sk, id)?.o.kind)];
|
|
75
|
-
if (!kinds.length)
|
|
76
|
-
return;
|
|
77
|
-
if (kinds.every(k => k === 'wall')) {
|
|
78
|
-
add('thick', 'Thickness', 'thick');
|
|
79
|
-
add('line', 'To line', 'kind');
|
|
80
|
-
}
|
|
81
|
-
else
|
|
82
|
-
add('wall', 'To wall', 'kind');
|
|
83
|
-
};
|
|
84
78
|
if (ed.mode === 'annotate') {
|
|
85
79
|
const ims = selImages();
|
|
86
80
|
if (ims.length && ims.length === s.n) {
|
|
87
81
|
// an image (or several): lock, its own scale, and which goes in front where they overlap
|
|
88
82
|
// (showing and hiding them all is the Layers menu's)
|
|
89
83
|
add('lock', 'Lock', 'imgLock');
|
|
90
|
-
|
|
84
|
+
// a photo of the job's, expanded from its tile: opening it, and Expand (on) to fold it back into its tile
|
|
85
|
+
const lk = ims.length === 1 ? ims[0].link : undefined;
|
|
86
|
+
if (lk && canOpenLink(lk))
|
|
87
|
+
add('open', 'Open', 'linkOpen');
|
|
88
|
+
// locked: unlocking it, and opening what it's a photo of — nothing that changes it (the user's call,
|
|
89
|
+
// 2026-10-30: "images in locked state: shouldn't have these options - only lock and link should be available")
|
|
90
|
+
if (ims.every(u => u.locked))
|
|
91
|
+
return items;
|
|
92
|
+
if (lk)
|
|
93
|
+
add('expand', 'Expand', 'linkExpand', { on: true });
|
|
94
|
+
if (ims.length === 1)
|
|
91
95
|
add('calib', 'Set scale', 'calibtool');
|
|
92
96
|
if (ims.length === 1)
|
|
93
97
|
add('opacity', 'Opacity', 'imgOpacity');
|
|
94
|
-
|
|
95
|
-
if (ims.length === 1 && !ims[0].locked)
|
|
98
|
+
if (ims.length === 1)
|
|
96
99
|
add('rotate', 'Rotate', 'imgRotate');
|
|
97
100
|
if (ims.length === 1 && page().underlays.length > 1) {
|
|
98
101
|
const at = page().underlays.indexOf(ims[0]), last = page().underlays.length - 1;
|
|
@@ -108,20 +111,19 @@ function ctxItems() {
|
|
|
108
111
|
// a link's tile: opening it, its leader, its size (its name is on the tile, not here: the user's call, 2026-10-21)
|
|
109
112
|
if (canOpenLink(lk.link))
|
|
110
113
|
add('open', 'Open', 'linkOpen');
|
|
114
|
+
// (a photo's: the photo whole, as one of the page's images)
|
|
115
|
+
if (canExpand(lk))
|
|
116
|
+
add('expand', 'Expand', 'linkExpand', { on: false });
|
|
111
117
|
add('leader', 'Leader', 'linkLeader', { on: !!lk.leader });
|
|
112
118
|
// (its leader's colour, as all markup's, is the rail's colour dot's)
|
|
113
119
|
// (every tile on the page: a slider above the bar)
|
|
114
120
|
add('tilesize', 'Size', 'linkSize');
|
|
115
121
|
return items;
|
|
116
122
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
add('convert', 'To lines', 'convert');
|
|
122
|
-
if (linkSizeVisible())
|
|
123
|
-
add('tilesize', 'Size', 'linkSize');
|
|
124
|
-
}
|
|
123
|
+
// markup: nothing of its own — a pen stroke as any other (no To lines: the user's call, 2026-10-27), and no Color
|
|
124
|
+
// (the rail's colour dot styles what's selected); with tiles among it, their size
|
|
125
|
+
if (s.notes.length && linkSizeVisible())
|
|
126
|
+
add('tilesize', 'Size', 'linkSize');
|
|
125
127
|
// nothing selected: no bar (the markup colour is the rail's colour dot, for what's selected too; the image's own
|
|
126
128
|
// options come with selecting it)
|
|
127
129
|
return items;
|
|
@@ -143,11 +145,6 @@ function ctxItems() {
|
|
|
143
145
|
add('finish', 'Finish', 'finish');
|
|
144
146
|
return items;
|
|
145
147
|
}
|
|
146
|
-
if (ed.tool === 'dim') {
|
|
147
|
-
add('dim', 'Driving', 'dimDrive', { on: ed.dimLayer === 'drive' });
|
|
148
|
-
add('ref', 'Print', 'dimPrint', { on: ed.dimLayer === 'print' });
|
|
149
|
-
return items;
|
|
150
|
-
}
|
|
151
148
|
// nothing selected: the bar stays empty (Fit and Icons live in the top bar; grid snap and auto-constrain in Settings).
|
|
152
149
|
// Just placed a door, window or cabinet (its tool still on): its bar, for its sizes
|
|
153
150
|
if (!s.n || (ed.tool !== 'select' && !PLACING.has(ed.tool)))
|
|
@@ -161,15 +158,36 @@ function ctxItems() {
|
|
|
161
158
|
add(it.ic, it.label, it.act);
|
|
162
159
|
return items;
|
|
163
160
|
}
|
|
161
|
+
/*
|
|
162
|
+
Figma's bars (2528:101128, the user's call, 2026-10-27): Lock first, its icon alone (arrangeLead); then what the lines
|
|
163
|
+
and curves are (kind); then what ties them, or edits them. The yellow + measures: no Distance of its own here.
|
|
164
|
+
*/
|
|
164
165
|
// on a wall face, walls don't exist: no To wall / Thickness
|
|
165
166
|
const wall = elevOpen();
|
|
166
167
|
const P = s.points.length, L = s.lines.length, C = s.curves.length;
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
168
|
+
/**
|
|
169
|
+
* What the selected lines and curves are, and its divider: walls — one straight wall's wall view, and their Thickness;
|
|
170
|
+
* otherwise To wall, and Guide (Solid, when they all are guides). A wall is always solid; on a wall face, Guide alone.
|
|
171
|
+
*/
|
|
172
|
+
const kind = () => {
|
|
173
|
+
const objs = [...s.lines.map(id => sk.lines[id]), ...s.curves.map(id => curveInfo(sk, id)?.o)].filter((o) => !!o);
|
|
174
|
+
if (!objs.length)
|
|
175
|
+
return;
|
|
176
|
+
if (objs.every(o => o.kind === 'wall' && !o.constr)) {
|
|
177
|
+
if (wall)
|
|
178
|
+
return;
|
|
179
|
+
if (s.n === 1 && L === 1)
|
|
180
|
+
add('elev', 'Elevation', 'elev');
|
|
181
|
+
add('thick', 'Thickness', 'thick');
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
if (!wall)
|
|
185
|
+
add('wall', 'To wall', 'kind');
|
|
186
|
+
add('constr', objs.every(o => o.constr) ? 'Solid' : 'Guide', 'constr');
|
|
187
|
+
}
|
|
188
|
+
items.push('sep');
|
|
189
|
+
};
|
|
190
|
+
// a wall's edge: named, with its wall view (with another edge, a line or a point, the + is the distance between them)
|
|
173
191
|
if (s.wfaces.length) {
|
|
174
192
|
// (a curved wall has no wall view: just its name)
|
|
175
193
|
if (s.n === 1) {
|
|
@@ -178,16 +196,12 @@ function ctxItems() {
|
|
|
178
196
|
if (sk.lines[id])
|
|
179
197
|
add('elev', 'Elevation', 'elev');
|
|
180
198
|
}
|
|
181
|
-
else if (edgePair(s)) {
|
|
182
|
-
const one = s.wfaces.length === 2 && edgeOf(s.wfaces[0])[0] === edgeOf(s.wfaces[1])[0];
|
|
183
|
-
add(one ? 'thick' : 'dim', one ? 'Thickness' : 'Distance', 'edgeDist');
|
|
184
|
-
}
|
|
185
199
|
return items;
|
|
186
200
|
}
|
|
187
201
|
if (s.n === 1 && s.dims.length) {
|
|
188
202
|
const c = sk.cons[s.dims[0]];
|
|
189
203
|
// linked to a measurement of the job: opening it, letting go of it — no text saying what it's linked to (the user's
|
|
190
|
-
// call, 2026-10-21; its chip is drawn linked)
|
|
204
|
+
// call, 2026-10-21; its chip is drawn linked)
|
|
191
205
|
if (c.measurementRef) {
|
|
192
206
|
const t = c.measurementRef;
|
|
193
207
|
if (canOpenLink(t))
|
|
@@ -196,10 +210,7 @@ function ctxItems() {
|
|
|
196
210
|
}
|
|
197
211
|
else if (canSaveDim(s.dims[0]))
|
|
198
212
|
add('savegroup', 'Save to group', 'dimSave');
|
|
199
|
-
// Reference
|
|
200
|
-
const cabSize = Object.values(sk.cabs ?? {}).some(cb => cb.w === s.dims[0] || cb.h === s.dims[0]);
|
|
201
|
-
if (c.layer !== 'print' && !cabSize && !c.measurementRef)
|
|
202
|
-
add('ref', c.driving === false ? 'Drive' : 'Reference', 'toggleref');
|
|
213
|
+
// (no Reference: Figma's dimension bars have none, 2026-10-27)
|
|
203
214
|
// measured on walls: which of their faces (the drawing stays as it is; the number becomes what it measures there)
|
|
204
215
|
const fc = faceChoices(sk, c);
|
|
205
216
|
if (fc.length && items.length)
|
|
@@ -220,96 +231,71 @@ function ctxItems() {
|
|
|
220
231
|
add('rename', 'Rename', 'renameRoom');
|
|
221
232
|
return items;
|
|
222
233
|
}
|
|
223
|
-
// one end of a door or window: named
|
|
234
|
+
// one end of a door or window: named (with another point, end or door/window, the + is the distance between them)
|
|
224
235
|
if (s.oanchors.length && s.n === 1) {
|
|
225
236
|
items.push({ lbl: anchorLabel(s.oanchors[0]) });
|
|
226
237
|
return items;
|
|
227
238
|
}
|
|
228
|
-
if (s.oanchors.length
|
|
229
|
-
add('dim', 'Distance', 'anchorDist');
|
|
230
|
-
return items;
|
|
231
|
-
}
|
|
232
|
-
// two doors/windows, or one and a point: the distance between them (locks the second one picked)
|
|
233
|
-
if (s.n === 2 && (s.openings.length === 2 || (s.openings.length === 1 && s.points.length === 1))) {
|
|
234
|
-
add('dim', 'Distance', 'opDist');
|
|
239
|
+
if (s.oanchors.length || (s.n === 2 && (s.openings.length === 2 || (s.openings.length === 1 && s.points.length === 1))))
|
|
235
240
|
return items;
|
|
236
|
-
}
|
|
237
241
|
if (s.n === 1 && s.openings.length) {
|
|
238
|
-
// its sizes
|
|
239
|
-
//
|
|
240
|
-
|
|
241
|
-
items.push({ lbl: openingLabel(o) });
|
|
242
|
+
// its sizes (no +: it isn't one dimension), with nothing saying them — the drawing does (the user's call, 2026-10-27);
|
|
243
|
+
// a door's Swing too, round its four ways (it was Flip: the user's call, 2026-10-27, "ok do swing instead"). (The
|
|
244
|
+
// type is fixed once placed: Door and Window are separate tools.)
|
|
242
245
|
for (const it of OPENING_SIZES)
|
|
243
246
|
add(it.ic, it.label, it.act);
|
|
244
|
-
if (
|
|
247
|
+
if (sk.openings[s.openings[0]].type === 'door') {
|
|
245
248
|
items.push('sep');
|
|
246
|
-
add('flip', '
|
|
249
|
+
add('flip', 'Swing', 'opFlip');
|
|
247
250
|
}
|
|
248
251
|
return items;
|
|
249
252
|
}
|
|
253
|
+
// a cabinet's corner: nothing of its own (pick another point or line with it to dimension or constrain)
|
|
254
|
+
if (P === 1 && s.n === 1 && cabOfPoint(sk, s.points[0]))
|
|
255
|
+
return items;
|
|
256
|
+
add('lock', 'Lock', 'lock', { key: '⇧F' });
|
|
250
257
|
if (L === 1 && s.n === 1) {
|
|
251
|
-
|
|
258
|
+
kind();
|
|
259
|
+
// (its length: the yellow +)
|
|
252
260
|
add('h', 'Horizontal', 'h', { key: '⇧H' });
|
|
253
|
-
add('v', 'Vertical', 'v', { key: '⇧V' });
|
|
254
|
-
items.push('sep');
|
|
255
|
-
add('lock', 'Lock', 'lock', { key: '⇧F' });
|
|
261
|
+
add('v', 'Vertical', 'v', { key: '⇧V' });
|
|
256
262
|
add('split', 'Split', 'split');
|
|
257
263
|
add('offset', 'Offset', 'offset');
|
|
258
|
-
if (l.constr)
|
|
259
|
-
add('constr', 'Solid', 'constr');
|
|
260
|
-
else
|
|
261
|
-
guide();
|
|
262
|
-
if (!wall) {
|
|
263
|
-
if (l.kind === 'wall') {
|
|
264
|
-
add('thick', 'Thickness', 'thick');
|
|
265
|
-
add('line', 'To line', 'kind');
|
|
266
|
-
}
|
|
267
|
-
else
|
|
268
|
-
add('wall', 'To wall', 'kind');
|
|
269
|
-
if (l.kind === 'wall' && !l.constr) {
|
|
270
|
-
items.push('sep');
|
|
271
|
-
add('elev', 'Elevation', 'elev');
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
264
|
return items;
|
|
275
265
|
}
|
|
276
266
|
if (L === 2 && s.n === 2) {
|
|
277
|
-
// (no Angle
|
|
267
|
+
// (no Angle: the yellow + is the angle between them — or, parallel, the distance)
|
|
268
|
+
kind();
|
|
278
269
|
add('par', 'Parallel', 'par', { key: '⇧P' });
|
|
279
270
|
add('perp', 'Perpendicular', 'perp', { key: '⇧R' });
|
|
280
271
|
add('col', 'Collinear', 'col', { key: '⇧L' });
|
|
281
272
|
add('eq', 'Equal', 'eq', { key: '⇧E' });
|
|
282
273
|
// two lines that meet: round off (or cut off) the corner between them
|
|
283
274
|
if (filletCorner2()) {
|
|
275
|
+
items.push('sep');
|
|
284
276
|
add('fillet', 'Fillet', 'fillet');
|
|
285
277
|
add('chamfer', 'Chamfer', 'chamfer');
|
|
286
278
|
}
|
|
287
|
-
wallItems();
|
|
288
279
|
return items;
|
|
289
280
|
}
|
|
290
|
-
// a
|
|
291
|
-
if (P === 1 && s.n === 1 && cabOfPoint(sk, s.points[0]))
|
|
292
|
-
return items;
|
|
281
|
+
// a corner where exactly two lines meet: rounding it off or cutting it off (its angle is the yellow +)
|
|
293
282
|
if (P === 1 && s.n === 1) {
|
|
294
|
-
// a corner where exactly two lines meet: rounding it off or cutting it off (its angle is the yellow +)
|
|
295
283
|
if (filletable(sk, s.points[0])) {
|
|
296
284
|
add('fillet', 'Fillet', 'fillet');
|
|
297
285
|
add('chamfer', 'Chamfer', 'chamfer');
|
|
298
286
|
}
|
|
299
|
-
add('lock', 'Lock', 'lock', { key: '⇧F' });
|
|
300
287
|
return items;
|
|
301
288
|
}
|
|
302
|
-
|
|
289
|
+
// points: joined into the first picked, or level or plumb with it
|
|
290
|
+
if (P >= 2 && s.n === P) {
|
|
291
|
+
add('merge', 'Join', 'merge');
|
|
303
292
|
add('h', 'Horizontal', 'h');
|
|
304
293
|
add('v', 'Vertical', 'v');
|
|
305
|
-
add('dim', 'Distance', 'pdist');
|
|
306
|
-
add('merge', 'Join', 'merge');
|
|
307
294
|
return items;
|
|
308
295
|
}
|
|
309
296
|
if (P === 1 && L === 1 && s.n === 2) {
|
|
310
|
-
add('mid', 'Midpoint', 'mid', { key: '⇧M' });
|
|
311
297
|
add('online', 'On line', 'online', { key: '⇧O' });
|
|
312
|
-
add('
|
|
298
|
+
add('mid', 'Midpoint', 'mid', { key: '⇧M' });
|
|
313
299
|
return items;
|
|
314
300
|
}
|
|
315
301
|
if (P === 1 && C === 1 && s.n === 2) {
|
|
@@ -317,51 +303,35 @@ function ctxItems() {
|
|
|
317
303
|
return items;
|
|
318
304
|
}
|
|
319
305
|
if (L === 1 && C === 1 && s.n === 2) {
|
|
306
|
+
kind();
|
|
320
307
|
add('tan', 'Tangent', 'tan', { key: '⇧T' });
|
|
321
|
-
wallItems();
|
|
322
308
|
return items;
|
|
323
309
|
}
|
|
324
310
|
// a circle or arc: the yellow + sets its diameter
|
|
325
311
|
if (C === 1 && s.n === 1) {
|
|
326
|
-
|
|
327
|
-
guide();
|
|
328
|
-
if (wall)
|
|
329
|
-
return items;
|
|
330
|
-
if (ci && ci.o.kind === 'wall') {
|
|
331
|
-
add('thick', 'Thickness', 'thick');
|
|
332
|
-
add('arc', 'To line', 'kind');
|
|
333
|
-
}
|
|
334
|
-
else
|
|
335
|
-
add('wall', 'To wall', 'kind');
|
|
312
|
+
kind();
|
|
336
313
|
return items;
|
|
337
314
|
}
|
|
338
315
|
if (C === 2 && s.n === 2) {
|
|
339
|
-
|
|
316
|
+
kind();
|
|
340
317
|
add('conc', 'Concentric', 'conc');
|
|
341
|
-
|
|
318
|
+
add('eq', 'Equal', 'eqr');
|
|
342
319
|
return items;
|
|
343
320
|
}
|
|
344
321
|
// 3+ lines (only lines): the constraints that make sense across many, each tied to the first line picked
|
|
345
322
|
if (L >= 3 && s.n === L) {
|
|
323
|
+
kind();
|
|
346
324
|
add('h', 'Horizontal', 'h', { key: '⇧H' });
|
|
347
325
|
add('v', 'Vertical', 'v', { key: '⇧V' });
|
|
348
326
|
add('par', 'Parallel', 'par', { key: '⇧P' });
|
|
349
327
|
add('col', 'Collinear', 'col', { key: '⇧L' });
|
|
350
328
|
add('eq', 'Equal', 'eq', { key: '⇧E' });
|
|
351
|
-
items.push('sep');
|
|
352
|
-
add('lock', 'Lock', 'lock');
|
|
353
|
-
guide();
|
|
354
|
-
wallItems();
|
|
355
329
|
return items;
|
|
356
330
|
}
|
|
357
331
|
if (L >= 2) {
|
|
332
|
+
kind();
|
|
358
333
|
add('eq', 'Equal', 'eqall');
|
|
359
|
-
add('lock', 'Lock', 'lock');
|
|
360
|
-
guide();
|
|
361
|
-
wallItems();
|
|
362
|
-
return items;
|
|
363
334
|
}
|
|
364
|
-
add('lock', 'Lock', 'lock');
|
|
365
335
|
return items;
|
|
366
336
|
}
|
|
367
337
|
/** The keypad for walls' thickness, finished face to face. */
|
|
@@ -400,7 +370,7 @@ function filletCorner2() {
|
|
|
400
370
|
return p && filletable(sk, p) ? p : null;
|
|
401
371
|
}
|
|
402
372
|
/** Buttons for the bottom bar: they adapt to the tool and the selection (Lock first, when offered). */
|
|
403
|
-
export const barItems = () =>
|
|
373
|
+
export const barItems = () => arrangeLead(ctxItems());
|
|
404
374
|
/** The yellow + only appears for something it can measure: not for constraint badges or spaces alone. */
|
|
405
375
|
const DRAW_TOOLS = new Set(['line', 'freehand', 'rect', 'circle', 'arc']);
|
|
406
376
|
export function plusVisible() {
|
|
@@ -431,12 +401,8 @@ export function plusAction() {
|
|
|
431
401
|
if (wallPlus())
|
|
432
402
|
return;
|
|
433
403
|
const s = selInfo(), sk = SK();
|
|
434
|
-
if (s.dims.length === 1 && s.n === 1)
|
|
435
|
-
|
|
436
|
-
toast('Print dimensions are labels only — drag to move them.', { warn: true });
|
|
437
|
-
else
|
|
438
|
-
editDim(s.dims[0]);
|
|
439
|
-
}
|
|
404
|
+
if (s.dims.length === 1 && s.n === 1)
|
|
405
|
+
editDim(s.dims[0]);
|
|
440
406
|
else if (s.odims.length === 1 && s.n === 1) {
|
|
441
407
|
const [op, part] = s.odims[0].split('|');
|
|
442
408
|
editOpeningDim(op, part);
|
|
@@ -446,14 +412,14 @@ export function plusAction() {
|
|
|
446
412
|
else if (s.wfaces.length === 1 && s.n === 1) {
|
|
447
413
|
const [id, side] = edgeOf(s.wfaces[0]);
|
|
448
414
|
if (sk.lines[id])
|
|
449
|
-
dimLine(id,
|
|
415
|
+
dimLine(id, side);
|
|
450
416
|
else
|
|
451
417
|
dimCurve(id, side);
|
|
452
418
|
}
|
|
453
419
|
else if (edgePair(s))
|
|
454
420
|
applyCtxAction('edgeDist');
|
|
455
421
|
else if (s.lines.length === 1 && s.n === 1)
|
|
456
|
-
dimLine(s.lines[0]
|
|
422
|
+
dimLine(s.lines[0]);
|
|
457
423
|
else if (s.curves.length === 1 && s.n === 1)
|
|
458
424
|
dimCurve(s.curves[0]);
|
|
459
425
|
else if (s.openings.length === 1 && s.n === 1)
|
|
@@ -469,7 +435,7 @@ export function plusAction() {
|
|
|
469
435
|
else if (s.n === 2 && s.lines.length === 2)
|
|
470
436
|
dimTwoLines(s.lines[0], s.lines[1]);
|
|
471
437
|
else if (s.n === 2 && s.points.length === 2)
|
|
472
|
-
dimPoints(s.points[0], s.points[1]
|
|
438
|
+
dimPoints(s.points[0], s.points[1]);
|
|
473
439
|
else if (s.n === 2 && s.points.length === 1 && s.lines.length === 1)
|
|
474
440
|
dimPointLine(s.points[0], s.lines[0]);
|
|
475
441
|
else
|
|
@@ -540,9 +506,14 @@ const NEEDS = {
|
|
|
540
506
|
h: 'a line or two points', v: 'a line or two points', par: 'two lines', perp: 'two lines', col: 'two lines', eq: 'two lines', mid: 'a point and a line',
|
|
541
507
|
online: 'a point and a line', oncircle: 'a point and a circle', tan: 'a line and a circle or arc', eqr: 'two circles', conc: 'two circles',
|
|
542
508
|
};
|
|
543
|
-
/**
|
|
509
|
+
/**
|
|
510
|
+
* H/V on each selected line, or parallel/collinear/equal from the first selected line to each of the others; three
|
|
511
|
+
* or more points, each level or plumb with the first picked.
|
|
512
|
+
*/
|
|
544
513
|
function multiLineCons(type) {
|
|
545
514
|
const sk = SK(), s = selInfo();
|
|
515
|
+
if ((type === 'h' || type === 'v') && s.n === s.points.length && s.points.length >= 3)
|
|
516
|
+
return s.points.slice(1).map(id => ({ type, a: s.points[0], b: id }));
|
|
546
517
|
if (s.n !== s.lines.length || s.lines.length < (type === 'h' || type === 'v' ? 2 : 3))
|
|
547
518
|
return null;
|
|
548
519
|
if (type === 'h' || type === 'v')
|
|
@@ -552,8 +523,11 @@ function multiLineCons(type) {
|
|
|
552
523
|
return null;
|
|
553
524
|
}
|
|
554
525
|
const ADJ = { h: 'horizontal', v: 'vertical', par: 'parallel', col: 'collinear', eq: 'equal' };
|
|
555
|
-
/**
|
|
556
|
-
|
|
526
|
+
/**
|
|
527
|
+
* Apply one constraint type across several lines (or `points`, each tied to the first) as one undo step. A conflict
|
|
528
|
+
* opens the conflict sheet for the first one that fails.
|
|
529
|
+
*/
|
|
530
|
+
function applyMulti(type, list, points = false) {
|
|
557
531
|
pushHistory();
|
|
558
532
|
let added = 0;
|
|
559
533
|
const failed = [];
|
|
@@ -572,9 +546,9 @@ function applyMulti(type, list) {
|
|
|
572
546
|
}
|
|
573
547
|
if (!added)
|
|
574
548
|
dropHistory();
|
|
575
|
-
// every selected line that now satisfies it (some may already have, through other constraints)
|
|
576
|
-
const lines = (type === 'h' || type === 'v' ? list.length : list.length + 1) - failed.length;
|
|
577
|
-
const nLines = lines + ' line' + (lines === 1 ? '' : 's');
|
|
549
|
+
// every selected line (or point) that now satisfies it (some may already have, through other constraints)
|
|
550
|
+
const lines = ((type === 'h' || type === 'v') && !points ? list.length : list.length + 1) - failed.length;
|
|
551
|
+
const nLines = lines + (points ? ' point' : ' line') + (lines === 1 ? '' : 's');
|
|
578
552
|
if (failed.length) {
|
|
579
553
|
// explain the first conflict; the sheet's Remove re-applies it
|
|
580
554
|
tryAddConstraint(failed[0], { onResolved: () => afterEdit() });
|
|
@@ -586,9 +560,9 @@ function applyMulti(type, list) {
|
|
|
586
560
|
afterEdit();
|
|
587
561
|
}
|
|
588
562
|
function applyCons(type) {
|
|
589
|
-
const multi = multiLineCons(type);
|
|
590
|
-
if (multi && (multi.length > 1 ||
|
|
591
|
-
applyMulti(type, multi);
|
|
563
|
+
const multi = multiLineCons(type), s = selInfo(), points = s.points.length > 2 && s.n === s.points.length;
|
|
564
|
+
if (multi && (multi.length > 1 || s.lines.length > 2 || points)) {
|
|
565
|
+
applyMulti(type, multi, points);
|
|
592
566
|
return;
|
|
593
567
|
}
|
|
594
568
|
const c = consFromSel(type);
|
|
@@ -654,12 +628,6 @@ export function applyCtxAction(act, anchor) {
|
|
|
654
628
|
tools[ed.tool].finish();
|
|
655
629
|
updateCtx();
|
|
656
630
|
return;
|
|
657
|
-
case 'dimDrive':
|
|
658
|
-
ed.dimLayer = 'drive';
|
|
659
|
-
break;
|
|
660
|
-
case 'dimPrint':
|
|
661
|
-
ed.dimLayer = 'print';
|
|
662
|
-
break;
|
|
663
631
|
case 'h':
|
|
664
632
|
case 'v':
|
|
665
633
|
case 'par':
|
|
@@ -676,15 +644,15 @@ export function applyCtxAction(act, anchor) {
|
|
|
676
644
|
return;
|
|
677
645
|
case 'len':
|
|
678
646
|
if (s.lines.length === 1)
|
|
679
|
-
dimLine(s.lines[0]
|
|
647
|
+
dimLine(s.lines[0]);
|
|
680
648
|
else if (s.wfaces.length === 1 && sk.lines[edgeOf(s.wfaces[0])[0]]) {
|
|
681
649
|
const [lid, side] = edgeOf(s.wfaces[0]);
|
|
682
|
-
dimLine(lid,
|
|
650
|
+
dimLine(lid, side);
|
|
683
651
|
}
|
|
684
652
|
return;
|
|
685
653
|
case 'pdist':
|
|
686
654
|
if (s.points.length === 2)
|
|
687
|
-
dimPoints(s.points[0], s.points[1]
|
|
655
|
+
dimPoints(s.points[0], s.points[1]);
|
|
688
656
|
return;
|
|
689
657
|
case 'pldist':
|
|
690
658
|
if (s.points.length === 1 && s.lines.length === 1)
|
|
@@ -705,9 +673,17 @@ export function applyCtxAction(act, anchor) {
|
|
|
705
673
|
cornerAngle(s.points[0]);
|
|
706
674
|
return;
|
|
707
675
|
case 'merge': {
|
|
676
|
+
// (every one into the first picked)
|
|
677
|
+
if (s.points.length < 2)
|
|
678
|
+
return;
|
|
708
679
|
pushHistory();
|
|
709
680
|
const snap = JSON.stringify(sk);
|
|
710
|
-
if (joinPoints(sk, s.points[0],
|
|
681
|
+
if (!s.points.slice(1).every(id => joinPoints(sk, s.points[0], id))) {
|
|
682
|
+
replaceSK(JSON.parse(snap));
|
|
683
|
+
dropHistory();
|
|
684
|
+
toast('Two locked points can’t be joined. Unlock one first.', { warn: true });
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
711
687
|
const r = solveSketch();
|
|
712
688
|
if (!r.ok) {
|
|
713
689
|
replaceSK(JSON.parse(snap));
|
|
@@ -824,10 +800,11 @@ export function applyCtxAction(act, anchor) {
|
|
|
824
800
|
case 'renameRoom':
|
|
825
801
|
editRoomName(s.rooms[0]);
|
|
826
802
|
return;
|
|
803
|
+
// (the room is on the left of the wall's start → end when there's none either side of it)
|
|
827
804
|
case 'opFlip': {
|
|
828
805
|
pushHistory();
|
|
829
806
|
const o = sk.openings[s.openings[0]];
|
|
830
|
-
o
|
|
807
|
+
flipDoor(o, roomSideOf(sk, o.line) === -1 ? -1 : 1);
|
|
831
808
|
afterEdit();
|
|
832
809
|
return;
|
|
833
810
|
}
|
|
@@ -876,12 +853,12 @@ export function applyCtxAction(act, anchor) {
|
|
|
876
853
|
case 'palette':
|
|
877
854
|
openPalette(anchor);
|
|
878
855
|
return;
|
|
879
|
-
case 'convert':
|
|
880
|
-
convertNote(s.notes[0]);
|
|
881
|
-
return;
|
|
882
856
|
case 'linkOpen':
|
|
883
857
|
openSelectedLink();
|
|
884
858
|
return;
|
|
859
|
+
case 'linkExpand':
|
|
860
|
+
toggleExpand();
|
|
861
|
+
return;
|
|
885
862
|
case 'linkSize':
|
|
886
863
|
toggleTileSize(anchor);
|
|
887
864
|
return;
|
|
@@ -916,6 +893,9 @@ export function applyCtxAction(act, anchor) {
|
|
|
916
893
|
const lock = !ims.every(u => u.locked), many = ims.length > 1;
|
|
917
894
|
pushHistory();
|
|
918
895
|
ims.forEach(u => { u.locked = lock; });
|
|
896
|
+
// (locked, its bar is Lock and Open alone: an opacity or turning slider open over it goes with the rest)
|
|
897
|
+
if (lock)
|
|
898
|
+
dismissPopovers();
|
|
919
899
|
afterEdit();
|
|
920
900
|
toast(lock ? (many ? 'Images locked. They won’t move while you trace over them.' : 'Image locked. It won’t move while you trace over it.')
|
|
921
901
|
: many ? 'Images unlocked.' : 'Image unlocked. Drag it, or drag its handles to size it.');
|