@reekon-tools/boldr-cad 0.1.0 → 1.0.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 +81 -9
- package/dist/app/edit.d.ts +12 -1
- package/dist/app/edit.js +96 -11
- package/dist/app/edit.js.map +1 -1
- package/dist/app/state.d.ts +31 -4
- package/dist/app/state.js +31 -2
- package/dist/app/state.js.map +1 -1
- package/dist/data/memory.d.ts +5 -0
- package/dist/data/memory.js +2 -2
- package/dist/data/memory.js.map +1 -1
- package/dist/data/provider.d.ts +19 -7
- package/dist/data/sync.d.ts +7 -2
- package/dist/data/sync.js +115 -64
- package/dist/data/sync.js.map +1 -1
- package/dist/editor/actions.d.ts +1 -2
- package/dist/editor/actions.js +391 -113
- package/dist/editor/actions.js.map +1 -1
- package/dist/editor/confirm.d.ts +23 -0
- package/dist/editor/confirm.js +35 -0
- package/dist/editor/confirm.js.map +1 -0
- package/dist/editor/elevation.d.ts +191 -90
- package/dist/editor/elevation.js +812 -616
- package/dist/editor/elevation.js.map +1 -1
- package/dist/editor/face.d.ts +73 -0
- package/dist/editor/face.js +269 -0
- package/dist/editor/face.js.map +1 -0
- package/dist/editor/files.d.ts +6 -0
- package/dist/editor/files.js +50 -4
- package/dist/editor/files.js.map +1 -1
- package/dist/editor/gestures.d.ts +18 -3
- package/dist/editor/gestures.js +145 -30
- package/dist/editor/gestures.js.map +1 -1
- package/dist/editor/keypad.d.ts +19 -4
- package/dist/editor/keypad.js +39 -9
- package/dist/editor/keypad.js.map +1 -1
- package/dist/editor/keys.js +36 -4
- package/dist/editor/keys.js.map +1 -1
- package/dist/editor/laser.d.ts +20 -4
- package/dist/editor/laser.js +104 -21
- package/dist/editor/laser.js.map +1 -1
- package/dist/editor/linkActions.d.ts +49 -0
- package/dist/editor/linkActions.js +210 -0
- package/dist/editor/linkActions.js.map +1 -0
- package/dist/editor/linkFollow.d.ts +2 -0
- package/dist/editor/linkFollow.js +53 -0
- package/dist/editor/linkFollow.js.map +1 -0
- package/dist/editor/linkWrite.d.ts +13 -0
- package/dist/editor/linkWrite.js +84 -0
- package/dist/editor/linkWrite.js.map +1 -0
- package/dist/editor/links.d.ts +84 -0
- package/dist/editor/links.js +130 -0
- package/dist/editor/links.js.map +1 -0
- package/dist/editor/markup.d.ts +29 -3
- package/dist/editor/markup.js +95 -10
- package/dist/editor/markup.js.map +1 -1
- package/dist/editor/mode.d.ts +40 -2
- package/dist/editor/mode.js +71 -4
- package/dist/editor/mode.js.map +1 -1
- package/dist/editor/ports.d.ts +36 -6
- package/dist/editor/ports.js +10 -1
- package/dist/editor/ports.js.map +1 -1
- package/dist/editor/project.d.ts +38 -6
- package/dist/editor/project.js +167 -27
- package/dist/editor/project.js.map +1 -1
- package/dist/editor/selection.d.ts +10 -0
- package/dist/editor/selection.js +62 -8
- package/dist/editor/selection.js.map +1 -1
- package/dist/editor/status.js +3 -0
- package/dist/editor/status.js.map +1 -1
- package/dist/editor/tooltips.d.ts +5 -0
- package/dist/editor/tooltips.js +48 -0
- package/dist/editor/tooltips.js.map +1 -0
- package/dist/editor/view3d.d.ts +17 -0
- package/dist/editor/view3d.js +135 -0
- package/dist/editor/view3d.js.map +1 -0
- package/dist/editor/view3dState.d.ts +39 -0
- package/dist/editor/view3dState.js +44 -0
- package/dist/editor/view3dState.js.map +1 -0
- package/dist/export/dxf.js +1 -1
- package/dist/export/dxf.js.map +1 -1
- package/dist/export/pdf.js +17 -12
- package/dist/export/pdf.js.map +1 -1
- package/dist/export/plan.d.ts +23 -6
- package/dist/export/plan.js +127 -44
- package/dist/export/plan.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/model/constraints.js +15 -4
- package/dist/model/constraints.js.map +1 -1
- package/dist/model/demo.js +3 -3
- package/dist/model/demo.js.map +1 -1
- package/dist/model/elevation.d.ts +6 -9
- package/dist/model/elevation.js +5 -13
- package/dist/model/elevation.js.map +1 -1
- package/dist/model/faces.d.ts +58 -0
- package/dist/model/faces.js +197 -0
- package/dist/model/faces.js.map +1 -0
- package/dist/model/facesolve.d.ts +72 -0
- package/dist/model/facesolve.js +369 -0
- package/dist/model/facesolve.js.map +1 -0
- package/dist/model/history.d.ts +3 -2
- package/dist/model/history.js +17 -11
- package/dist/model/history.js.map +1 -1
- package/dist/model/images.d.ts +18 -0
- package/dist/model/images.js +26 -0
- package/dist/model/images.js.map +1 -0
- package/dist/model/links.d.ts +46 -0
- package/dist/model/links.js +73 -0
- package/dist/model/links.js.map +1 -0
- package/dist/model/markup.d.ts +46 -0
- package/dist/model/markup.js +71 -0
- package/dist/model/markup.js.map +1 -0
- package/dist/model/measurement.d.ts +11 -0
- package/dist/model/measurement.js +24 -0
- package/dist/model/measurement.js.map +1 -0
- package/dist/model/openings.d.ts +4 -0
- package/dist/model/openings.js +4 -0
- package/dist/model/openings.js.map +1 -1
- package/dist/model/planGeom.d.ts +72 -2
- package/dist/model/planGeom.js +184 -10
- package/dist/model/planGeom.js.map +1 -1
- package/dist/model/project.js +3 -2
- package/dist/model/project.js.map +1 -1
- package/dist/model/projectfile.js +2 -1
- package/dist/model/projectfile.js.map +1 -1
- package/dist/model/rooms.js +2 -2
- package/dist/model/rooms.js.map +1 -1
- package/dist/model/settings.d.ts +6 -0
- package/dist/model/settings.js +2 -2
- package/dist/model/settings.js.map +1 -1
- package/dist/model/sketch.d.ts +14 -0
- package/dist/model/sketch.js +143 -24
- package/dist/model/sketch.js.map +1 -1
- package/dist/model/smooth.d.ts +6 -0
- package/dist/model/smooth.js +52 -0
- package/dist/model/smooth.js.map +1 -0
- package/dist/model/store.d.ts +12 -0
- package/dist/model/store.js +35 -3
- package/dist/model/store.js.map +1 -1
- package/dist/model/types.d.ts +173 -8
- package/dist/model/wallfaces.d.ts +32 -0
- package/dist/model/wallfaces.js +133 -20
- package/dist/model/wallfaces.js.map +1 -1
- package/dist/model/wallshape.d.ts +15 -0
- package/dist/model/wallshape.js +89 -0
- package/dist/model/wallshape.js.map +1 -0
- package/dist/react/BottomBar.d.ts +9 -5
- package/dist/react/BottomBar.js +213 -24
- package/dist/react/BottomBar.js.map +1 -1
- package/dist/react/Buttons.d.ts +13 -2
- package/dist/react/Buttons.js +37 -5
- package/dist/react/Buttons.js.map +1 -1
- package/dist/react/CadEditor.d.ts +46 -3
- package/dist/react/CadEditor.js +77 -20
- package/dist/react/CadEditor.js.map +1 -1
- package/dist/react/ConfirmSheet.d.ts +4 -0
- package/dist/react/ConfirmSheet.js +31 -0
- package/dist/react/ConfirmSheet.js.map +1 -0
- package/dist/react/ConflictSheet.d.ts +2 -1
- package/dist/react/ConflictSheet.js +16 -8
- package/dist/react/ConflictSheet.js.map +1 -1
- package/dist/react/Drawer.d.ts +8 -2
- package/dist/react/Drawer.js +167 -38
- package/dist/react/Drawer.js.map +1 -1
- package/dist/react/Duck.d.ts +13 -0
- package/dist/react/Duck.js +45 -0
- package/dist/react/Duck.js.map +1 -0
- package/dist/react/Empty3D.d.ts +2 -0
- package/dist/react/Empty3D.js +39 -0
- package/dist/react/Empty3D.js.map +1 -0
- package/dist/react/FitScroll.d.ts +6 -0
- package/dist/react/FitScroll.js +14 -0
- package/dist/react/FitScroll.js.map +1 -0
- package/dist/react/Icon.native.d.ts +6 -0
- package/dist/react/Icon.native.js +4 -0
- package/dist/react/Icon.native.js.map +1 -1
- package/dist/react/Icon.web.d.ts +6 -0
- package/dist/react/Icon.web.js +4 -0
- package/dist/react/Icon.web.js.map +1 -1
- package/dist/react/KeypadSheet.d.ts +9 -2
- package/dist/react/KeypadSheet.js +174 -42
- package/dist/react/KeypadSheet.js.map +1 -1
- package/dist/react/LaserTray.js +8 -5
- package/dist/react/LaserTray.js.map +1 -1
- package/dist/react/MarkupPanel.d.ts +8 -0
- package/dist/react/MarkupPanel.js +67 -0
- package/dist/react/MarkupPanel.js.map +1 -0
- package/dist/react/PageThumb.native.d.ts +7 -2
- package/dist/react/PageThumb.native.js +19 -7
- package/dist/react/PageThumb.native.js.map +1 -1
- package/dist/react/PageThumb.web.d.ts +3 -2
- package/dist/react/PageThumb.web.js +4 -4
- package/dist/react/PageThumb.web.js.map +1 -1
- package/dist/react/PlanCanvas.native.d.ts +1 -1
- package/dist/react/PlanCanvas.native.js +134 -36
- package/dist/react/PlanCanvas.native.js.map +1 -1
- package/dist/react/PlanCanvas.web.js +16 -5
- package/dist/react/PlanCanvas.web.js.map +1 -1
- package/dist/react/Popovers.d.ts +12 -6
- package/dist/react/Popovers.js +53 -26
- package/dist/react/Popovers.js.map +1 -1
- package/dist/react/Rail.d.ts +24 -0
- package/dist/react/Rail.js +324 -34
- package/dist/react/Rail.js.map +1 -1
- package/dist/react/ShapeMenu.d.ts +7 -0
- package/dist/react/ShapeMenu.js +58 -0
- package/dist/react/ShapeMenu.js.map +1 -0
- package/dist/react/Sheet.d.ts +14 -4
- package/dist/react/Sheet.js +54 -9
- package/dist/react/Sheet.js.map +1 -1
- package/dist/react/Slider.d.ts +18 -0
- package/dist/react/Slider.js +27 -0
- package/dist/react/Slider.js.map +1 -0
- package/dist/react/Toast.js +14 -6
- package/dist/react/Toast.js.map +1 -1
- package/dist/react/ToolTip.d.ts +7 -0
- package/dist/react/ToolTip.js +43 -0
- package/dist/react/ToolTip.js.map +1 -0
- package/dist/react/TopBar.d.ts +6 -3
- package/dist/react/TopBar.js +48 -17
- package/dist/react/TopBar.js.map +1 -1
- package/dist/react/Tween.d.ts +7 -0
- package/dist/react/Tween.js +26 -0
- package/dist/react/Tween.js.map +1 -0
- package/dist/react/Tween.native.d.ts +6 -0
- package/dist/react/Tween.native.js +43 -0
- package/dist/react/Tween.native.js.map +1 -0
- package/dist/react/View3D.native.d.ts +4 -0
- package/dist/react/View3D.native.js +103 -0
- package/dist/react/View3D.native.js.map +1 -0
- package/dist/react/View3D.web.d.ts +4 -0
- package/dist/react/View3D.web.js +151 -0
- package/dist/react/View3D.web.js.map +1 -0
- package/dist/react/View3DHost.d.ts +143 -0
- package/dist/react/View3DHost.js +377 -0
- package/dist/react/View3DHost.js.map +1 -0
- package/dist/react/corners.d.ts +7 -0
- package/dist/react/corners.js +51 -0
- package/dist/react/corners.js.map +1 -0
- package/dist/react/easing.d.ts +15 -0
- package/dist/react/easing.js +10 -0
- package/dist/react/easing.js.map +1 -0
- package/dist/react/host.d.ts +7 -2
- package/dist/react/host.js +8 -3
- package/dist/react/host.js.map +1 -1
- package/dist/react/hostKeypad.d.ts +56 -0
- package/dist/react/hostKeypad.js +5 -0
- package/dist/react/hostKeypad.js.map +1 -0
- package/dist/react/motion.d.ts +77 -0
- package/dist/react/motion.js +173 -0
- package/dist/react/motion.js.map +1 -0
- package/dist/react/ports.js +90 -8
- package/dist/react/ports.js.map +1 -1
- package/dist/react/saving.d.ts +15 -0
- package/dist/react/saving.js +76 -0
- package/dist/react/saving.js.map +1 -0
- package/dist/react/store.d.ts +38 -1
- package/dist/react/store.js +31 -1
- package/dist/react/store.js.map +1 -1
- package/dist/react/theme.d.ts +10 -0
- package/dist/react/theme.js +8 -0
- package/dist/react/theme.js.map +1 -1
- package/dist/react/topRow.d.ts +22 -0
- package/dist/react/topRow.js +61 -0
- package/dist/react/topRow.js.map +1 -0
- package/dist/react/touch.d.ts +6 -0
- package/dist/react/touch.js +9 -0
- package/dist/react/touch.js.map +1 -0
- package/dist/render/ctx.d.ts +8 -0
- package/dist/render/draw.d.ts +33 -1
- package/dist/render/draw.js +593 -215
- package/dist/render/draw.js.map +1 -1
- package/dist/render/elevation.d.ts +65 -43
- package/dist/render/elevation.js +414 -140
- package/dist/render/elevation.js.map +1 -1
- package/dist/render/figmaIcons.d.ts +12 -0
- package/dist/render/figmaIcons.js +21 -0
- package/dist/render/figmaIcons.js.map +1 -0
- package/dist/render/hit.d.ts +22 -2
- package/dist/render/hit.js +168 -25
- package/dist/render/hit.js.map +1 -1
- package/dist/render/icons.js +69 -10
- package/dist/render/icons.js.map +1 -1
- package/dist/render/linkTile.d.ts +15 -0
- package/dist/render/linkTile.js +369 -0
- package/dist/render/linkTile.js.map +1 -0
- package/dist/render/markup.d.ts +11 -0
- package/dist/render/markup.js +112 -0
- package/dist/render/markup.js.map +1 -0
- package/dist/render/navigate.d.ts +88 -0
- package/dist/render/navigate.js +100 -0
- package/dist/render/navigate.js.map +1 -0
- package/dist/render/snap.d.ts +20 -1
- package/dist/render/snap.js +118 -25
- package/dist/render/snap.js.map +1 -1
- package/dist/render/squircle.d.ts +8 -0
- package/dist/render/squircle.js +71 -0
- package/dist/render/squircle.js.map +1 -0
- package/dist/render/view.d.ts +115 -2
- package/dist/render/view.js +244 -19
- package/dist/render/view.js.map +1 -1
- package/dist/skia/SkiaDrawCtx.d.ts +30 -2
- package/dist/skia/SkiaDrawCtx.js +198 -43
- package/dist/skia/SkiaDrawCtx.js.map +1 -1
- package/dist/skia/host.d.ts +18 -2
- package/dist/skia/host.js +46 -1
- package/dist/skia/host.js.map +1 -1
- package/dist/solver/equations.js +37 -2
- package/dist/solver/equations.js.map +1 -1
- package/dist/solver/gauss-newton.d.ts +17 -0
- package/dist/solver/gauss-newton.js +144 -36
- package/dist/solver/gauss-newton.js.map +1 -1
- package/dist/solver/linalg.d.ts +45 -2
- package/dist/solver/linalg.js +164 -10
- package/dist/solver/linalg.js.map +1 -1
- package/dist/solver/types.d.ts +5 -0
- package/dist/tools/annotate.d.ts +57 -1
- package/dist/tools/annotate.js +64 -43
- package/dist/tools/annotate.js.map +1 -1
- package/dist/tools/chamfer.d.ts +12 -0
- package/dist/tools/chamfer.js +114 -0
- package/dist/tools/chamfer.js.map +1 -0
- package/dist/tools/corner.d.ts +38 -0
- package/dist/tools/corner.js +99 -0
- package/dist/tools/corner.js.map +1 -0
- package/dist/tools/dim.d.ts +86 -9
- package/dist/tools/dim.js +581 -96
- package/dist/tools/dim.js.map +1 -1
- package/dist/tools/elevation.js +1 -1
- package/dist/tools/elevation.js.map +1 -1
- package/dist/tools/fillet.d.ts +9 -0
- package/dist/tools/fillet.js +86 -0
- package/dist/tools/fillet.js.map +1 -0
- package/dist/tools/freehand.d.ts +3 -2
- package/dist/tools/freehand.js +19 -12
- package/dist/tools/freehand.js.map +1 -1
- package/dist/tools/index.js +14 -4
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/line.d.ts +8 -3
- package/dist/tools/line.js +66 -30
- package/dist/tools/line.js.map +1 -1
- package/dist/tools/modify.d.ts +13 -0
- package/dist/tools/modify.js +105 -25
- package/dist/tools/modify.js.map +1 -1
- package/dist/tools/openings.js +22 -15
- package/dist/tools/openings.js.map +1 -1
- package/dist/tools/registry.d.ts +8 -0
- package/dist/tools/registry.js +42 -2
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/select.d.ts +77 -3
- package/dist/tools/select.js +560 -74
- package/dist/tools/select.js.map +1 -1
- package/dist/tools/shapes.d.ts +14 -0
- package/dist/tools/shapes.js +154 -75
- package/dist/tools/shapes.js.map +1 -1
- package/dist/units/format.d.ts +10 -2
- package/dist/units/format.js +18 -3
- package/dist/units/format.js.map +1 -1
- package/dist/view3d/build.d.ts +35 -0
- package/dist/view3d/build.js +630 -0
- package/dist/view3d/build.js.map +1 -0
- package/dist/view3d/camera.d.ts +60 -0
- package/dist/view3d/camera.js +84 -0
- package/dist/view3d/camera.js.map +1 -0
- package/dist/view3d/fatLines.d.ts +11 -0
- package/dist/view3d/fatLines.js +83 -0
- package/dist/view3d/fatLines.js.map +1 -0
- package/dist/view3d/lines.d.ts +17 -0
- package/dist/view3d/lines.js +271 -0
- package/dist/view3d/lines.js.map +1 -0
- package/dist/view3d/mesh.d.ts +51 -0
- package/dist/view3d/mesh.js +101 -0
- package/dist/view3d/mesh.js.map +1 -0
- package/dist/view3d/poly.d.ts +16 -0
- package/dist/view3d/poly.js +109 -0
- package/dist/view3d/poly.js.map +1 -0
- package/dist/view3d/viewer.d.ts +86 -0
- package/dist/view3d/viewer.js +350 -0
- package/dist/view3d/viewer.js.map +1 -0
- package/package.json +15 -2
- package/src/app/edit.ts +83 -12
- package/src/app/main.ts +11 -5
- package/src/app/state.ts +38 -5
- package/src/data/memory.ts +2 -2
- package/src/data/provider.ts +15 -7
- package/src/data/sync.ts +87 -50
- package/src/editor/actions.ts +292 -83
- package/src/editor/confirm.ts +58 -0
- package/src/editor/elevation.ts +698 -478
- package/src/editor/face.ts +227 -0
- package/src/editor/files.ts +40 -4
- package/src/editor/gestures.ts +147 -31
- package/src/editor/keypad.ts +43 -11
- package/src/editor/keys.ts +25 -5
- package/src/editor/laser.ts +98 -20
- package/src/editor/linkActions.ts +194 -0
- package/src/editor/linkFollow.ts +43 -0
- package/src/editor/linkWrite.ts +77 -0
- package/src/editor/links.ts +181 -0
- package/src/editor/markup.ts +92 -8
- package/src/editor/mode.ts +83 -7
- package/src/editor/ports.ts +37 -6
- package/src/editor/project.ts +155 -26
- package/src/editor/selection.ts +54 -8
- package/src/editor/status.ts +2 -0
- package/src/editor/tooltips.ts +47 -0
- package/src/editor/view3d.ts +119 -0
- package/src/editor/view3dState.ts +61 -0
- package/src/export/dxf.ts +1 -1
- package/src/export/pdf.ts +15 -7
- package/src/export/plan.ts +119 -35
- package/src/index.ts +5 -0
- package/src/input/keyboard.ts +2 -4
- package/src/model/constraints.ts +13 -4
- package/src/model/demo.ts +3 -3
- package/src/model/elevation.ts +6 -16
- package/src/model/faces.ts +153 -0
- package/src/model/facesolve.ts +322 -0
- package/src/model/history.ts +19 -11
- package/src/model/images.ts +35 -0
- package/src/model/links.ts +77 -0
- package/src/model/markup.ts +80 -0
- package/src/model/measurement.ts +26 -0
- package/src/model/openings.ts +6 -0
- package/src/model/planGeom.ts +174 -8
- package/src/model/project.ts +3 -2
- package/src/model/projectfile.ts +2 -1
- package/src/model/rooms.ts +1 -1
- package/src/model/settings.ts +8 -2
- package/src/model/sketch.ts +101 -14
- package/src/model/smooth.ts +51 -0
- package/src/model/store.ts +35 -4
- package/src/model/types.ts +170 -8
- package/src/model/wallfaces.ts +126 -17
- package/src/model/wallshape.ts +97 -0
- package/src/react/BottomBar.tsx +263 -46
- package/src/react/Buttons.tsx +50 -9
- package/src/react/CadEditor.tsx +133 -30
- package/src/react/ConfirmSheet.tsx +38 -0
- package/src/react/ConflictSheet.tsx +19 -12
- package/src/react/Drawer.tsx +213 -110
- package/src/react/Duck.tsx +50 -0
- package/src/react/Empty3D.tsx +32 -0
- package/src/react/FitScroll.tsx +19 -0
- package/src/react/Icon.native.tsx +5 -0
- package/src/react/Icon.web.tsx +5 -0
- package/src/react/KeypadSheet.tsx +253 -60
- package/src/react/LaserTray.tsx +12 -8
- package/src/react/MarkupPanel.tsx +150 -0
- package/src/react/PageThumb.native.tsx +18 -7
- package/src/react/PageThumb.web.tsx +4 -4
- package/src/react/PlanCanvas.native.tsx +151 -34
- package/src/react/PlanCanvas.web.tsx +15 -5
- package/src/react/Popovers.tsx +116 -51
- package/src/react/Rail.tsx +360 -27
- package/src/react/ShapeMenu.tsx +66 -0
- package/src/react/Sheet.tsx +56 -11
- package/src/react/Slider.tsx +48 -0
- package/src/react/Toast.tsx +16 -9
- package/src/react/ToolTip.tsx +49 -0
- package/src/react/TopBar.tsx +97 -39
- package/src/react/Tween.native.tsx +36 -0
- package/src/react/Tween.tsx +24 -0
- package/src/react/View3D.native.tsx +131 -0
- package/src/react/View3D.web.tsx +131 -0
- package/src/react/View3DHost.ts +367 -0
- package/src/react/corners.ts +52 -0
- package/src/react/easing.ts +14 -0
- package/src/react/host.ts +8 -3
- package/src/react/hostKeypad.ts +66 -0
- package/src/react/motion.tsx +239 -0
- package/src/react/ports.ts +78 -9
- package/src/react/saving.ts +80 -0
- package/src/react/store.ts +35 -3
- package/src/react/theme.ts +8 -0
- package/src/react/topRow.ts +71 -0
- package/src/react/touch.ts +10 -0
- package/src/render/ctx.ts +8 -0
- package/src/render/draw.ts +437 -175
- package/src/render/elevation.ts +349 -143
- package/src/render/figmaIcons.ts +22 -0
- package/src/render/hit.ts +140 -21
- package/src/render/icons.ts +69 -10
- package/src/render/linkTile.ts +297 -0
- package/src/render/markup.ts +91 -0
- package/src/render/navigate.ts +114 -0
- package/src/render/snap.ts +105 -22
- package/src/render/squircle.ts +73 -0
- package/src/render/view.ts +255 -19
- package/src/skia/SkiaDrawCtx.ts +163 -41
- package/src/skia/host.ts +46 -1
- package/src/solver/equations.ts +37 -2
- package/src/solver/gauss-newton.ts +125 -28
- package/src/solver/linalg.ts +141 -5
- package/src/solver/types.ts +5 -0
- package/src/styles.css +91 -42
- package/src/tools/annotate.ts +58 -29
- package/src/tools/chamfer.ts +103 -0
- package/src/tools/corner.ts +99 -0
- package/src/tools/dim.ts +524 -92
- package/src/tools/elevation.ts +1 -1
- package/src/tools/fillet.ts +82 -0
- package/src/tools/freehand.ts +20 -16
- package/src/tools/index.ts +17 -4
- package/src/tools/line.ts +59 -23
- package/src/tools/modify.ts +86 -24
- package/src/tools/openings.ts +8 -13
- package/src/tools/registry.ts +27 -2
- package/src/tools/select.ts +424 -75
- package/src/tools/shapes.ts +119 -58
- package/src/ui/ctx.ts +2 -1
- package/src/ui/domPorts.ts +23 -4
- package/src/ui/drawer.ts +6 -26
- package/src/ui/elevation.ts +10 -101
- package/src/ui/keypad.ts +8 -14
- package/src/ui/laser.ts +2 -2
- package/src/ui/menu.ts +63 -17
- package/src/ui/noteinput.ts +3 -3
- package/src/ui/opacity.ts +108 -0
- package/src/ui/palette.ts +67 -12
- package/src/ui/rail.ts +21 -7
- package/src/ui/topbar.ts +4 -14
- package/src/units/format.ts +18 -3
- package/src/view3d/build.ts +607 -0
- package/src/view3d/camera.ts +113 -0
- package/src/view3d/fatLines.ts +83 -0
- package/src/view3d/lines.ts +258 -0
- package/src/view3d/mesh.ts +111 -0
- package/src/view3d/poly.ts +104 -0
- package/src/view3d/viewer.ts +348 -0
- package/dist/react/ElevCanvas.native.d.ts +0 -3
- package/dist/react/ElevCanvas.native.js +0 -58
- package/dist/react/ElevCanvas.native.js.map +0 -1
- package/dist/react/ElevCanvas.web.d.ts +0 -4
- package/dist/react/ElevCanvas.web.js +0 -44
- package/dist/react/ElevCanvas.web.js.map +0 -1
- package/dist/react/ElevationSheet.d.ts +0 -2
- package/dist/react/ElevationSheet.js +0 -38
- package/dist/react/ElevationSheet.js.map +0 -1
- package/dist/render/elevationView.d.ts +0 -3
- package/dist/render/elevationView.js +0 -100
- package/dist/render/elevationView.js.map +0 -1
- package/src/react/ElevCanvas.native.tsx +0 -68
- package/src/react/ElevCanvas.web.tsx +0 -42
- package/src/react/ElevationSheet.tsx +0 -88
- package/src/render/elevationView.ts +0 -73
package/src/editor/actions.ts
CHANGED
|
@@ -1,23 +1,31 @@
|
|
|
1
|
-
import { afterEdit, solveSketch, tryAddConstraint, undo } from '../app/edit';
|
|
1
|
+
import { afterEdit, holdFaces, solveSketch, tryAddConstraint, undo } from '../app/edit';
|
|
2
2
|
import { ed, type OpeningDimPart } from '../app/state';
|
|
3
|
-
import { CONS_LABEL } from '../model/constraints';
|
|
3
|
+
import { CONS_LABEL, measure } from '../model/constraints';
|
|
4
4
|
import { dropHistory, pushHistory } from '../model/history';
|
|
5
|
-
import { createsCycle, facingEdges, nearerEdge } from '../model/openings';
|
|
5
|
+
import { createsCycle, edgePoint, facingEdges, nearerEdge } from '../model/openings';
|
|
6
|
+
import { cabOfPoint } from '../model/faces';
|
|
6
7
|
import { settings } from '../model/settings';
|
|
7
|
-
import { connectedGeometry, curveInfo, deleteSel,
|
|
8
|
-
import { page, saveSoon, SK } from '../model/store';
|
|
9
|
-
import type { Constraint, ConsType, Sketch } from '../model/types';
|
|
8
|
+
import { connectedGeometry, curveInfo, deleteSel, joinPoints } from '../model/sketch';
|
|
9
|
+
import { page, planSK, replaceSK, saveSoon, SK } from '../model/store';
|
|
10
|
+
import type { Constraint, ConsType, OpeningEdge, Sketch, Underlay, WallFace } from '../model/types';
|
|
10
11
|
import { fitView, requestDraw } from '../render/view';
|
|
11
12
|
import { convertNote } from '../tools/annotate';
|
|
12
|
-
import {
|
|
13
|
+
import { cornerAngle, dimCurve, dimEdges, dimLine, dimPointLine, dimPoints, dimTwoLines, editDim, faceChoices, onChoice, setDimFace, toggleRef } from '../tools/dim';
|
|
14
|
+
import { edgeLabel, edgeOf } from '../model/planGeom';
|
|
13
15
|
import type { LineTool } from '../tools/line';
|
|
14
16
|
import { offsetPrompt, splitLine } from '../tools/modify';
|
|
17
|
+
import { filletable, filletPrompt } from '../tools/fillet';
|
|
18
|
+
import { chamferPrompt } from '../tools/chamfer';
|
|
15
19
|
import { addOpeningDim, editOpeningDim, toggleOpeningDim } from '../tools/openings';
|
|
16
20
|
import { setTool, tools } from '../tools/registry';
|
|
17
21
|
import { fmtLen } from '../units';
|
|
18
|
-
import { openElevation } from './elevation';
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
22
|
+
import { elevOpen, fitState, OPENING_SIZES, openElevation, openingLabel, overflow, restoreFit, TOO_SHORT, wallAction, wallBar, wallDelete, wallPlus } from './elevation';
|
|
23
|
+
import { pushedOut } from '../model/facesolve';
|
|
24
|
+
import { editRoomName, openKeypad, openPalette, toast, toggleImageOpacity, toggleImageRotation, toggleTileSize, updateCtx, updateTopBar } from './ports';
|
|
25
|
+
import { cornerLines, edgePair, selInfo } from './selection';
|
|
26
|
+
import { restackImage } from './project';
|
|
27
|
+
import { imageById } from '../model/images';
|
|
28
|
+
import { canOpenLink, canSaveDim, linkSizeVisible, openDimLink, openSelectedLink, saveDimToGroup, selectedLink, toggleLeader, unlinkDim } from './linkActions';
|
|
21
29
|
|
|
22
30
|
/*
|
|
23
31
|
The contextual bottom bar's decisions: which actions fit the tool and the selection, what the yellow + measures,
|
|
@@ -32,7 +40,7 @@ function lockTargets(): string[] {
|
|
|
32
40
|
const sk = SK(), s = selInfo(), ids = new Set(s.points);
|
|
33
41
|
s.lines.forEach(id => { ids.add(sk.lines[id].a); ids.add(sk.lines[id].b); });
|
|
34
42
|
s.curves.forEach(id => { const ci = curveInfo(sk, id); if (ci) ids.add(ci.o.c); });
|
|
35
|
-
return [...ids].filter(id => sk.points[id] && !sk.points[id].origin);
|
|
43
|
+
return [...ids].filter(id => sk.points[id] && !sk.points[id].origin && !sk.points[id].anchor);
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
/**
|
|
@@ -44,7 +52,7 @@ function arrangeLock(items: CtxEntry[]): CtxEntry[] {
|
|
|
44
52
|
if (i < 0) return items;
|
|
45
53
|
const it = items[i] as CtxItem;
|
|
46
54
|
const sk = SK();
|
|
47
|
-
const locked = it.act === 'imgLock' ?
|
|
55
|
+
const locked = it.act === 'imgLock' ? selImages().length > 0 && selImages().every(u => u.locked) : (() => { const l = lockTargets(); return l.length > 0 && l.every(id => sk.points[id].fixed); })();
|
|
48
56
|
Object.assign(it, { ic: locked ? 'lock' : 'unlock', label: locked ? 'Locked' : 'Lock', on: undefined, locked });
|
|
49
57
|
const rest = items.filter((_, k) => k !== i);
|
|
50
58
|
const out: CtxEntry[] = [it];
|
|
@@ -54,52 +62,120 @@ function arrangeLock(items: CtxEntry[]): CtxEntry[] {
|
|
|
54
62
|
while (out[out.length - 1] === 'sep') out.pop();
|
|
55
63
|
return out;
|
|
56
64
|
}
|
|
65
|
+
/** What the bar calls one end of a door or window. */
|
|
66
|
+
function anchorLabel(id: string): string {
|
|
67
|
+
return (planSK().openings[id.split('|')[0]]?.type === 'door' ? 'Door' : 'Window') + ' edge';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The selected images. */
|
|
71
|
+
const selImages = (): Underlay[] => ed.sel.filter(x => x.type === 'underlay').map(x => imageById(x.id)).filter((u): u is Underlay => !!u);
|
|
72
|
+
|
|
57
73
|
export type CtxEntry = CtxItem | 'sep' | { lbl: string };
|
|
58
74
|
|
|
75
|
+
/** The tools that place a door, window or cabinet (in the plan or the wall view), which select what they've placed. */
|
|
76
|
+
const PLACING = new Set(['door', 'window', 'wdoor', 'wwindow', 'cabinet']);
|
|
77
|
+
|
|
59
78
|
function ctxItems(): CtxEntry[] {
|
|
60
79
|
const items: CtxEntry[] = [], sk = SK(), s = selInfo();
|
|
61
80
|
const add = (ic: string, label: string, act: string, o: Partial<CtxItem> = {}) => items.push(Object.assign({ ic, label, act }, o));
|
|
62
81
|
/** To wall (all the selected lines and curves) — or To line + Thickness when they're all walls already. */
|
|
63
82
|
const wallItems = () => {
|
|
83
|
+
if (elevOpen()) return;
|
|
64
84
|
const kinds = [...s.lines.map(id => sk.lines[id].kind), ...s.curves.map(id => curveInfo(sk, id)?.o.kind)];
|
|
65
85
|
if (!kinds.length) return;
|
|
66
86
|
if (kinds.every(k => k === 'wall')) { add('thick', 'Thickness', 'thick'); add('line', 'To line', 'kind'); }
|
|
67
87
|
else add('wall', 'To wall', 'kind');
|
|
68
88
|
};
|
|
69
89
|
if (ed.mode === 'annotate') {
|
|
70
|
-
|
|
71
|
-
|
|
90
|
+
const ims = selImages();
|
|
91
|
+
if (ims.length && ims.length === s.n) {
|
|
92
|
+
// an image (or several): lock, its own scale, and which goes in front where they overlap
|
|
93
|
+
// (showing and hiding them all is the Layers menu's)
|
|
72
94
|
add('lock', 'Lock', 'imgLock');
|
|
73
|
-
if (!locked) add('calib', 'Set scale', 'calibtool');
|
|
74
|
-
add('
|
|
95
|
+
if (ims.length === 1 && !ims[0].locked) add('calib', 'Set scale', 'calibtool');
|
|
96
|
+
if (ims.length === 1) add('opacity', 'Opacity', 'imgOpacity');
|
|
97
|
+
// (turning moves it: not while it's locked, as it can't be moved or sized then)
|
|
98
|
+
if (ims.length === 1 && !ims[0].locked) add('rotate', 'Rotate', 'imgRotate');
|
|
99
|
+
if (ims.length === 1 && page().underlays.length > 1) {
|
|
100
|
+
const at = page().underlays.indexOf(ims[0]), last = page().underlays.length - 1;
|
|
101
|
+
if (at < last) add('tofront', 'To front', 'imgFront');
|
|
102
|
+
if (at > 0) add('toback', 'To back', 'imgBack');
|
|
103
|
+
}
|
|
104
|
+
return items;
|
|
105
|
+
}
|
|
106
|
+
const lk = selectedLink();
|
|
107
|
+
if (lk) {
|
|
108
|
+
// 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
|
+
if (canOpenLink(lk.link)) add('open', 'Open', 'linkOpen');
|
|
110
|
+
add('leader', 'Leader', 'linkLeader', { on: !!lk.leader });
|
|
111
|
+
// (its leader's colour, as all markup's, is the rail's colour dot's)
|
|
112
|
+
// (every tile on the page: a slider above the bar)
|
|
113
|
+
add('tilesize', 'Size', 'linkSize');
|
|
75
114
|
return items;
|
|
76
115
|
}
|
|
77
116
|
if (s.notes.length) {
|
|
117
|
+
// (no Color: the rail's colour dot styles what's selected)
|
|
78
118
|
const n = sk.notes.find(n => n.id === s.notes[0]);
|
|
79
|
-
add('color', 'Color', 'palette');
|
|
80
119
|
if (n && n.type === 'pen' && s.notes.length === 1) add('convert', 'To lines', 'convert');
|
|
81
|
-
|
|
82
|
-
add('color', 'Color', 'palette');
|
|
83
|
-
if (page().underlay) { add('calib', 'Scale', 'calibtool'); add('eye', settings.layers.underlay ? 'Hide image' : 'Show image', 'toggleUnderlay'); }
|
|
120
|
+
if (linkSizeVisible()) add('tilesize', 'Size', 'linkSize');
|
|
84
121
|
}
|
|
122
|
+
// nothing selected: no bar (the markup colour is the rail's colour dot, for what's selected too; the image's own
|
|
123
|
+
// options come with selecting it)
|
|
85
124
|
return items;
|
|
86
125
|
}
|
|
87
|
-
if (ed.tool === 'line'
|
|
126
|
+
if (ed.tool === 'line') {
|
|
88
127
|
const st = (tools[ed.tool] as LineTool).st;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (st.chain.length >= 3) add('closeShape', 'Close', 'closeShape');
|
|
92
|
-
add('finish', 'Finish', 'finish');
|
|
93
|
-
return items;
|
|
94
|
-
}
|
|
128
|
+
// drawing: no bar (tap the last point again to finish, the first to close the shape; Esc or Enter on a keyboard)
|
|
129
|
+
if (st.start) return items;
|
|
95
130
|
}
|
|
96
131
|
if (ed.tool === 'dim') { add('dim', 'Driving', 'dimDrive', { on: ed.dimLayer === 'drive' }); add('ref', 'Print', 'dimPrint', { on: ed.dimLayer === 'print' }); return items; }
|
|
97
|
-
// nothing selected: the bar stays empty (Fit and Icons live in the top bar; grid snap and auto-constrain in Settings)
|
|
98
|
-
|
|
132
|
+
// nothing selected: the bar stays empty (Fit and Icons live in the top bar; grid snap and auto-constrain in Settings).
|
|
133
|
+
// Just placed a door, window or cabinet (its tool still on): its bar, for its sizes
|
|
134
|
+
if (!s.n || (ed.tool !== 'select' && !PLACING.has(ed.tool))) return items;
|
|
135
|
+
// the wall view's own things: a door or window, a cabinet, one of its dimension chips, the plan's edges
|
|
136
|
+
const wb = wallBar();
|
|
137
|
+
if (wb) {
|
|
138
|
+
if (wb.label) items.push({ lbl: wb.label });
|
|
139
|
+
for (const it of wb.items) add(it.ic, it.label, it.act);
|
|
140
|
+
return items;
|
|
141
|
+
}
|
|
142
|
+
// on a wall face, walls don't exist: no To wall / Thickness
|
|
143
|
+
const wall = elevOpen();
|
|
99
144
|
const P = s.points.length, L = s.lines.length, C = s.curves.length;
|
|
145
|
+
// Guide (dashed) is for lines: a wall is always solid (one that's a guide from before can still go back to Solid)
|
|
146
|
+
const allWalls = L + C > 0 && s.lines.every(id => sk.lines[id].kind === 'wall' && !sk.lines[id].constr) && s.curves.every(id => { const o = curveInfo(sk, id)?.o; return o?.kind === 'wall' && !o.constr; });
|
|
147
|
+
const guide = () => { if (!allWalls) add('constr', 'Guide', 'constr'); };
|
|
148
|
+
// a wall's edge: named, with its wall view; with another edge, a line or a point, the distance between them (two
|
|
149
|
+
// edges of one wall: its thickness)
|
|
150
|
+
if (s.wfaces.length) {
|
|
151
|
+
// (a curved wall has no wall view: just its name)
|
|
152
|
+
if (s.n === 1) {
|
|
153
|
+
const [id, side] = edgeOf(s.wfaces[0]);
|
|
154
|
+
items.push({ lbl: edgeLabel(sk, id, side) });
|
|
155
|
+
if (sk.lines[id]) add('elev', 'Elevation', 'elev');
|
|
156
|
+
}
|
|
157
|
+
else if (edgePair(s)) {
|
|
158
|
+
const one = s.wfaces.length === 2 && edgeOf(s.wfaces[0])[0] === edgeOf(s.wfaces[1])[0];
|
|
159
|
+
add(one ? 'thick' : 'dim', one ? 'Thickness' : 'Distance', 'edgeDist');
|
|
160
|
+
}
|
|
161
|
+
return items;
|
|
162
|
+
}
|
|
100
163
|
if (s.n === 1 && s.dims.length) {
|
|
101
164
|
const c = sk.cons[s.dims[0]];
|
|
102
|
-
|
|
165
|
+
// linked to a measurement of the job: opening it, letting go of it — no text saying what it's linked to (the user's
|
|
166
|
+
// call, 2026-10-21; its chip is drawn linked), and no Reference (a measurement drives its line, or it isn't linked)
|
|
167
|
+
if (c.measurementRef) {
|
|
168
|
+
const t = c.measurementRef;
|
|
169
|
+
if (canOpenLink(t)) add('open', 'Open', 'dimLinkOpen');
|
|
170
|
+
add('unlink', 'Unlink', 'dimUnlink');
|
|
171
|
+
} else if (canSaveDim(s.dims[0])) add('savegroup', 'Save to group', 'dimSave');
|
|
172
|
+
// Reference frees what a dimension holds — a distance, say; not a cabinet's own width or height, which it keeps
|
|
173
|
+
const cabSize = Object.values((sk as WallFace).cabs ?? {}).some(cb => cb.w === s.dims[0] || cb.h === s.dims[0]);
|
|
174
|
+
if (c.layer !== 'print' && !cabSize && !c.measurementRef) add('ref', c.driving === false ? 'Drive' : 'Reference', 'toggleref');
|
|
175
|
+
// measured on walls: which of their faces (the drawing stays as it is; the number becomes what it measures there)
|
|
176
|
+
const fc = faceChoices(sk, c);
|
|
177
|
+
if (fc.length && items.length) items.push('sep');
|
|
178
|
+
fc.forEach((ch, i) => add(ch.icon!, ch.short!, 'dimFace' + i, { on: onChoice(c, ch) }));
|
|
103
179
|
return items;
|
|
104
180
|
}
|
|
105
181
|
if (s.n === 1 && s.odims.length) {
|
|
@@ -110,26 +186,44 @@ function ctxItems(): CtxEntry[] {
|
|
|
110
186
|
// a constraint badge: nothing to offer but delete (the trash), so the bar stays empty
|
|
111
187
|
if (s.n === 1 && s.cons.length) return items;
|
|
112
188
|
if (s.n === 1 && s.rooms.length) { add('rename', 'Rename', 'renameRoom'); return items; }
|
|
189
|
+
// one end of a door or window: named; with another point, end or door/window, the distance between them
|
|
190
|
+
if (s.oanchors.length && s.n === 1) { items.push({ lbl: anchorLabel(s.oanchors[0]) }); return items; }
|
|
191
|
+
if (s.oanchors.length && s.n === 2 && s.oanchors.length + s.points.length + s.openings.length === 2) { add('dim', 'Distance', 'anchorDist'); return items; }
|
|
113
192
|
// two doors/windows, or one and a point: the distance between them (locks the second one picked)
|
|
114
193
|
if (s.n === 2 && (s.openings.length === 2 || (s.openings.length === 1 && s.points.length === 1))) { add('dim', 'Distance', 'opDist'); return items; }
|
|
115
194
|
if (s.n === 1 && s.openings.length) {
|
|
116
|
-
//
|
|
117
|
-
|
|
195
|
+
// its sizes by name, as in the wall view (no +: it isn't one dimension); a door's swing too. (The type is fixed
|
|
196
|
+
// once placed: Door and Window are separate tools.)
|
|
197
|
+
const o = sk.openings[s.openings[0]];
|
|
198
|
+
items.push({ lbl: openingLabel(o) });
|
|
199
|
+
for (const it of OPENING_SIZES) add(it.ic, it.label, it.act);
|
|
200
|
+
if (o.type === 'door') { items.push('sep'); add('flip', 'Flip', 'opFlip'); }
|
|
118
201
|
return items;
|
|
119
202
|
}
|
|
120
203
|
if (L === 1 && s.n === 1) {
|
|
121
204
|
const l = sk.lines[s.lines[0]];
|
|
122
|
-
add('h', 'Horizontal', 'h', { key: '⇧H' }); add('v', 'Vertical', 'v', { key: '⇧V' });
|
|
205
|
+
add('h', 'Horizontal', 'h', { key: '⇧H' }); add('v', 'Vertical', 'v', { key: '⇧V' }); // (its length: the yellow +)
|
|
123
206
|
items.push('sep');
|
|
124
|
-
add('lock', 'Lock', 'lock', { key: '⇧F' }); add('split', 'Split', 'split'); add('offset', 'Offset', 'offset');
|
|
125
|
-
if (
|
|
126
|
-
|
|
207
|
+
add('lock', 'Lock', 'lock', { key: '⇧F' }); add('split', 'Split', 'split'); add('offset', 'Offset', 'offset'); if (l.constr) add('constr', 'Solid', 'constr'); else guide();
|
|
208
|
+
if (!wall) {
|
|
209
|
+
if (l.kind === 'wall') { add('thick', 'Thickness', 'thick'); add('line', 'To line', 'kind'); } else add('wall', 'To wall', 'kind');
|
|
210
|
+
if (l.kind === 'wall' && !l.constr) { items.push('sep'); add('elev', 'Elevation', 'elev'); }
|
|
211
|
+
}
|
|
127
212
|
return items;
|
|
128
213
|
}
|
|
129
|
-
if (L === 2 && s.n === 2) {
|
|
214
|
+
if (L === 2 && s.n === 2) {
|
|
215
|
+
// (no Angle here: the yellow + is the angle between them — or, parallel, the distance)
|
|
216
|
+
add('par', 'Parallel', 'par', { key: '⇧P' }); add('perp', 'Perpendicular', 'perp', { key: '⇧R' }); add('col', 'Collinear', 'col', { key: '⇧L' }); add('eq', 'Equal', 'eq', { key: '⇧E' });
|
|
217
|
+
// two lines that meet: round off (or cut off) the corner between them
|
|
218
|
+
if (filletCorner2()) { add('fillet', 'Fillet', 'fillet'); add('chamfer', 'Chamfer', 'chamfer'); }
|
|
219
|
+
wallItems();
|
|
220
|
+
return items;
|
|
221
|
+
}
|
|
222
|
+
// a cabinet's corner: nothing of its own (pick another point or line with it to dimension or constrain)
|
|
223
|
+
if (P === 1 && s.n === 1 && cabOfPoint(sk, s.points[0])) return items;
|
|
130
224
|
if (P === 1 && s.n === 1) {
|
|
131
|
-
// a corner where exactly two lines meet:
|
|
132
|
-
if (
|
|
225
|
+
// a corner where exactly two lines meet: rounding it off or cutting it off (its angle is the yellow +)
|
|
226
|
+
if (filletable(sk, s.points[0])) { add('fillet', 'Fillet', 'fillet'); add('chamfer', 'Chamfer', 'chamfer'); }
|
|
133
227
|
add('lock', 'Lock', 'lock', { key: '⇧F' });
|
|
134
228
|
return items;
|
|
135
229
|
}
|
|
@@ -140,7 +234,8 @@ function ctxItems(): CtxEntry[] {
|
|
|
140
234
|
// a circle or arc: the yellow + sets its diameter
|
|
141
235
|
if (C === 1 && s.n === 1) {
|
|
142
236
|
const ci = curveInfo(sk, s.curves[0]);
|
|
143
|
-
|
|
237
|
+
guide();
|
|
238
|
+
if (wall) return items;
|
|
144
239
|
if (ci && ci.o.kind === 'wall') { add('thick', 'Thickness', 'thick'); add('arc', 'To line', 'kind'); } else add('wall', 'To wall', 'kind');
|
|
145
240
|
return items;
|
|
146
241
|
}
|
|
@@ -150,33 +245,87 @@ function ctxItems(): CtxEntry[] {
|
|
|
150
245
|
add('h', 'Horizontal', 'h', { key: '⇧H' }); add('v', 'Vertical', 'v', { key: '⇧V' });
|
|
151
246
|
add('par', 'Parallel', 'par', { key: '⇧P' }); add('col', 'Collinear', 'col', { key: '⇧L' }); add('eq', 'Equal', 'eq', { key: '⇧E' });
|
|
152
247
|
items.push('sep');
|
|
153
|
-
add('
|
|
248
|
+
add('lock', 'Lock', 'lock'); guide(); wallItems();
|
|
154
249
|
return items;
|
|
155
250
|
}
|
|
156
|
-
if (L >= 2) { add('eq', 'Equal', 'eqall'); add('
|
|
251
|
+
if (L >= 2) { add('eq', 'Equal', 'eqall'); add('lock', 'Lock', 'lock'); guide(); wallItems(); return items; }
|
|
157
252
|
add('lock', 'Lock', 'lock');
|
|
158
253
|
return items;
|
|
159
254
|
}
|
|
160
255
|
|
|
256
|
+
/** The keypad for walls' thickness, finished face to face. */
|
|
257
|
+
function thicknessPrompt(walls: { thick?: number }[]): void {
|
|
258
|
+
const sk = SK();
|
|
259
|
+
if (!walls.length) return;
|
|
260
|
+
openKeypad({
|
|
261
|
+
title: 'Wall thickness', sub: 'Finished face to face', initial: fmtLen(walls[0].thick || 4.5), selectAll: true,
|
|
262
|
+
onDone: v => {
|
|
263
|
+
if (v <= 0) return;
|
|
264
|
+
// a thicker wall's faces are shorter (its corners come in): not shorter than what's on them — and what's measured
|
|
265
|
+
// on walls' faces stays as measured, the centrelines moving to keep it
|
|
266
|
+
const was = overflow(sk), held = fitState(sk), had = walls.map(w => w.thick);
|
|
267
|
+
pushHistory();
|
|
268
|
+
walls.forEach(w => (w.thick = v));
|
|
269
|
+
const faces = holdFaces();
|
|
270
|
+
if (!faces || pushedOut(was, overflow(sk))) {
|
|
271
|
+
walls.forEach((w, i) => (w.thick = had[i])); restoreFit(sk, held); dropHistory();
|
|
272
|
+
toast(faces ? TOO_SHORT : 'The dimensions measured on walls’ faces can’t hold at that thickness.', { warn: true });
|
|
273
|
+
}
|
|
274
|
+
afterEdit();
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Two selected lines that meet at a corner of just those two: that corner (to round it off). */
|
|
280
|
+
function filletCorner2(): string | null {
|
|
281
|
+
const sk = SK(), s = selInfo();
|
|
282
|
+
if (s.lines.length !== 2 || s.n !== 2) return null;
|
|
283
|
+
const [a, b] = s.lines.map(id => sk.lines[id]);
|
|
284
|
+
const p = [a.a, a.b].find(x => x === b.a || x === b.b);
|
|
285
|
+
return p && filletable(sk, p) ? p : null;
|
|
286
|
+
}
|
|
287
|
+
|
|
161
288
|
/** Buttons for the bottom bar: they adapt to the tool and the selection (Lock first, when offered). */
|
|
162
289
|
export const barItems = (): CtxEntry[] => arrangeLock(ctxItems());
|
|
163
290
|
|
|
164
291
|
/** The yellow + only appears for something it can measure: not for constraint badges or spaces alone. */
|
|
292
|
+
const DRAW_TOOLS = new Set(['line', 'freehand', 'rect', 'circle', 'arc']);
|
|
293
|
+
|
|
165
294
|
export function plusVisible(): boolean {
|
|
295
|
+
// markup isn't measured: Annotate has no + (notes are the rail's Text tool)
|
|
296
|
+
if (ed.mode === 'annotate') return false;
|
|
297
|
+
const wb = wallBar();
|
|
298
|
+
if (wb) return wb.plus;
|
|
299
|
+
// a shape just drawn (Rectangle, Circle, Arc keep their tool, the shape selected) gets no + of its own;
|
|
300
|
+
// a door or window just placed does (its width)
|
|
301
|
+
if (ed.mode === 'draft' && DRAW_TOOLS.has(ed.tool)) return false;
|
|
302
|
+
// one end of a door or window, alone: nothing to measure yet
|
|
303
|
+
if (ed.sel.length === 1 && ed.sel[0].type === 'oanchor') return false;
|
|
304
|
+
// one door or window picked as a whole: its sizes are in the bar, by name — no single dimension for a +
|
|
305
|
+
// (two, or one and a point: the + is the distance between them)
|
|
306
|
+
if (ed.sel.length === 1 && ed.sel[0].type === 'opening') return false;
|
|
166
307
|
return ed.sel.length > 0 && !ed.sel.every(x => x.type === 'cons') && !ed.sel.every(x => x.type === 'room');
|
|
167
308
|
}
|
|
168
309
|
|
|
169
|
-
export const plusLabel = (): string =>
|
|
310
|
+
export const plusLabel = (): string => 'Add a measurement';
|
|
170
311
|
|
|
171
|
-
/** The yellow +:
|
|
312
|
+
/** The yellow +: the measurement that fits the selection (opens the keypad). */
|
|
172
313
|
export function plusAction(): void {
|
|
173
|
-
if (ed.mode === 'annotate')
|
|
314
|
+
if (ed.mode === 'annotate') return;
|
|
315
|
+
if (wallPlus()) return;
|
|
174
316
|
const s = selInfo(), sk = SK();
|
|
175
|
-
if (s.dims.length === 1 && s.n === 1) { if (sk.cons[s.dims[0]]?.layer === 'print') toast('Print dimensions are labels only — drag to move them.'); else editDim(s.dims[0]); }
|
|
317
|
+
if (s.dims.length === 1 && s.n === 1) { if (sk.cons[s.dims[0]]?.layer === 'print') toast('Print dimensions are labels only — drag to move them.', { warn: true }); else editDim(s.dims[0]); }
|
|
176
318
|
else if (s.odims.length === 1 && s.n === 1) { const [op, part] = s.odims[0].split('|'); editOpeningDim(op, part as OpeningDimPart); }
|
|
319
|
+
// a wall's edge: its length on that face; with another edge, a line or a point, the distance from it
|
|
320
|
+
// (a curved wall's: its diameter across that face)
|
|
321
|
+
else if (s.wfaces.length === 1 && s.n === 1) { const [id, side] = edgeOf(s.wfaces[0]); if (sk.lines[id]) dimLine(id, false, side); else dimCurve(id, side); }
|
|
322
|
+
else if (edgePair(s)) applyCtxAction('edgeDist');
|
|
177
323
|
else if (s.lines.length === 1 && s.n === 1) dimLine(s.lines[0], false);
|
|
178
324
|
else if (s.curves.length === 1 && s.n === 1) dimCurve(s.curves[0]);
|
|
179
325
|
else if (s.openings.length === 1 && s.n === 1) editOpeningDim(s.openings[0], 'w');
|
|
326
|
+
// a corner where two lines meet: its angle
|
|
327
|
+
else if (s.points.length === 1 && s.n === 1 && cornerLines(sk, s.points[0])) applyCtxAction('cornerAngle');
|
|
328
|
+
else if (s.n === 2 && s.oanchors.length) applyCtxAction('anchorDist');
|
|
180
329
|
else if (s.n === 2 && (s.openings.length === 2 || (s.openings.length === 1 && s.points.length === 1))) applyCtxAction('opDist');
|
|
181
330
|
// two things selected: the dimension between them
|
|
182
331
|
else if (s.n === 2 && s.lines.length === 2) dimTwoLines(s.lines[0], s.lines[1]);
|
|
@@ -187,18 +336,20 @@ export function plusAction(): void {
|
|
|
187
336
|
|
|
188
337
|
export function deleteSelection(): void {
|
|
189
338
|
if (!ed.sel.length) return;
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const
|
|
339
|
+
if (wallDelete()) return;
|
|
340
|
+
// (a wall's edge picked: the wall)
|
|
341
|
+
const sk = SK(), wallType = (id: string) => (sk.lines[id] ? 'line' as const : sk.circles[id] ? 'circle' as const : 'arc' as const);
|
|
342
|
+
const items = ed.sel.map(x => (x.type === 'wface' ? { type: wallType(edgeOf(x.id)[0]), id: edgeOf(x.id)[0] } : x)).filter((x, i, a) => a.findIndex(y => y.type === x.type && y.id === x.id) === i);
|
|
343
|
+
if (items.length === 1 && items.some(s => s.type === 'point' && sk.points[s.id] && sk.points[s.id].origin)) { toast('The origin can’t be deleted.', { warn: true }); return; }
|
|
344
|
+
// images are part of the page's undo history, so they go in the same step as the rest
|
|
345
|
+
const imgIds = new Set(items.filter(s => s.type === 'underlay').map(s => s.id));
|
|
194
346
|
const rest = items.filter(s => s.type !== 'underlay');
|
|
195
|
-
|
|
196
|
-
if (
|
|
347
|
+
pushHistory();
|
|
348
|
+
if (imgIds.size) page().underlays = page().underlays.filter(u => !imgIds.has(u.id));
|
|
349
|
+
if (rest.length) deleteSel(sk, rest);
|
|
197
350
|
ed.sel = [];
|
|
198
351
|
afterEdit();
|
|
199
|
-
toast(
|
|
200
|
-
action: { label: 'Undo', fn: () => { if (rest.length) undo(); if (removed && !page().underlay) { page().underlay = removed; afterEdit(); } } },
|
|
201
|
-
});
|
|
352
|
+
toast(imgIds.size && !rest.length ? (imgIds.size > 1 ? 'Images removed' : 'Image removed') : 'Deleted', { action: { label: 'Undo', fn: () => undo() } });
|
|
202
353
|
}
|
|
203
354
|
|
|
204
355
|
function consFromSel(type: ConsType): Constraint | null {
|
|
@@ -255,7 +406,7 @@ function applyMulti(type: ConsType, list: Constraint[]): void {
|
|
|
255
406
|
if (r.ok) added++;
|
|
256
407
|
else if (r.conflict) failed.push(c);
|
|
257
408
|
}
|
|
258
|
-
if (!added && !failed.length) { dropHistory(); toast('They’re already ' + ADJ[type] + '.'); afterEdit(); return; }
|
|
409
|
+
if (!added && !failed.length) { dropHistory(); toast('They’re already ' + ADJ[type] + '.', { warn: true }); afterEdit(); return; }
|
|
259
410
|
if (!added) dropHistory();
|
|
260
411
|
// every selected line that now satisfies it (some may already have, through other constraints)
|
|
261
412
|
const lines = (type === 'h' || type === 'v' ? list.length : list.length + 1) - failed.length;
|
|
@@ -272,7 +423,7 @@ function applyCons(type: ConsType): void {
|
|
|
272
423
|
const multi = multiLineCons(type);
|
|
273
424
|
if (multi && (multi.length > 1 || selInfo().lines.length > 2)) { applyMulti(type, multi); return; }
|
|
274
425
|
const c = consFromSel(type);
|
|
275
|
-
if (!c) { toast('Select ' + (NEEDS[type] || 'geometry') + ' first.'); return; }
|
|
426
|
+
if (!c) { toast('Select ' + (NEEDS[type] || 'geometry') + ' first.', { warn: true }); return; }
|
|
276
427
|
pushHistory();
|
|
277
428
|
const r = tryAddConstraint(c, { onResolved: () => afterEdit() });
|
|
278
429
|
if (!r.ok) dropHistory();
|
|
@@ -282,10 +433,21 @@ function applyCons(type: ConsType): void {
|
|
|
282
433
|
|
|
283
434
|
/** Run a bottom-bar action. `anchor` is the button pressed, for actions that open a popover next to it. */
|
|
284
435
|
export function applyCtxAction(act: string, anchor?: unknown): void {
|
|
436
|
+
if (wallAction(act)) return;
|
|
285
437
|
const sk = SK(), s = selInfo();
|
|
286
438
|
switch (act) {
|
|
287
439
|
case 'fit': fitView(); return;
|
|
288
|
-
case 'elev':
|
|
440
|
+
case 'elev':
|
|
441
|
+
if (s.lines.length === 1) openElevation(s.lines[0]);
|
|
442
|
+
// (an edge: seen from that face)
|
|
443
|
+
else if (s.wfaces.length === 1 && sk.lines[edgeOf(s.wfaces[0])[0]]) openElevation(...edgeOf(s.wfaces[0]));
|
|
444
|
+
return;
|
|
445
|
+
case 'edgeDist': dimEdges(ed.sel, id => thicknessPrompt([sk.lines[id] ?? sk.circles[id] ?? sk.arcs[id]])); return;
|
|
446
|
+
case 'dimFace0': case 'dimFace1': case 'dimFace2': {
|
|
447
|
+
const ch = s.dims.length === 1 && sk.cons[s.dims[0]] ? faceChoices(sk, sk.cons[s.dims[0]])[+act.slice(7)] : undefined;
|
|
448
|
+
if (ch) setDimFace(s.dims[0], ch);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
289
451
|
case 'grid': settings.snapGrid = !settings.snapGrid; break;
|
|
290
452
|
case 'autocons':
|
|
291
453
|
settings.autoCons = !settings.autoCons;
|
|
@@ -299,25 +461,25 @@ export function applyCtxAction(act: string, anchor?: unknown): void {
|
|
|
299
461
|
case 'dimPrint': ed.dimLayer = 'print'; break;
|
|
300
462
|
case 'h': case 'v': case 'par': case 'perp': case 'col': case 'eq': case 'mid': case 'online': case 'oncircle': case 'tan': case 'eqr': case 'conc':
|
|
301
463
|
applyCons(act); return;
|
|
302
|
-
case 'len':
|
|
464
|
+
case 'len':
|
|
465
|
+
if (s.lines.length === 1) dimLine(s.lines[0], false);
|
|
466
|
+
else if (s.wfaces.length === 1 && sk.lines[edgeOf(s.wfaces[0])[0]]) { const [lid, side] = edgeOf(s.wfaces[0]); dimLine(lid, false, side); }
|
|
467
|
+
return;
|
|
303
468
|
case 'pdist': if (s.points.length === 2) dimPoints(s.points[0], s.points[1], false); return;
|
|
304
469
|
case 'pldist': if (s.points.length === 1 && s.lines.length === 1) dimPointLine(s.points[0], s.lines[0]); return;
|
|
305
470
|
case 'diam': if (s.curves.length === 1) dimCurve(s.curves[0]); return;
|
|
306
|
-
case '
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
if (!pair) return;
|
|
310
|
-
// edit the corner's existing angle dimension rather than stacking a second one
|
|
311
|
-
const ex = Object.keys(sk.cons).find(id => { const c = sk.cons[id]; return c.type === 'angle' && c.layer !== 'print' && ((c.l1 === pair[0] && c.l2 === pair[1]) || (c.l1 === pair[1] && c.l2 === pair[0])); });
|
|
312
|
-
if (ex) editDim(ex); else dimAngle(pair[0], pair[1]);
|
|
471
|
+
case 'fillet': case 'chamfer': {
|
|
472
|
+
const p = s.points.length === 1 ? s.points[0] : filletCorner2();
|
|
473
|
+
if (p) (act === 'fillet' ? filletPrompt : chamferPrompt)(p);
|
|
313
474
|
return;
|
|
314
475
|
}
|
|
476
|
+
case 'cornerAngle': cornerAngle(s.points[0]); return;
|
|
315
477
|
case 'merge': {
|
|
316
478
|
pushHistory();
|
|
317
479
|
const snap = JSON.stringify(sk);
|
|
318
|
-
if (
|
|
480
|
+
if (joinPoints(sk, s.points[0], s.points[1])) {
|
|
319
481
|
const r = solveSketch();
|
|
320
|
-
if (!r.ok) {
|
|
482
|
+
if (!r.ok) { replaceSK(JSON.parse(snap) as Sketch); dropHistory(); toast('Can’t join those points — it conflicts with other constraints.', { warn: true }); }
|
|
321
483
|
else { ed.sel = [{ type: 'point', id: s.points[0] }]; toast('Points joined'); }
|
|
322
484
|
}
|
|
323
485
|
afterEdit();
|
|
@@ -343,7 +505,7 @@ export function applyCtxAction(act: string, anchor?: unknown): void {
|
|
|
343
505
|
}
|
|
344
506
|
case 'constr':
|
|
345
507
|
pushHistory();
|
|
346
|
-
s.lines.forEach(id => (sk.lines[id].constr = !sk.lines[id].constr)
|
|
508
|
+
s.lines.forEach(id => { if (!sk.lines[id].anchor) sk.lines[id].constr = !sk.lines[id].constr; });
|
|
347
509
|
s.curves.forEach(id => { const ci = curveInfo(sk, id); if (ci) ci.o.constr = !ci.o.constr; });
|
|
348
510
|
afterEdit();
|
|
349
511
|
return;
|
|
@@ -353,24 +515,34 @@ export function applyCtxAction(act: string, anchor?: unknown): void {
|
|
|
353
515
|
if (!targets.length) return;
|
|
354
516
|
const toWall = !targets.every(t => t.kind === 'wall');
|
|
355
517
|
pushHistory();
|
|
356
|
-
targets.forEach(t => { t.kind = toWall ? 'wall' : 'line'; t.thick = t.thick || 4.5; });
|
|
518
|
+
targets.forEach(t => { t.kind = toWall ? 'wall' : 'line'; t.thick = t.thick || 4.5; if (toWall && (t as { constr?: boolean }).constr) (t as { constr?: boolean }).constr = false; });
|
|
519
|
+
// made lines: what was measured on their faces is measured as they are now (nothing moves); the walls beside
|
|
520
|
+
// them keep theirs
|
|
521
|
+
if (!toWall) {
|
|
522
|
+
const ids = new Set([...s.lines, ...s.curves]);
|
|
523
|
+
for (const c of Object.values(sk.cons)) {
|
|
524
|
+
if (c.type === 'dist' && c.face && c.line && ids.has(c.line)) { delete c.face; if (c.driving !== false) c.value = measure(sk, c); }
|
|
525
|
+
if (c.type === 'diam' && c.face && c.c && ids.has(c.c)) { delete c.face; if (c.driving !== false) c.value = measure(sk, c); }
|
|
526
|
+
if (c.type === 'pldist' && c.faces && ((c.l && ids.has(c.l)) || (c.l2 && ids.has(c.l2)))) {
|
|
527
|
+
if (c.driving !== false) c.value = measure(sk, c);
|
|
528
|
+
if (c.faces[0] && c.l && ids.has(c.l)) c.faces = [0, c.faces[1]];
|
|
529
|
+
if (c.faces[1] && c.l2 && ids.has(c.l2)) c.faces = [c.faces[0], 0];
|
|
530
|
+
if (!c.faces[0] && !c.faces[1]) delete c.faces;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
holdFaces();
|
|
357
535
|
afterEdit();
|
|
358
536
|
toast(targets.length > 1 ? (toWall ? targets.length + ' items are now walls' : targets.length + ' items are now lines') : toWall ? 'Now a wall' : 'Now a line');
|
|
359
537
|
return;
|
|
360
538
|
}
|
|
361
|
-
case 'thick':
|
|
539
|
+
case 'thick':
|
|
362
540
|
// straight and curved walls in the selection
|
|
363
|
-
|
|
541
|
+
thicknessPrompt([
|
|
364
542
|
...s.lines.map(id => sk.lines[id]).filter(l => l.kind === 'wall'),
|
|
365
543
|
...s.curves.map(id => curveInfo(sk, id)?.o).filter((o): o is NonNullable<typeof o> => !!o && o.kind === 'wall'),
|
|
366
|
-
];
|
|
367
|
-
if (!walls.length) return;
|
|
368
|
-
openKeypad({
|
|
369
|
-
title: 'Wall thickness', sub: 'Finished face to face', initial: fmtLen(walls[0].thick || 4.5), selectAll: true,
|
|
370
|
-
onDone: v => { if (v <= 0) return; pushHistory(); walls.forEach(w => (w.thick = v)); afterEdit(); },
|
|
371
|
-
});
|
|
544
|
+
]);
|
|
372
545
|
return;
|
|
373
|
-
}
|
|
374
546
|
case 'eqall': {
|
|
375
547
|
// lines mixed with other things: equal across just the lines
|
|
376
548
|
if (s.lines.length < 2) return;
|
|
@@ -395,20 +567,57 @@ export function applyCtxAction(act: string, anchor?: unknown): void {
|
|
|
395
567
|
}
|
|
396
568
|
return;
|
|
397
569
|
}
|
|
570
|
+
case 'anchorDist': {
|
|
571
|
+
// from one end of a door or window to a point, another end, or another door or window (its nearer end): the
|
|
572
|
+
// second one picked is placed from the first (the other way round if that would tie them in a loop)
|
|
573
|
+
const end = (id: string) => { const [op, edge] = id.split('|'); return { op, edge: edge as OpeningEdge }; };
|
|
574
|
+
const [first, second] = ed.sel;
|
|
575
|
+
if (!first || !second) return;
|
|
576
|
+
if (first.type === 'point' || second.type === 'point') {
|
|
577
|
+
const e = end((first.type === 'oanchor' ? first : second).id), pt = (first.type === 'point' ? first : second).id;
|
|
578
|
+
addOpeningDim(e.op, e.edge, { pt });
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
const nearer = (op: string, from: { op: string; edge: OpeningEdge }): OpeningEdge => {
|
|
582
|
+
const p = edgePoint(sk, from.op, from.edge), a = edgePoint(sk, op, 's0'), b = edgePoint(sk, op, 's1');
|
|
583
|
+
return !p || !a || !b || Math.hypot(a.x - p.x, a.y - p.y) <= Math.hypot(b.x - p.x, b.y - p.y) ? 's0' : 's1';
|
|
584
|
+
};
|
|
585
|
+
let a = first.type === 'oanchor' ? end(first.id) : null, b = second.type === 'oanchor' ? end(second.id) : null;
|
|
586
|
+
if (!a) a = { op: first.id, edge: nearer(first.id, b!) };
|
|
587
|
+
if (!b) b = { op: second.id, edge: nearer(second.id, a) };
|
|
588
|
+
if (a.op !== b.op && createsCycle(sk, b.op, { op: a.op, edge: a.edge })) [a, b] = [b, a];
|
|
589
|
+
addOpeningDim(b.op, b.edge, { op: a.op, edge: a.edge });
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
398
592
|
case 'palette': openPalette(anchor); return;
|
|
399
593
|
case 'convert': convertNote(s.notes[0]); return;
|
|
594
|
+
case 'linkOpen': openSelectedLink(); return;
|
|
595
|
+
case 'linkSize': toggleTileSize(anchor); return;
|
|
596
|
+
case 'dimLinkOpen': if (s.dims.length === 1) openDimLink(s.dims[0]); return;
|
|
597
|
+
case 'dimUnlink': if (s.dims.length === 1) unlinkDim(s.dims[0]); return;
|
|
598
|
+
case 'dimSave': if (s.dims.length === 1) void saveDimToGroup(s.dims[0]); return;
|
|
599
|
+
case 'linkLeader': if (s.notes.length === 1) toggleLeader(s.notes[0]); return;
|
|
400
600
|
case 'calibtool': setTool('calib'); return;
|
|
401
601
|
case 'toggleUnderlay':
|
|
402
602
|
settings.layers.underlay = !settings.layers.underlay;
|
|
403
603
|
if (!settings.layers.underlay) ed.sel = ed.sel.filter(x => x.type !== 'underlay');
|
|
404
604
|
break;
|
|
405
605
|
case 'imgLock': {
|
|
406
|
-
const
|
|
407
|
-
if (!
|
|
606
|
+
const ims = selImages();
|
|
607
|
+
if (!ims.length) return;
|
|
608
|
+
const lock = !ims.every(u => u.locked), many = ims.length > 1;
|
|
408
609
|
pushHistory();
|
|
409
|
-
u.locked =
|
|
610
|
+
ims.forEach(u => { u.locked = lock; });
|
|
410
611
|
afterEdit();
|
|
411
|
-
toast(
|
|
612
|
+
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.')
|
|
613
|
+
: many ? 'Images unlocked.' : 'Image unlocked. Drag it, or drag its handles to size it.');
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
case 'imgOpacity': toggleImageOpacity(anchor); return;
|
|
617
|
+
case 'imgRotate': toggleImageRotation(anchor); return;
|
|
618
|
+
case 'imgFront': case 'imgBack': {
|
|
619
|
+
const ims = selImages();
|
|
620
|
+
if (ims.length === 1) restackImage(ims[0].id, act === 'imgFront' ? 'front' : 'back');
|
|
412
621
|
return;
|
|
413
622
|
}
|
|
414
623
|
}
|