@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/ui/menu.ts
CHANGED
|
@@ -7,26 +7,43 @@ import { updateCrumb } from './drawer';
|
|
|
7
7
|
import { closePopovers } from './palette';
|
|
8
8
|
import { applyTheme } from './theme';
|
|
9
9
|
import { disarmReset, exportDxf, exportPdf, openProjectText, resetToDemo, saveProjectFile } from '../editor/files';
|
|
10
|
+
import {
|
|
11
|
+
editDefaultWallHeight, LAYER_ROWS, layerOn, setDefaultWallHeight, toggleLayer,
|
|
12
|
+
WALL_PRESETS, type LayerKey,
|
|
13
|
+
} from '../editor/project';
|
|
14
|
+
import { fmtLen } from '../units';
|
|
10
15
|
import { bindTip } from './tip';
|
|
11
16
|
import { toast } from './toast';
|
|
12
17
|
|
|
18
|
+
/** The open menu: ••• (exports, files and settings) or Layers (the stack). */
|
|
19
|
+
let kind: 'more' | 'layers' = 'more';
|
|
20
|
+
|
|
13
21
|
function renderMenu(): void {
|
|
14
22
|
const m = $('#menu');
|
|
23
|
+
$('#btnMore').setAttribute('aria-expanded', String(kind === 'more'));
|
|
24
|
+
$('#btnLayers').setAttribute('aria-expanded', String(kind === 'layers'));
|
|
25
|
+
m.setAttribute('aria-label', kind === 'layers' ? 'Layers' : 'Settings');
|
|
26
|
+
m.classList.toggle('layers', kind === 'layers');
|
|
27
|
+
if (kind === 'layers') { renderLayers(m); return; }
|
|
15
28
|
const seg = (key: keyof Settings, opts: [string | number, string][]) =>
|
|
16
29
|
`<div class="seg">${opts.map(([v, l]) => `<button data-k="${key}" data-v="${v}" aria-pressed="${String(settings[key]) === String(v)}">${l}</button>`).join('')}</div>`;
|
|
17
30
|
const tg = (key: keyof Settings, label: string) =>
|
|
18
31
|
`<div class="mrow"><span>${label}</span><button class="switch" role="switch" aria-label="${label}" aria-checked="${settings[key]}" data-t="${key}"></button></div>`;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
<button class="mlink" id="mPdf">Export page as PDF (.pdf)</button>
|
|
32
|
+
// the exports first (as in the React editor: there's no share button of its own)
|
|
33
|
+
m.innerHTML = `<button class="mlink" id="mPdf">Export page as PDF (.pdf)</button>
|
|
22
34
|
<button class="mlink" id="mDxf">Export page as DXF (.dxf)</button><hr>
|
|
35
|
+
<button class="mlink" id="mSaveFile">Save project to file…</button>
|
|
36
|
+
<button class="mlink" id="mOpenFile">Open project from file…</button><hr>
|
|
23
37
|
<div class="mrow"><span>Units</span>${seg('unit', [['ftin', 'ft-in'], ['in', 'in'], ['mm', 'mm']])}</div>
|
|
24
38
|
<div class="mrow"><span>Precision</span>${seg('denom', [[16, '1/16"'], [8, '1/8"'], [4, '1/4"']])}</div>
|
|
25
|
-
<div class="mrow"><span>Wall height</span>${
|
|
26
|
-
${tg('snapGrid', 'Snap to grid')}
|
|
39
|
+
<div class="mrow"><span>Wall height</span><div class="seg">${[[96, "8'"], [108, "9'"], [120, "10'"]].map(([v, l]) => `<button data-wall="${v}" aria-pressed="${settings.wallH === v}">${l}</button>`).join('')}<button data-wall="custom" aria-pressed="${!WALL_PRESETS.includes(settings.wallH)}">${WALL_PRESETS.includes(settings.wallH) ? 'Custom' : fmtLen(settings.wallH)}</button></div></div>
|
|
40
|
+
${tg('snapGrid', 'Snap to grid')}
|
|
41
|
+
<div class="mrow"><span>Object snapping</span><button class="switch" role="switch" aria-label="Object snapping" aria-checked="${layerOn('objSnap')}" id="mObjSnap"></button></div>
|
|
42
|
+
${tg('autoCons', 'Auto-constrain while drawing')}
|
|
43
|
+
${tg('toolTips', 'Tool tips')}
|
|
27
44
|
<div class="mrow"><span>Theme</span>${seg('theme', [['system', 'Auto'], ['light', 'Light'], ['dark', 'Dark']])}</div><hr>
|
|
28
45
|
<button class="mlink" id="mKeys">Keyboard shortcuts</button>
|
|
29
|
-
<div class="keys-list" id="keysList"><kbd>V L
|
|
46
|
+
<div class="keys-list" id="keysList"><kbd>V L R</kbd>Select, line, rectangle<br><kbd>S</kbd>Draw to shape (freehand → lines)<br><kbd>C A D</kbd>Circle, arc, dimension<br><kbd>T O M</kbd>Trim, offset, space<br><kbd>B N</kbd>Door, window<br><kbd>⇧H ⇧V</kbd>Horizontal, vertical<br><kbd>⇧P ⇧R ⇧E</kbd>Parallel, perpendicular, equal<br><kbd>⇧L ⇧T</kbd>Collinear, tangent<br><kbd>⇧M ⇧O ⇧F</kbd>Midpoint, on line, lock<br><kbd>W X</kbd>Draw walls, construction lines (on or off)<br><kbd>G F</kbd>Grid snap, fit<br><kbd>Esc</kbd>Cancel the step, then back to Select<br><kbd>Space</kbd>Hold and drag to pan<br><kbd>⇧ drag</kbd>Level, plumb or 45° while drawing or dragging<br><kbd>⌘Z ⇧⌘Z</kbd>Undo, redo<br><kbd>2 / 3 fingers</kbd>Tap to undo / redo</div>
|
|
30
47
|
<button class="mlink" id="mCopy">Copy page data (JSON)</button>
|
|
31
48
|
<button class="mlink" id="mHelp">Show the quick start again</button>
|
|
32
49
|
<button class="mlink danger" id="mReset">Reset to the demo project</button>`;
|
|
@@ -38,10 +55,18 @@ function renderMenu(): void {
|
|
|
38
55
|
afterEdit();
|
|
39
56
|
}));
|
|
40
57
|
m.querySelectorAll<HTMLElement>('[data-t]').forEach(b => (b.onclick = () => {
|
|
41
|
-
const k = b.dataset.t as 'snapGrid' | 'autoCons';
|
|
58
|
+
const k = b.dataset.t as 'snapGrid' | 'autoCons' | 'toolTips';
|
|
42
59
|
settings[k] = !settings[k];
|
|
43
60
|
renderMenu(); updateCtx(); saveSoon();
|
|
44
61
|
}));
|
|
62
|
+
$('#mObjSnap').onclick = () => { toggleLayer('objSnap'); renderMenu(); };
|
|
63
|
+
// Wall height: a preset, or Custom on the keypad
|
|
64
|
+
m.querySelectorAll<HTMLElement>('[data-wall]').forEach(b => (b.onclick = () => {
|
|
65
|
+
if (b.dataset.wall === 'custom') { closeMenu(); editDefaultWallHeight(); return; }
|
|
66
|
+
if (setDefaultWallHeight(+b.dataset.wall!)) renderMenu();
|
|
67
|
+
}));
|
|
68
|
+
$('#mPdf').onclick = () => { closeMenu(); exportPdf(); };
|
|
69
|
+
$('#mDxf').onclick = () => { closeMenu(); exportDxf(); };
|
|
45
70
|
$('#mKeys').onclick = () => $('#keysList').classList.toggle('show');
|
|
46
71
|
$('#mCopy').onclick = async () => {
|
|
47
72
|
try { await navigator.clipboard.writeText(JSON.stringify({ page: page().name, sketch: SK() }, null, 1)); toast('Page data copied'); }
|
|
@@ -49,8 +74,6 @@ function renderMenu(): void {
|
|
|
49
74
|
};
|
|
50
75
|
$('#mSaveFile').onclick = () => { m.classList.remove('show'); saveProjectFile(); };
|
|
51
76
|
$('#mOpenFile').onclick = () => { m.classList.remove('show'); $<HTMLInputElement>('#projFile').click(); };
|
|
52
|
-
$('#mPdf').onclick = () => { m.classList.remove('show'); exportPdf(); };
|
|
53
|
-
$('#mDxf').onclick = () => { m.classList.remove('show'); exportDxf(); };
|
|
54
77
|
$('#mHelp').onclick = () => { m.classList.remove('show'); $('#welcome').hidden = false; };
|
|
55
78
|
$('#mReset').onclick = () => {
|
|
56
79
|
if (!resetToDemo()) { $('#mReset').textContent = 'Tap again to reset — this clears your pages'; return; }
|
|
@@ -59,6 +82,21 @@ function renderMenu(): void {
|
|
|
59
82
|
};
|
|
60
83
|
}
|
|
61
84
|
|
|
85
|
+
/** The Layers menu: the photos and plans under the drawing, then each layer's switch (an image's own lock, scale and opacity are in its bar). */
|
|
86
|
+
function renderLayers(m: HTMLElement): void {
|
|
87
|
+
const ims = page().underlays, n = ims.length;
|
|
88
|
+
const sw = (key: string, label: string, sub: string, val: boolean) =>
|
|
89
|
+
`<div class="mrow"><div>${label}${sub ? `<small>${sub}</small>` : ''}</div><button class="switch" role="switch" aria-label="${label}" aria-checked="${val}" data-sw="${key}"></button></div>`;
|
|
90
|
+
let h = sw('underlay', n > 1 ? 'Photos and plans' : 'Photo or plan underlay', n > 1 ? n + ' images under the drawing' : n ? 'Traced image under the drawing' : 'None on this page', settings.layers.underlay);
|
|
91
|
+
h += '<hr>' + LAYER_ROWS.map(([k, label, sub]) => sw(k, label, sub, layerOn(k))).join('');
|
|
92
|
+
m.innerHTML = h;
|
|
93
|
+
m.querySelectorAll<HTMLElement>('[data-sw]').forEach(b => (b.onclick = () => {
|
|
94
|
+
const k = b.dataset.sw as LayerKey;
|
|
95
|
+
toggleLayer(k);
|
|
96
|
+
b.setAttribute('aria-checked', String(layerOn(k)));
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
|
|
62
100
|
/** Save a file through a temporary download link. */
|
|
63
101
|
export function download(blob: Blob, name: string): void {
|
|
64
102
|
const a = document.createElement('a');
|
|
@@ -79,14 +117,21 @@ export function doSave(): void {
|
|
|
79
117
|
toast(ok ? 'Saved on this device' : 'Saved, but the image was too large to keep offline.', { warn: !ok });
|
|
80
118
|
}
|
|
81
119
|
|
|
120
|
+
function closeMenu(): void {
|
|
121
|
+
$('#menu').classList.remove('show');
|
|
122
|
+
for (const id of ['#btnMore', '#btnLayers']) $(id).setAttribute('aria-expanded', 'false');
|
|
123
|
+
}
|
|
124
|
+
|
|
82
125
|
export function initMenu(): void {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
126
|
+
for (const [id, k] of [['#btnMore', 'more'], ['#btnLayers', 'layers']] as const) {
|
|
127
|
+
$(id).onclick = e => {
|
|
128
|
+
e.stopPropagation();
|
|
129
|
+
const m = $('#menu');
|
|
130
|
+
if (m.classList.contains('show') && kind === k) closeMenu();
|
|
131
|
+
else { kind = k; disarmReset(); renderMenu(); m.classList.add('show'); }
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
document.addEventListener('pointerdown', e => { if (!(e.target as Element).closest('#menu,#btnMore,#btnLayers,#palette,#opacityPop,#rotatePop,.rail,.ctx')) closePopovers(); });
|
|
90
135
|
$('#btnSave').onclick = doSave;
|
|
91
136
|
$<HTMLInputElement>('#projFile').onchange = e => {
|
|
92
137
|
const input = e.target as HTMLInputElement, f = input.files && input.files[0];
|
|
@@ -95,6 +140,7 @@ export function initMenu(): void {
|
|
|
95
140
|
};
|
|
96
141
|
bindTip($('#btnSave'), 'Save', '⌘S');
|
|
97
142
|
bindTip($('#btnLayers'), 'Layers', '');
|
|
98
|
-
bindTip($('#
|
|
143
|
+
bindTip($('#btnProject'), 'Pages and spaces', '');
|
|
144
|
+
bindTip($('#btnMore'), 'Share, settings and more', '');
|
|
99
145
|
bindTip($('#btnPages'), 'Project pages', '');
|
|
100
146
|
}
|
package/src/ui/noteinput.ts
CHANGED
|
@@ -5,15 +5,15 @@ import { onSessionReset } from '../model/session';
|
|
|
5
5
|
let noteCb: ((txt: string) => void) | null = null;
|
|
6
6
|
onSessionReset(() => { noteCb = null; });
|
|
7
7
|
|
|
8
|
-
export function showNoteInput(sx: number, sy: number, cb: (txt: string) => void): void {
|
|
8
|
+
export function showNoteInput(sx: number, sy: number, cb: (txt: string) => void, initial = ''): void {
|
|
9
9
|
const el = $<HTMLInputElement>('#noteIn');
|
|
10
10
|
const r = cvs.cv.getBoundingClientRect();
|
|
11
11
|
el.style.left = r.left + sx + 'px';
|
|
12
12
|
el.style.top = r.top + sy - 18 + 'px';
|
|
13
|
-
el.value =
|
|
13
|
+
el.value = initial;
|
|
14
14
|
el.style.display = 'block';
|
|
15
15
|
noteCb = cb;
|
|
16
|
-
setTimeout(() => el.focus({ preventScroll: true }), 30);
|
|
16
|
+
setTimeout(() => { el.focus({ preventScroll: true }); el.select(); }, 30);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export function hideNoteInput(): void {
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { ed } from '../app/state';
|
|
2
|
+
import { OPACITY_MIN, OPACITY_STEP, ROTATE_MAX, ROTATE_STEP, setUnderlayOpacity, setUnderlayRotation } from '../editor/project';
|
|
3
|
+
import { pushHistory } from '../model/history';
|
|
4
|
+
import { imageById } from '../model/images';
|
|
5
|
+
import { $ } from './dom';
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
The selected image's sliders, just above the bar: its opacity, or how far it's turned (Opacity or Rotate in the bar
|
|
9
|
+
opens and closes each; opening one closes the other).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
type Kind = 'opacity' | 'rotate';
|
|
13
|
+
const fmtDeg = (v: number): string => (Math.round(v * 2) / 2).toString().replace('-', '−') + '°';
|
|
14
|
+
const KINDS: Record<Kind, {
|
|
15
|
+
id: string; label: string; aria: string; min: number; max: number; step: number;
|
|
16
|
+
get: (u: NonNullable<ReturnType<typeof selected>>) => number; set: (id: string, v: number) => void; show: (v: number) => string;
|
|
17
|
+
}> = {
|
|
18
|
+
opacity: {
|
|
19
|
+
id: 'opacityPop', label: 'Opacity', aria: 'Image opacity', min: OPACITY_MIN, max: 100, step: OPACITY_STEP,
|
|
20
|
+
get: u => Math.round((u.opacity ?? 0.6) * 100), set: (id, v) => setUnderlayOpacity(id, v / 100), show: v => v + '%',
|
|
21
|
+
},
|
|
22
|
+
rotate: {
|
|
23
|
+
id: 'rotatePop', label: 'Rotate', aria: 'Image rotation', min: -ROTATE_MAX, max: ROTATE_MAX, step: ROTATE_STEP,
|
|
24
|
+
get: u => u.rot ?? 0, set: setUnderlayRotation, show: fmtDeg,
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function pop(kind: Kind): HTMLElement {
|
|
29
|
+
const k = KINDS[kind];
|
|
30
|
+
let el = document.querySelector<HTMLElement>('#' + k.id);
|
|
31
|
+
if (!el) {
|
|
32
|
+
el = document.createElement('div');
|
|
33
|
+
el.id = k.id;
|
|
34
|
+
el.className = 'opacity-pop';
|
|
35
|
+
el.innerHTML = `<span>${k.label}</span><input type="range" min="${k.min}" max="${k.max}" step="${k.step}" aria-label="${k.aria}"><b></b>`
|
|
36
|
+
+ (kind === 'rotate' ? '<button type="button" class="pop-reset" aria-label="Reset rotation">Reset</button>' : '');
|
|
37
|
+
$('#app').appendChild(el);
|
|
38
|
+
const input = el.querySelector('input')!;
|
|
39
|
+
// Reset: upright again (one undo step)
|
|
40
|
+
el.querySelector<HTMLButtonElement>('.pop-reset')?.addEventListener('click', () => {
|
|
41
|
+
const u = selected();
|
|
42
|
+
if (!u) return;
|
|
43
|
+
pushHistory();
|
|
44
|
+
k.set(u.id, 0);
|
|
45
|
+
input.value = '0';
|
|
46
|
+
el!.querySelector('b')!.textContent = k.show(0);
|
|
47
|
+
upright(el!, 0);
|
|
48
|
+
});
|
|
49
|
+
// one undo step for each drag
|
|
50
|
+
input.addEventListener('pointerdown', () => pushHistory());
|
|
51
|
+
input.oninput = () => {
|
|
52
|
+
const u = selected();
|
|
53
|
+
if (!u) return;
|
|
54
|
+
k.set(u.id, +input.value);
|
|
55
|
+
el!.querySelector('b')!.textContent = k.show(+input.value);
|
|
56
|
+
upright(el!, +input.value);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return el;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Reset has nothing to do once the image is upright: greyed out. */
|
|
63
|
+
const upright = (el: HTMLElement, v: number) => { const b = el.querySelector<HTMLButtonElement>('.pop-reset'); if (b) b.disabled = Math.abs(v) < 1e-9; };
|
|
64
|
+
|
|
65
|
+
const selected = () => (ed.sel.length === 1 && ed.sel[0].type === 'underlay' ? imageById(ed.sel[0].id) : undefined);
|
|
66
|
+
|
|
67
|
+
function toggle(kind: Kind, anchor?: HTMLElement): void {
|
|
68
|
+
const el = pop(kind);
|
|
69
|
+
if (el.classList.contains('show')) { closeOpacity(); return; }
|
|
70
|
+
closeOpacity();
|
|
71
|
+
const u = selected();
|
|
72
|
+
if (!u) return;
|
|
73
|
+
const k = KINDS[kind], v = k.get(u);
|
|
74
|
+
el.querySelector('input')!.value = String(v);
|
|
75
|
+
el.querySelector('b')!.textContent = k.show(v);
|
|
76
|
+
upright(el, v);
|
|
77
|
+
// (a toast would sit over the slider: it's done with once the slider opens)
|
|
78
|
+
document.querySelector('#toast')?.classList.remove('show');
|
|
79
|
+
el.classList.add('show');
|
|
80
|
+
// centred over the bar, just above it
|
|
81
|
+
const bar = (anchor?.closest('.ctx') as HTMLElement | null) ?? document.querySelector<HTMLElement>('.ctx');
|
|
82
|
+
const ar = $('#app').getBoundingClientRect();
|
|
83
|
+
if (bar) {
|
|
84
|
+
const r = bar.getBoundingClientRect();
|
|
85
|
+
el.style.left = Math.max(8, Math.min(ar.width - el.offsetWidth - 8, r.left - ar.left + r.width / 2 - el.offsetWidth / 2)) + 'px';
|
|
86
|
+
el.style.top = r.top - ar.top - el.offsetHeight - 10 + 'px';
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const toggleOpacity = (anchor?: HTMLElement): void => toggle('opacity', anchor);
|
|
91
|
+
export const toggleRotation = (anchor?: HTMLElement): void => toggle('rotate', anchor);
|
|
92
|
+
|
|
93
|
+
/** Close whichever image slider is open; true if one was. */
|
|
94
|
+
export function closeOpacity(): boolean {
|
|
95
|
+
let was = false;
|
|
96
|
+
for (const k of Object.values(KINDS)) {
|
|
97
|
+
const el = document.querySelector<HTMLElement>('#' + k.id);
|
|
98
|
+
if (el && el.classList.contains('show')) { el.classList.remove('show'); was = true; }
|
|
99
|
+
}
|
|
100
|
+
return was;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** They close once the image isn't the one thing selected (and Rotate once it's locked). */
|
|
104
|
+
export function refreshOpacity(): void {
|
|
105
|
+
const u = selected();
|
|
106
|
+
if (!u) { closeOpacity(); return; }
|
|
107
|
+
if (u.locked) document.querySelector('#rotatePop')?.classList.remove('show');
|
|
108
|
+
}
|
package/src/ui/palette.ts
CHANGED
|
@@ -1,29 +1,84 @@
|
|
|
1
1
|
import { ed } from '../app/state';
|
|
2
2
|
import { clamp } from '../model/geom';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
markupTarget, NOTE_CAPS, NOTE_COLORS, NOTE_STYLES, NOTE_WIDTHS, setNoteCap, setNoteColor, setNoteStyle, setNoteWidth,
|
|
5
|
+
} from '../editor/markup';
|
|
6
|
+
import type { NoteStyle } from '../model/markup';
|
|
7
|
+
import type { NoteCap } from '../model/types';
|
|
4
8
|
import { $ } from './dom';
|
|
5
9
|
|
|
10
|
+
const STYLE_LABEL: Record<NoteStyle, string> = { solid: 'Solid', dash: 'Dashed', dot: 'Dotted' };
|
|
11
|
+
const CAP_LABEL: Record<NoteCap, string> = { butt: 'Flat', round: 'Round', arrow: 'Arrow' };
|
|
12
|
+
const stylePreview = (st: NoteStyle) =>
|
|
13
|
+
st === 'solid' ? '<i class="pv-bar"></i>' : '<span class="pv-row">' + ('<i class="pv-' + st + '"></i>').repeat(3) + '</span>';
|
|
14
|
+
const capPreview = (cap: NoteCap, start: boolean) =>
|
|
15
|
+
cap === 'arrow' ? '<span class="pv-row pv-arrow' + (start ? ' start' : '') + '"><i class="pv-shaft"></i><i class="pv-head"></i></span>'
|
|
16
|
+
: '<i class="pv-cap' + (cap === 'round' ? ' round' : '') + '"></i>';
|
|
6
17
|
|
|
18
|
+
let anchorEl: HTMLElement | null = null;
|
|
19
|
+
|
|
20
|
+
/** The markup stroke panel (BOLDR's annotation set): color, width, solid / dashed / dotted, and the ends of a stroke. */
|
|
7
21
|
export function openPalette(anchor: HTMLElement): void {
|
|
8
22
|
const p = $('#palette');
|
|
9
|
-
p.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
b.style.background = c;
|
|
13
|
-
b.setAttribute('aria-label', 'Color ' + c);
|
|
14
|
-
if (c === ed.noteColor) b.className = 'on';
|
|
15
|
-
b.onclick = () => { setNoteColor(c); p.classList.remove('show'); };
|
|
16
|
-
p.appendChild(b);
|
|
17
|
-
}
|
|
23
|
+
if (p.classList.contains('show') && anchorEl === anchor) { p.classList.remove('show'); return; }
|
|
24
|
+
anchorEl = anchor;
|
|
25
|
+
renderPalette();
|
|
18
26
|
const r = anchor.getBoundingClientRect(), ar = $('#app').getBoundingClientRect();
|
|
19
27
|
p.classList.add('show');
|
|
20
28
|
const w = p.offsetWidth;
|
|
21
29
|
if (anchor.closest('.rail')) {
|
|
22
30
|
p.style.left = r.right - ar.left + 10 + 'px';
|
|
23
|
-
p.style.top = r.top - ar.top + r.height / 2 - p.offsetHeight / 2 + 'px';
|
|
31
|
+
p.style.top = clamp(r.top - ar.top + r.height / 2 - p.offsetHeight / 2, 8, ar.height - p.offsetHeight - 8) + 'px';
|
|
24
32
|
} else {
|
|
25
33
|
p.style.left = clamp(r.left - ar.left + r.width / 2 - w / 2, 8, ar.width - w - 8) + 'px';
|
|
26
|
-
p.style.top = r.top - ar.top - p.offsetHeight - 10 + 'px';
|
|
34
|
+
p.style.top = Math.max(8, r.top - ar.top - p.offsetHeight - 10) + 'px';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Rebuild its contents (it stays open while things are set). */
|
|
39
|
+
function renderPalette(): void {
|
|
40
|
+
const p = $('#palette'), t = markupTarget();
|
|
41
|
+
p.innerHTML = '';
|
|
42
|
+
const label = (text: string) => { const l = document.createElement('div'); l.className = 'pl-label'; l.textContent = text; p.appendChild(l); };
|
|
43
|
+
const row = (cls = 'pl-row') => { const d = document.createElement('div'); d.className = cls; p.appendChild(d); return d; };
|
|
44
|
+
const btn = (parent: HTMLElement, aria: string, on: boolean, html: string, act: () => void, cls = 'pl-opt') => {
|
|
45
|
+
const b = document.createElement('button');
|
|
46
|
+
b.className = cls + (on ? ' on' : '');
|
|
47
|
+
b.setAttribute('aria-label', aria);
|
|
48
|
+
b.setAttribute('aria-pressed', String(on));
|
|
49
|
+
b.innerHTML = html;
|
|
50
|
+
b.onclick = () => { act(); renderPalette(); };
|
|
51
|
+
parent.appendChild(b);
|
|
52
|
+
return b;
|
|
53
|
+
};
|
|
54
|
+
label('Color');
|
|
55
|
+
const sw = row('pl-swatches');
|
|
56
|
+
for (const c of NOTE_COLORS) btn(sw, 'Color ' + c, c === t.color, '', () => setNoteColor(c), 'pl-swatch').style.background = c;
|
|
57
|
+
if (t.widthApplies) {
|
|
58
|
+
label('Width');
|
|
59
|
+
const wr = row();
|
|
60
|
+
NOTE_WIDTHS.forEach((w, i) => btn(wr, 'Width ' + w, t.width === w, `<i class="pv-dot" style="width:${4 + i * 2.5}px;height:${4 + i * 2.5}px"></i>`, () => setNoteWidth(w)));
|
|
61
|
+
}
|
|
62
|
+
label('Style');
|
|
63
|
+
const sr = row();
|
|
64
|
+
for (const st of NOTE_STYLES) btn(sr, STYLE_LABEL[st], t.style === st, stylePreview(st) + '<span>' + STYLE_LABEL[st] + '</span>', () => setNoteStyle(st), 'pl-opt tall');
|
|
65
|
+
if (t.ends) {
|
|
66
|
+
for (const which of ['start', 'end'] as const) {
|
|
67
|
+
label(which === 'start' ? 'Start' : 'End');
|
|
68
|
+
const cr = row();
|
|
69
|
+
for (const cap of NOTE_CAPS) {
|
|
70
|
+
btn(cr, (which === 'start' ? 'Start: ' : 'End: ') + CAP_LABEL[cap], (which === 'start' ? t.startCap : t.endCap) === cap,
|
|
71
|
+
capPreview(cap, which === 'start') + '<span>' + CAP_LABEL[cap] + '</span>', () => setNoteCap(which, cap), 'pl-opt tall');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Keep an open panel in step with the selection. */
|
|
78
|
+
export function refreshPalette(): void {
|
|
79
|
+
if ($('#palette').classList.contains('show')) {
|
|
80
|
+
if (ed.mode !== 'annotate') $('#palette').classList.remove('show');
|
|
81
|
+
else renderPalette();
|
|
27
82
|
}
|
|
28
83
|
}
|
|
29
84
|
|
package/src/ui/rail.ts
CHANGED
|
@@ -1,28 +1,30 @@
|
|
|
1
1
|
import { redo, undo } from '../app/edit';
|
|
2
2
|
import { ed, type Mode } from '../app/state';
|
|
3
|
-
import {
|
|
3
|
+
import { railIcon, railItems, setMode, TOGGLES } from '../editor/mode';
|
|
4
4
|
import { setTool } from '../tools/registry';
|
|
5
5
|
import { $ } from './dom';
|
|
6
6
|
import { icon } from './icons';
|
|
7
|
+
import { markupTarget } from '../editor/markup';
|
|
7
8
|
import { openPalette } from './palette';
|
|
8
9
|
import { bindTip } from './tip';
|
|
9
|
-
import {
|
|
10
|
+
import { toolTip } from '../editor/tooltips';
|
|
10
11
|
|
|
11
12
|
export function buildRail(): void {
|
|
12
13
|
const rail = $('#rail');
|
|
13
14
|
rail.innerHTML = '';
|
|
14
|
-
for (const it of
|
|
15
|
+
for (const it of railItems()) {
|
|
15
16
|
if (it === '|') { const d = document.createElement('div'); d.className = 'div'; rail.appendChild(d); continue; }
|
|
16
17
|
const [id, label, key, kind] = it;
|
|
17
18
|
const b = document.createElement('button');
|
|
18
19
|
b.dataset.tool = id;
|
|
19
20
|
b.setAttribute('aria-label', label);
|
|
20
21
|
if (kind === 'toggle') b.classList.add('toggle');
|
|
21
|
-
b.innerHTML = id === 'color' ? '<span class="dot"></span>' : icon(id);
|
|
22
|
+
b.innerHTML = id === 'color' ? '<span class="dot"></span>' : icon(railIcon(id));
|
|
22
23
|
b.onclick = () => {
|
|
23
24
|
if (kind === 'act') railAction(id, b);
|
|
24
|
-
else if (kind === 'toggle')
|
|
25
|
+
else if (kind === 'toggle') TOGGLES[id]?.flip();
|
|
25
26
|
else setTool(id);
|
|
27
|
+
if (kind !== 'act') showToolTip(toolTip(id, label));
|
|
26
28
|
};
|
|
27
29
|
bindTip(b, label, key, 'right');
|
|
28
30
|
rail.appendChild(b);
|
|
@@ -33,13 +35,25 @@ export function buildRail(): void {
|
|
|
33
35
|
export function updateRail(): void {
|
|
34
36
|
document.querySelectorAll<HTMLElement>('#rail button').forEach(b => {
|
|
35
37
|
const id = b.dataset.tool;
|
|
36
|
-
const on =
|
|
38
|
+
const on = b.classList.contains('toggle') ? !!TOGGLES[id!]?.on() : id === ed.tool;
|
|
37
39
|
b.classList.toggle('on', on);
|
|
38
40
|
b.setAttribute('aria-pressed', String(on));
|
|
39
|
-
if (id === 'color') (b.querySelector('.dot') as HTMLElement).style.background =
|
|
41
|
+
if (id === 'color') (b.querySelector('.dot') as HTMLElement).style.background = markupTarget().color;
|
|
40
42
|
});
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
let tipT: ReturnType<typeof setTimeout> | undefined;
|
|
46
|
+
/** What the tool just picked does, in a line under the Annotate / Draft tabs; it goes by itself. */
|
|
47
|
+
function showToolTip(html: string | null): void {
|
|
48
|
+
if (!html) return;
|
|
49
|
+
let el = document.querySelector<HTMLElement>('#toolTip');
|
|
50
|
+
if (!el) { el = document.createElement('div'); el.id = 'toolTip'; el.className = 'tooltip-note'; el.setAttribute('aria-live', 'polite'); $('#app').appendChild(el); }
|
|
51
|
+
el.innerHTML = html;
|
|
52
|
+
el.classList.remove('show'); void el.offsetWidth; el.classList.add('show');
|
|
53
|
+
clearTimeout(tipT);
|
|
54
|
+
tipT = setTimeout(() => el!.classList.remove('show'), 2600);
|
|
55
|
+
}
|
|
56
|
+
|
|
43
57
|
function railAction(id: string, btn: HTMLElement): void {
|
|
44
58
|
if (id === 'undo') undo();
|
|
45
59
|
else if (id === 'redo') redo();
|
package/src/ui/topbar.ts
CHANGED
|
@@ -1,24 +1,14 @@
|
|
|
1
|
-
import { settings } from '../model/settings';
|
|
2
1
|
import { fitView } from '../render/view';
|
|
3
|
-
import { applyCtxAction } from './ctx';
|
|
4
2
|
import { $ } from './dom';
|
|
5
|
-
import {
|
|
3
|
+
import { refreshDrawerSoon } from './drawer';
|
|
6
4
|
import { bindTip } from './tip';
|
|
7
5
|
|
|
8
|
-
/* Top bar view controls
|
|
6
|
+
/* Top bar view controls: Fit, and Layers (the stack), which opens the Layers menu (ui/menu.ts). */
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (!b) return;
|
|
13
|
-
b.setAttribute('aria-pressed', String(settings.showCons));
|
|
14
|
-
b.innerHTML = icon(settings.showCons ? 'eye' : 'eyeoff');
|
|
15
|
-
b.classList.toggle('off', !settings.showCons);
|
|
16
|
-
}
|
|
8
|
+
/** What's shown changed (constraint icons toggled from the keyboard, say): the Layers panel's switches follow. */
|
|
9
|
+
export function updateTopBar(): void { refreshDrawerSoon(); }
|
|
17
10
|
|
|
18
11
|
export function initTopBar(): void {
|
|
19
12
|
$('#btnFit').onclick = () => fitView();
|
|
20
|
-
$('#btnIcons').onclick = () => applyCtxAction('showcons');
|
|
21
13
|
bindTip($('#btnFit'), 'Fit to screen', 'F');
|
|
22
|
-
bindTip($('#btnIcons'), 'Constraint icons', '');
|
|
23
|
-
updateTopBar();
|
|
24
14
|
}
|
package/src/units/format.ts
CHANGED
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
import { getUnitPrefs, type UnitPrefs } from './prefs';
|
|
2
2
|
|
|
3
3
|
let custom: ((inches: number) => string) | null = null;
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
let hostPrefs: UnitPrefs | null = null;
|
|
5
|
+
/** The person picked the editor's own units in Settings over the host's. */
|
|
6
|
+
let own = false;
|
|
7
|
+
/**
|
|
8
|
+
* Format lengths the host's way (e.g. BOLDR's units and precision); `prefs` are the built-in units that come closest
|
|
9
|
+
* (for reading typed values). null: back to the built-in units.
|
|
10
|
+
*/
|
|
11
|
+
export function setLengthFormatter(fn: ((inches: number) => string) | null, prefs?: UnitPrefs): void {
|
|
12
|
+
custom = fn;
|
|
13
|
+
hostPrefs = fn && prefs ? { ...prefs } : null;
|
|
14
|
+
own = false;
|
|
15
|
+
}
|
|
16
|
+
/** The host's units (BOLDR: the person's account settings), when the host formats lengths. */
|
|
17
|
+
export const hostUnits = (): UnitPrefs | null => (custom ? hostPrefs ?? { unit: 'ftin', denom: 16 } : null);
|
|
18
|
+
/** Lengths read the host's way, rather than in units picked in the editor. */
|
|
19
|
+
export const followingHostUnits = (): boolean => !!custom && !own;
|
|
20
|
+
export function setOwnUnits(v: boolean): void { own = v; }
|
|
6
21
|
|
|
7
22
|
/** Format a length in inches: 150.5 → 12'-6 1/2" (ft-in), 150 1/2" (in), 3,823 mm (mm). */
|
|
8
23
|
export function fmtLen(v: number, u: UnitPrefs = getUnitPrefs()): string {
|
|
9
24
|
v = Math.abs(v);
|
|
10
|
-
if (custom && u === getUnitPrefs()) return custom(v);
|
|
25
|
+
if (custom && !own && u === getUnitPrefs()) return custom(v);
|
|
11
26
|
if (u.unit === 'mm') return Math.round(v * 25.4).toLocaleString() + ' mm';
|
|
12
27
|
const d = u.denom;
|
|
13
28
|
const t = Math.round(v * d) / d;
|