@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.
Files changed (547) hide show
  1. package/README.md +81 -9
  2. package/dist/app/edit.d.ts +12 -1
  3. package/dist/app/edit.js +96 -11
  4. package/dist/app/edit.js.map +1 -1
  5. package/dist/app/state.d.ts +31 -4
  6. package/dist/app/state.js +31 -2
  7. package/dist/app/state.js.map +1 -1
  8. package/dist/data/memory.d.ts +5 -0
  9. package/dist/data/memory.js +2 -2
  10. package/dist/data/memory.js.map +1 -1
  11. package/dist/data/provider.d.ts +19 -7
  12. package/dist/data/sync.d.ts +7 -2
  13. package/dist/data/sync.js +115 -64
  14. package/dist/data/sync.js.map +1 -1
  15. package/dist/editor/actions.d.ts +1 -2
  16. package/dist/editor/actions.js +391 -113
  17. package/dist/editor/actions.js.map +1 -1
  18. package/dist/editor/confirm.d.ts +23 -0
  19. package/dist/editor/confirm.js +35 -0
  20. package/dist/editor/confirm.js.map +1 -0
  21. package/dist/editor/elevation.d.ts +191 -90
  22. package/dist/editor/elevation.js +812 -616
  23. package/dist/editor/elevation.js.map +1 -1
  24. package/dist/editor/face.d.ts +73 -0
  25. package/dist/editor/face.js +269 -0
  26. package/dist/editor/face.js.map +1 -0
  27. package/dist/editor/files.d.ts +6 -0
  28. package/dist/editor/files.js +50 -4
  29. package/dist/editor/files.js.map +1 -1
  30. package/dist/editor/gestures.d.ts +18 -3
  31. package/dist/editor/gestures.js +145 -30
  32. package/dist/editor/gestures.js.map +1 -1
  33. package/dist/editor/keypad.d.ts +19 -4
  34. package/dist/editor/keypad.js +39 -9
  35. package/dist/editor/keypad.js.map +1 -1
  36. package/dist/editor/keys.js +36 -4
  37. package/dist/editor/keys.js.map +1 -1
  38. package/dist/editor/laser.d.ts +20 -4
  39. package/dist/editor/laser.js +104 -21
  40. package/dist/editor/laser.js.map +1 -1
  41. package/dist/editor/linkActions.d.ts +49 -0
  42. package/dist/editor/linkActions.js +210 -0
  43. package/dist/editor/linkActions.js.map +1 -0
  44. package/dist/editor/linkFollow.d.ts +2 -0
  45. package/dist/editor/linkFollow.js +53 -0
  46. package/dist/editor/linkFollow.js.map +1 -0
  47. package/dist/editor/linkWrite.d.ts +13 -0
  48. package/dist/editor/linkWrite.js +84 -0
  49. package/dist/editor/linkWrite.js.map +1 -0
  50. package/dist/editor/links.d.ts +84 -0
  51. package/dist/editor/links.js +130 -0
  52. package/dist/editor/links.js.map +1 -0
  53. package/dist/editor/markup.d.ts +29 -3
  54. package/dist/editor/markup.js +95 -10
  55. package/dist/editor/markup.js.map +1 -1
  56. package/dist/editor/mode.d.ts +40 -2
  57. package/dist/editor/mode.js +71 -4
  58. package/dist/editor/mode.js.map +1 -1
  59. package/dist/editor/ports.d.ts +36 -6
  60. package/dist/editor/ports.js +10 -1
  61. package/dist/editor/ports.js.map +1 -1
  62. package/dist/editor/project.d.ts +38 -6
  63. package/dist/editor/project.js +167 -27
  64. package/dist/editor/project.js.map +1 -1
  65. package/dist/editor/selection.d.ts +10 -0
  66. package/dist/editor/selection.js +62 -8
  67. package/dist/editor/selection.js.map +1 -1
  68. package/dist/editor/status.js +3 -0
  69. package/dist/editor/status.js.map +1 -1
  70. package/dist/editor/tooltips.d.ts +5 -0
  71. package/dist/editor/tooltips.js +48 -0
  72. package/dist/editor/tooltips.js.map +1 -0
  73. package/dist/editor/view3d.d.ts +17 -0
  74. package/dist/editor/view3d.js +135 -0
  75. package/dist/editor/view3d.js.map +1 -0
  76. package/dist/editor/view3dState.d.ts +39 -0
  77. package/dist/editor/view3dState.js +44 -0
  78. package/dist/editor/view3dState.js.map +1 -0
  79. package/dist/export/dxf.js +1 -1
  80. package/dist/export/dxf.js.map +1 -1
  81. package/dist/export/pdf.js +17 -12
  82. package/dist/export/pdf.js.map +1 -1
  83. package/dist/export/plan.d.ts +23 -6
  84. package/dist/export/plan.js +127 -44
  85. package/dist/export/plan.js.map +1 -1
  86. package/dist/index.d.ts +5 -0
  87. package/dist/index.js +2 -0
  88. package/dist/index.js.map +1 -1
  89. package/dist/model/constraints.js +15 -4
  90. package/dist/model/constraints.js.map +1 -1
  91. package/dist/model/demo.js +3 -3
  92. package/dist/model/demo.js.map +1 -1
  93. package/dist/model/elevation.d.ts +6 -9
  94. package/dist/model/elevation.js +5 -13
  95. package/dist/model/elevation.js.map +1 -1
  96. package/dist/model/faces.d.ts +58 -0
  97. package/dist/model/faces.js +197 -0
  98. package/dist/model/faces.js.map +1 -0
  99. package/dist/model/facesolve.d.ts +72 -0
  100. package/dist/model/facesolve.js +369 -0
  101. package/dist/model/facesolve.js.map +1 -0
  102. package/dist/model/history.d.ts +3 -2
  103. package/dist/model/history.js +17 -11
  104. package/dist/model/history.js.map +1 -1
  105. package/dist/model/images.d.ts +18 -0
  106. package/dist/model/images.js +26 -0
  107. package/dist/model/images.js.map +1 -0
  108. package/dist/model/links.d.ts +46 -0
  109. package/dist/model/links.js +73 -0
  110. package/dist/model/links.js.map +1 -0
  111. package/dist/model/markup.d.ts +46 -0
  112. package/dist/model/markup.js +71 -0
  113. package/dist/model/markup.js.map +1 -0
  114. package/dist/model/measurement.d.ts +11 -0
  115. package/dist/model/measurement.js +24 -0
  116. package/dist/model/measurement.js.map +1 -0
  117. package/dist/model/openings.d.ts +4 -0
  118. package/dist/model/openings.js +4 -0
  119. package/dist/model/openings.js.map +1 -1
  120. package/dist/model/planGeom.d.ts +72 -2
  121. package/dist/model/planGeom.js +184 -10
  122. package/dist/model/planGeom.js.map +1 -1
  123. package/dist/model/project.js +3 -2
  124. package/dist/model/project.js.map +1 -1
  125. package/dist/model/projectfile.js +2 -1
  126. package/dist/model/projectfile.js.map +1 -1
  127. package/dist/model/rooms.js +2 -2
  128. package/dist/model/rooms.js.map +1 -1
  129. package/dist/model/settings.d.ts +6 -0
  130. package/dist/model/settings.js +2 -2
  131. package/dist/model/settings.js.map +1 -1
  132. package/dist/model/sketch.d.ts +14 -0
  133. package/dist/model/sketch.js +143 -24
  134. package/dist/model/sketch.js.map +1 -1
  135. package/dist/model/smooth.d.ts +6 -0
  136. package/dist/model/smooth.js +52 -0
  137. package/dist/model/smooth.js.map +1 -0
  138. package/dist/model/store.d.ts +12 -0
  139. package/dist/model/store.js +35 -3
  140. package/dist/model/store.js.map +1 -1
  141. package/dist/model/types.d.ts +173 -8
  142. package/dist/model/wallfaces.d.ts +32 -0
  143. package/dist/model/wallfaces.js +133 -20
  144. package/dist/model/wallfaces.js.map +1 -1
  145. package/dist/model/wallshape.d.ts +15 -0
  146. package/dist/model/wallshape.js +89 -0
  147. package/dist/model/wallshape.js.map +1 -0
  148. package/dist/react/BottomBar.d.ts +9 -5
  149. package/dist/react/BottomBar.js +213 -24
  150. package/dist/react/BottomBar.js.map +1 -1
  151. package/dist/react/Buttons.d.ts +13 -2
  152. package/dist/react/Buttons.js +37 -5
  153. package/dist/react/Buttons.js.map +1 -1
  154. package/dist/react/CadEditor.d.ts +46 -3
  155. package/dist/react/CadEditor.js +77 -20
  156. package/dist/react/CadEditor.js.map +1 -1
  157. package/dist/react/ConfirmSheet.d.ts +4 -0
  158. package/dist/react/ConfirmSheet.js +31 -0
  159. package/dist/react/ConfirmSheet.js.map +1 -0
  160. package/dist/react/ConflictSheet.d.ts +2 -1
  161. package/dist/react/ConflictSheet.js +16 -8
  162. package/dist/react/ConflictSheet.js.map +1 -1
  163. package/dist/react/Drawer.d.ts +8 -2
  164. package/dist/react/Drawer.js +167 -38
  165. package/dist/react/Drawer.js.map +1 -1
  166. package/dist/react/Duck.d.ts +13 -0
  167. package/dist/react/Duck.js +45 -0
  168. package/dist/react/Duck.js.map +1 -0
  169. package/dist/react/Empty3D.d.ts +2 -0
  170. package/dist/react/Empty3D.js +39 -0
  171. package/dist/react/Empty3D.js.map +1 -0
  172. package/dist/react/FitScroll.d.ts +6 -0
  173. package/dist/react/FitScroll.js +14 -0
  174. package/dist/react/FitScroll.js.map +1 -0
  175. package/dist/react/Icon.native.d.ts +6 -0
  176. package/dist/react/Icon.native.js +4 -0
  177. package/dist/react/Icon.native.js.map +1 -1
  178. package/dist/react/Icon.web.d.ts +6 -0
  179. package/dist/react/Icon.web.js +4 -0
  180. package/dist/react/Icon.web.js.map +1 -1
  181. package/dist/react/KeypadSheet.d.ts +9 -2
  182. package/dist/react/KeypadSheet.js +174 -42
  183. package/dist/react/KeypadSheet.js.map +1 -1
  184. package/dist/react/LaserTray.js +8 -5
  185. package/dist/react/LaserTray.js.map +1 -1
  186. package/dist/react/MarkupPanel.d.ts +8 -0
  187. package/dist/react/MarkupPanel.js +67 -0
  188. package/dist/react/MarkupPanel.js.map +1 -0
  189. package/dist/react/PageThumb.native.d.ts +7 -2
  190. package/dist/react/PageThumb.native.js +19 -7
  191. package/dist/react/PageThumb.native.js.map +1 -1
  192. package/dist/react/PageThumb.web.d.ts +3 -2
  193. package/dist/react/PageThumb.web.js +4 -4
  194. package/dist/react/PageThumb.web.js.map +1 -1
  195. package/dist/react/PlanCanvas.native.d.ts +1 -1
  196. package/dist/react/PlanCanvas.native.js +134 -36
  197. package/dist/react/PlanCanvas.native.js.map +1 -1
  198. package/dist/react/PlanCanvas.web.js +16 -5
  199. package/dist/react/PlanCanvas.web.js.map +1 -1
  200. package/dist/react/Popovers.d.ts +12 -6
  201. package/dist/react/Popovers.js +53 -26
  202. package/dist/react/Popovers.js.map +1 -1
  203. package/dist/react/Rail.d.ts +24 -0
  204. package/dist/react/Rail.js +324 -34
  205. package/dist/react/Rail.js.map +1 -1
  206. package/dist/react/ShapeMenu.d.ts +7 -0
  207. package/dist/react/ShapeMenu.js +58 -0
  208. package/dist/react/ShapeMenu.js.map +1 -0
  209. package/dist/react/Sheet.d.ts +14 -4
  210. package/dist/react/Sheet.js +54 -9
  211. package/dist/react/Sheet.js.map +1 -1
  212. package/dist/react/Slider.d.ts +18 -0
  213. package/dist/react/Slider.js +27 -0
  214. package/dist/react/Slider.js.map +1 -0
  215. package/dist/react/Toast.js +14 -6
  216. package/dist/react/Toast.js.map +1 -1
  217. package/dist/react/ToolTip.d.ts +7 -0
  218. package/dist/react/ToolTip.js +43 -0
  219. package/dist/react/ToolTip.js.map +1 -0
  220. package/dist/react/TopBar.d.ts +6 -3
  221. package/dist/react/TopBar.js +48 -17
  222. package/dist/react/TopBar.js.map +1 -1
  223. package/dist/react/Tween.d.ts +7 -0
  224. package/dist/react/Tween.js +26 -0
  225. package/dist/react/Tween.js.map +1 -0
  226. package/dist/react/Tween.native.d.ts +6 -0
  227. package/dist/react/Tween.native.js +43 -0
  228. package/dist/react/Tween.native.js.map +1 -0
  229. package/dist/react/View3D.native.d.ts +4 -0
  230. package/dist/react/View3D.native.js +103 -0
  231. package/dist/react/View3D.native.js.map +1 -0
  232. package/dist/react/View3D.web.d.ts +4 -0
  233. package/dist/react/View3D.web.js +151 -0
  234. package/dist/react/View3D.web.js.map +1 -0
  235. package/dist/react/View3DHost.d.ts +143 -0
  236. package/dist/react/View3DHost.js +377 -0
  237. package/dist/react/View3DHost.js.map +1 -0
  238. package/dist/react/corners.d.ts +7 -0
  239. package/dist/react/corners.js +51 -0
  240. package/dist/react/corners.js.map +1 -0
  241. package/dist/react/easing.d.ts +15 -0
  242. package/dist/react/easing.js +10 -0
  243. package/dist/react/easing.js.map +1 -0
  244. package/dist/react/host.d.ts +7 -2
  245. package/dist/react/host.js +8 -3
  246. package/dist/react/host.js.map +1 -1
  247. package/dist/react/hostKeypad.d.ts +56 -0
  248. package/dist/react/hostKeypad.js +5 -0
  249. package/dist/react/hostKeypad.js.map +1 -0
  250. package/dist/react/motion.d.ts +77 -0
  251. package/dist/react/motion.js +173 -0
  252. package/dist/react/motion.js.map +1 -0
  253. package/dist/react/ports.js +90 -8
  254. package/dist/react/ports.js.map +1 -1
  255. package/dist/react/saving.d.ts +15 -0
  256. package/dist/react/saving.js +76 -0
  257. package/dist/react/saving.js.map +1 -0
  258. package/dist/react/store.d.ts +38 -1
  259. package/dist/react/store.js +31 -1
  260. package/dist/react/store.js.map +1 -1
  261. package/dist/react/theme.d.ts +10 -0
  262. package/dist/react/theme.js +8 -0
  263. package/dist/react/theme.js.map +1 -1
  264. package/dist/react/topRow.d.ts +22 -0
  265. package/dist/react/topRow.js +61 -0
  266. package/dist/react/topRow.js.map +1 -0
  267. package/dist/react/touch.d.ts +6 -0
  268. package/dist/react/touch.js +9 -0
  269. package/dist/react/touch.js.map +1 -0
  270. package/dist/render/ctx.d.ts +8 -0
  271. package/dist/render/draw.d.ts +33 -1
  272. package/dist/render/draw.js +593 -215
  273. package/dist/render/draw.js.map +1 -1
  274. package/dist/render/elevation.d.ts +65 -43
  275. package/dist/render/elevation.js +414 -140
  276. package/dist/render/elevation.js.map +1 -1
  277. package/dist/render/figmaIcons.d.ts +12 -0
  278. package/dist/render/figmaIcons.js +21 -0
  279. package/dist/render/figmaIcons.js.map +1 -0
  280. package/dist/render/hit.d.ts +22 -2
  281. package/dist/render/hit.js +168 -25
  282. package/dist/render/hit.js.map +1 -1
  283. package/dist/render/icons.js +69 -10
  284. package/dist/render/icons.js.map +1 -1
  285. package/dist/render/linkTile.d.ts +15 -0
  286. package/dist/render/linkTile.js +369 -0
  287. package/dist/render/linkTile.js.map +1 -0
  288. package/dist/render/markup.d.ts +11 -0
  289. package/dist/render/markup.js +112 -0
  290. package/dist/render/markup.js.map +1 -0
  291. package/dist/render/navigate.d.ts +88 -0
  292. package/dist/render/navigate.js +100 -0
  293. package/dist/render/navigate.js.map +1 -0
  294. package/dist/render/snap.d.ts +20 -1
  295. package/dist/render/snap.js +118 -25
  296. package/dist/render/snap.js.map +1 -1
  297. package/dist/render/squircle.d.ts +8 -0
  298. package/dist/render/squircle.js +71 -0
  299. package/dist/render/squircle.js.map +1 -0
  300. package/dist/render/view.d.ts +115 -2
  301. package/dist/render/view.js +244 -19
  302. package/dist/render/view.js.map +1 -1
  303. package/dist/skia/SkiaDrawCtx.d.ts +30 -2
  304. package/dist/skia/SkiaDrawCtx.js +198 -43
  305. package/dist/skia/SkiaDrawCtx.js.map +1 -1
  306. package/dist/skia/host.d.ts +18 -2
  307. package/dist/skia/host.js +46 -1
  308. package/dist/skia/host.js.map +1 -1
  309. package/dist/solver/equations.js +37 -2
  310. package/dist/solver/equations.js.map +1 -1
  311. package/dist/solver/gauss-newton.d.ts +17 -0
  312. package/dist/solver/gauss-newton.js +144 -36
  313. package/dist/solver/gauss-newton.js.map +1 -1
  314. package/dist/solver/linalg.d.ts +45 -2
  315. package/dist/solver/linalg.js +164 -10
  316. package/dist/solver/linalg.js.map +1 -1
  317. package/dist/solver/types.d.ts +5 -0
  318. package/dist/tools/annotate.d.ts +57 -1
  319. package/dist/tools/annotate.js +64 -43
  320. package/dist/tools/annotate.js.map +1 -1
  321. package/dist/tools/chamfer.d.ts +12 -0
  322. package/dist/tools/chamfer.js +114 -0
  323. package/dist/tools/chamfer.js.map +1 -0
  324. package/dist/tools/corner.d.ts +38 -0
  325. package/dist/tools/corner.js +99 -0
  326. package/dist/tools/corner.js.map +1 -0
  327. package/dist/tools/dim.d.ts +86 -9
  328. package/dist/tools/dim.js +581 -96
  329. package/dist/tools/dim.js.map +1 -1
  330. package/dist/tools/elevation.js +1 -1
  331. package/dist/tools/elevation.js.map +1 -1
  332. package/dist/tools/fillet.d.ts +9 -0
  333. package/dist/tools/fillet.js +86 -0
  334. package/dist/tools/fillet.js.map +1 -0
  335. package/dist/tools/freehand.d.ts +3 -2
  336. package/dist/tools/freehand.js +19 -12
  337. package/dist/tools/freehand.js.map +1 -1
  338. package/dist/tools/index.js +14 -4
  339. package/dist/tools/index.js.map +1 -1
  340. package/dist/tools/line.d.ts +8 -3
  341. package/dist/tools/line.js +66 -30
  342. package/dist/tools/line.js.map +1 -1
  343. package/dist/tools/modify.d.ts +13 -0
  344. package/dist/tools/modify.js +105 -25
  345. package/dist/tools/modify.js.map +1 -1
  346. package/dist/tools/openings.js +22 -15
  347. package/dist/tools/openings.js.map +1 -1
  348. package/dist/tools/registry.d.ts +8 -0
  349. package/dist/tools/registry.js +42 -2
  350. package/dist/tools/registry.js.map +1 -1
  351. package/dist/tools/select.d.ts +77 -3
  352. package/dist/tools/select.js +560 -74
  353. package/dist/tools/select.js.map +1 -1
  354. package/dist/tools/shapes.d.ts +14 -0
  355. package/dist/tools/shapes.js +154 -75
  356. package/dist/tools/shapes.js.map +1 -1
  357. package/dist/units/format.d.ts +10 -2
  358. package/dist/units/format.js +18 -3
  359. package/dist/units/format.js.map +1 -1
  360. package/dist/view3d/build.d.ts +35 -0
  361. package/dist/view3d/build.js +630 -0
  362. package/dist/view3d/build.js.map +1 -0
  363. package/dist/view3d/camera.d.ts +60 -0
  364. package/dist/view3d/camera.js +84 -0
  365. package/dist/view3d/camera.js.map +1 -0
  366. package/dist/view3d/fatLines.d.ts +11 -0
  367. package/dist/view3d/fatLines.js +83 -0
  368. package/dist/view3d/fatLines.js.map +1 -0
  369. package/dist/view3d/lines.d.ts +17 -0
  370. package/dist/view3d/lines.js +271 -0
  371. package/dist/view3d/lines.js.map +1 -0
  372. package/dist/view3d/mesh.d.ts +51 -0
  373. package/dist/view3d/mesh.js +101 -0
  374. package/dist/view3d/mesh.js.map +1 -0
  375. package/dist/view3d/poly.d.ts +16 -0
  376. package/dist/view3d/poly.js +109 -0
  377. package/dist/view3d/poly.js.map +1 -0
  378. package/dist/view3d/viewer.d.ts +86 -0
  379. package/dist/view3d/viewer.js +350 -0
  380. package/dist/view3d/viewer.js.map +1 -0
  381. package/package.json +15 -2
  382. package/src/app/edit.ts +83 -12
  383. package/src/app/main.ts +11 -5
  384. package/src/app/state.ts +38 -5
  385. package/src/data/memory.ts +2 -2
  386. package/src/data/provider.ts +15 -7
  387. package/src/data/sync.ts +87 -50
  388. package/src/editor/actions.ts +292 -83
  389. package/src/editor/confirm.ts +58 -0
  390. package/src/editor/elevation.ts +698 -478
  391. package/src/editor/face.ts +227 -0
  392. package/src/editor/files.ts +40 -4
  393. package/src/editor/gestures.ts +147 -31
  394. package/src/editor/keypad.ts +43 -11
  395. package/src/editor/keys.ts +25 -5
  396. package/src/editor/laser.ts +98 -20
  397. package/src/editor/linkActions.ts +194 -0
  398. package/src/editor/linkFollow.ts +43 -0
  399. package/src/editor/linkWrite.ts +77 -0
  400. package/src/editor/links.ts +181 -0
  401. package/src/editor/markup.ts +92 -8
  402. package/src/editor/mode.ts +83 -7
  403. package/src/editor/ports.ts +37 -6
  404. package/src/editor/project.ts +155 -26
  405. package/src/editor/selection.ts +54 -8
  406. package/src/editor/status.ts +2 -0
  407. package/src/editor/tooltips.ts +47 -0
  408. package/src/editor/view3d.ts +119 -0
  409. package/src/editor/view3dState.ts +61 -0
  410. package/src/export/dxf.ts +1 -1
  411. package/src/export/pdf.ts +15 -7
  412. package/src/export/plan.ts +119 -35
  413. package/src/index.ts +5 -0
  414. package/src/input/keyboard.ts +2 -4
  415. package/src/model/constraints.ts +13 -4
  416. package/src/model/demo.ts +3 -3
  417. package/src/model/elevation.ts +6 -16
  418. package/src/model/faces.ts +153 -0
  419. package/src/model/facesolve.ts +322 -0
  420. package/src/model/history.ts +19 -11
  421. package/src/model/images.ts +35 -0
  422. package/src/model/links.ts +77 -0
  423. package/src/model/markup.ts +80 -0
  424. package/src/model/measurement.ts +26 -0
  425. package/src/model/openings.ts +6 -0
  426. package/src/model/planGeom.ts +174 -8
  427. package/src/model/project.ts +3 -2
  428. package/src/model/projectfile.ts +2 -1
  429. package/src/model/rooms.ts +1 -1
  430. package/src/model/settings.ts +8 -2
  431. package/src/model/sketch.ts +101 -14
  432. package/src/model/smooth.ts +51 -0
  433. package/src/model/store.ts +35 -4
  434. package/src/model/types.ts +170 -8
  435. package/src/model/wallfaces.ts +126 -17
  436. package/src/model/wallshape.ts +97 -0
  437. package/src/react/BottomBar.tsx +263 -46
  438. package/src/react/Buttons.tsx +50 -9
  439. package/src/react/CadEditor.tsx +133 -30
  440. package/src/react/ConfirmSheet.tsx +38 -0
  441. package/src/react/ConflictSheet.tsx +19 -12
  442. package/src/react/Drawer.tsx +213 -110
  443. package/src/react/Duck.tsx +50 -0
  444. package/src/react/Empty3D.tsx +32 -0
  445. package/src/react/FitScroll.tsx +19 -0
  446. package/src/react/Icon.native.tsx +5 -0
  447. package/src/react/Icon.web.tsx +5 -0
  448. package/src/react/KeypadSheet.tsx +253 -60
  449. package/src/react/LaserTray.tsx +12 -8
  450. package/src/react/MarkupPanel.tsx +150 -0
  451. package/src/react/PageThumb.native.tsx +18 -7
  452. package/src/react/PageThumb.web.tsx +4 -4
  453. package/src/react/PlanCanvas.native.tsx +151 -34
  454. package/src/react/PlanCanvas.web.tsx +15 -5
  455. package/src/react/Popovers.tsx +116 -51
  456. package/src/react/Rail.tsx +360 -27
  457. package/src/react/ShapeMenu.tsx +66 -0
  458. package/src/react/Sheet.tsx +56 -11
  459. package/src/react/Slider.tsx +48 -0
  460. package/src/react/Toast.tsx +16 -9
  461. package/src/react/ToolTip.tsx +49 -0
  462. package/src/react/TopBar.tsx +97 -39
  463. package/src/react/Tween.native.tsx +36 -0
  464. package/src/react/Tween.tsx +24 -0
  465. package/src/react/View3D.native.tsx +131 -0
  466. package/src/react/View3D.web.tsx +131 -0
  467. package/src/react/View3DHost.ts +367 -0
  468. package/src/react/corners.ts +52 -0
  469. package/src/react/easing.ts +14 -0
  470. package/src/react/host.ts +8 -3
  471. package/src/react/hostKeypad.ts +66 -0
  472. package/src/react/motion.tsx +239 -0
  473. package/src/react/ports.ts +78 -9
  474. package/src/react/saving.ts +80 -0
  475. package/src/react/store.ts +35 -3
  476. package/src/react/theme.ts +8 -0
  477. package/src/react/topRow.ts +71 -0
  478. package/src/react/touch.ts +10 -0
  479. package/src/render/ctx.ts +8 -0
  480. package/src/render/draw.ts +437 -175
  481. package/src/render/elevation.ts +349 -143
  482. package/src/render/figmaIcons.ts +22 -0
  483. package/src/render/hit.ts +140 -21
  484. package/src/render/icons.ts +69 -10
  485. package/src/render/linkTile.ts +297 -0
  486. package/src/render/markup.ts +91 -0
  487. package/src/render/navigate.ts +114 -0
  488. package/src/render/snap.ts +105 -22
  489. package/src/render/squircle.ts +73 -0
  490. package/src/render/view.ts +255 -19
  491. package/src/skia/SkiaDrawCtx.ts +163 -41
  492. package/src/skia/host.ts +46 -1
  493. package/src/solver/equations.ts +37 -2
  494. package/src/solver/gauss-newton.ts +125 -28
  495. package/src/solver/linalg.ts +141 -5
  496. package/src/solver/types.ts +5 -0
  497. package/src/styles.css +91 -42
  498. package/src/tools/annotate.ts +58 -29
  499. package/src/tools/chamfer.ts +103 -0
  500. package/src/tools/corner.ts +99 -0
  501. package/src/tools/dim.ts +524 -92
  502. package/src/tools/elevation.ts +1 -1
  503. package/src/tools/fillet.ts +82 -0
  504. package/src/tools/freehand.ts +20 -16
  505. package/src/tools/index.ts +17 -4
  506. package/src/tools/line.ts +59 -23
  507. package/src/tools/modify.ts +86 -24
  508. package/src/tools/openings.ts +8 -13
  509. package/src/tools/registry.ts +27 -2
  510. package/src/tools/select.ts +424 -75
  511. package/src/tools/shapes.ts +119 -58
  512. package/src/ui/ctx.ts +2 -1
  513. package/src/ui/domPorts.ts +23 -4
  514. package/src/ui/drawer.ts +6 -26
  515. package/src/ui/elevation.ts +10 -101
  516. package/src/ui/keypad.ts +8 -14
  517. package/src/ui/laser.ts +2 -2
  518. package/src/ui/menu.ts +63 -17
  519. package/src/ui/noteinput.ts +3 -3
  520. package/src/ui/opacity.ts +108 -0
  521. package/src/ui/palette.ts +67 -12
  522. package/src/ui/rail.ts +21 -7
  523. package/src/ui/topbar.ts +4 -14
  524. package/src/units/format.ts +18 -3
  525. package/src/view3d/build.ts +607 -0
  526. package/src/view3d/camera.ts +113 -0
  527. package/src/view3d/fatLines.ts +83 -0
  528. package/src/view3d/lines.ts +258 -0
  529. package/src/view3d/mesh.ts +111 -0
  530. package/src/view3d/poly.ts +104 -0
  531. package/src/view3d/viewer.ts +348 -0
  532. package/dist/react/ElevCanvas.native.d.ts +0 -3
  533. package/dist/react/ElevCanvas.native.js +0 -58
  534. package/dist/react/ElevCanvas.native.js.map +0 -1
  535. package/dist/react/ElevCanvas.web.d.ts +0 -4
  536. package/dist/react/ElevCanvas.web.js +0 -44
  537. package/dist/react/ElevCanvas.web.js.map +0 -1
  538. package/dist/react/ElevationSheet.d.ts +0 -2
  539. package/dist/react/ElevationSheet.js +0 -38
  540. package/dist/react/ElevationSheet.js.map +0 -1
  541. package/dist/render/elevationView.d.ts +0 -3
  542. package/dist/render/elevationView.js +0 -100
  543. package/dist/render/elevationView.js.map +0 -1
  544. package/src/react/ElevCanvas.native.tsx +0 -68
  545. package/src/react/ElevCanvas.web.tsx +0 -42
  546. package/src/react/ElevationSheet.tsx +0 -88
  547. package/src/render/elevationView.ts +0 -73
@@ -0,0 +1,227 @@
1
+ import { afterEdit, tryAddConstraint } from '../app/edit';
2
+ import { ed } from '../app/state';
3
+ import { measure } from '../model/constraints';
4
+ import { cabPoints, cornerAnchor, endEdge } from '../model/faces';
5
+ import { dropHistory, pushHistory } from '../model/history';
6
+ import { uid } from '../model/ids';
7
+ import { settings } from '../model/settings';
8
+ import { addLine, addPoint } from '../model/sketch';
9
+ import { planSK, SK } from '../model/store';
10
+ import { elevOpenings } from '../model/elevation';
11
+ import type { Cabinet, Constraint, WallFace } from '../model/types';
12
+ import { requestDraw } from '../render/view';
13
+ import { editDim } from '../tools/dim';
14
+ import { fmtLen } from '../units';
15
+ import { elev } from './elevation';
16
+ import { openKeypad, toast, updateCtx } from './ports';
17
+
18
+ /*
19
+ Things the wall view adds to a wall face beyond what the plan's tools draw: cabinets, height lines, and the
20
+ dimensions behind the wall view's chips (along the floor, off the floor, a cabinet's size). The face is the sketch
21
+ being edited (SK()) while the wall view is open; x runs across the wall as seen, y up from the floor.
22
+ */
23
+
24
+ /** A new cabinet: one standard size (24" wide, 34½" high, 24" deep) — change it once it's placed. */
25
+ export const CAB = { w: 24, h: 34.5, d: 24 };
26
+
27
+ /** The wall face being edited. */
28
+ export const curFace = (): WallFace => SK() as WallFace;
29
+
30
+ const q = () => 1 / settings.denom;
31
+ const round = (v: number) => Math.round(v / q()) * q();
32
+
33
+ /** The cabinets on the face with their extent. */
34
+ export function cabExtents(face: WallFace = curFace()): { id: string; u0: number; u1: number; z0: number; z1: number }[] {
35
+ return Object.entries(face.cabs ?? {}).map(([id, cb]) => {
36
+ const ps = cabPoints(cb).map(p => face.points[p]).filter(Boolean);
37
+ return { id, u0: Math.min(...ps.map(p => p.x)), u1: Math.max(...ps.map(p => p.x)), z0: Math.min(...ps.map(p => p.y)), z1: Math.max(...ps.map(p => p.y)) };
38
+ });
39
+ }
40
+
41
+ /**
42
+ * Where a new cabinet goes for a tap at (u, z): centred on it, inside the wall, and pulled against a corner or the
43
+ * side of a cabinet within 8" (and onto the floor, the ceiling or a cabinet's top or bottom within 6"). Nothing holds
44
+ * it there: like a door or window, it's placed, and its dimensions lock it.
45
+ */
46
+ export function cabinetSpot(u: number, z: number): { u0: number; z0: number } | null {
47
+ const f = elev.frame;
48
+ if (!f || f.face[1] - f.face[0] < CAB.w - 1e-6 || f.H < CAB.h - 1e-6) return null;
49
+ let u0 = Math.min(Math.max(round(u - CAB.w / 2), f.face[0]), f.face[1] - CAB.w);
50
+ let z0 = Math.min(Math.max(round(z - CAB.h / 2), 0), f.H - CAB.h);
51
+ const cabs = settings.layers.objSnap ? cabExtents() : [], ops = settings.layers.objSnap ? elevOpenings(planSK(), f) : [];
52
+ // its sides against the corners and other cabinets, or in line with a door's or window's sides
53
+ const sides = [f.face[0], ...cabs.map(c => c.u1), ...ops.flatMap(o => [o.u0, o.u1])], ends = [f.face[1], ...cabs.map(c => c.u0), ...ops.flatMap(o => [o.u0, o.u1])];
54
+ let best = settings.layers.objSnap ? 8 : 0, nu = u0;
55
+ for (const x of sides) if (Math.abs(u0 - x) < best) { best = Math.abs(u0 - x); nu = x; }
56
+ for (const x of ends) if (Math.abs(u0 + CAB.w - x) < best) { best = Math.abs(u0 + CAB.w - x); nu = x - CAB.w; }
57
+ u0 = nu;
58
+ // its bottom on the floor or another cabinet (or just over a window's head); its top under a cabinet, or a window's sill
59
+ const floors = [0, ...cabs.map(c => c.z1), ...ops.filter(o => o.type === 'window').map(o => o.head)];
60
+ const ceilings = [f.H, ...cabs.map(c => c.z0), ...ops.filter(o => o.type === 'window').map(o => o.sill)];
61
+ let nz = z0;
62
+ best = settings.layers.objSnap ? 6 : 0;
63
+ for (const y of floors) if (Math.abs(z0 - y) < best) { best = Math.abs(z0 - y); nz = y; }
64
+ for (const y of ceilings) if (Math.abs(z0 + CAB.h - y) < best) { best = Math.abs(z0 + CAB.h - y); nz = y - CAB.h; }
65
+ return { u0, z0: nz };
66
+ }
67
+
68
+ /**
69
+ * Snap a cabinet being dragged (by du, dz from where its box started): its sides to the wall's corners, other cabinets'
70
+ * sides and doors' and windows' edges, its bottom and top to the floor, the ceiling, other cabinets' tops and bottoms
71
+ * and windows' sills and heads — whichever is nearest within `tol` inches, across and up separately.
72
+ */
73
+ export function snapCabinetMove(id: string, box: { u0: number; u1: number; z0: number; z1: number }, du: number, dz: number, tol: number): [number, number] {
74
+ return snapObjectMove({ cab: id }, box, du, dz, tol);
75
+ }
76
+
77
+ /**
78
+ * Snap anything being dragged on the wall (a cabinet, a door or window — `self`, left out of the targets): its sides to
79
+ * the wall's corners and the other objects' sides, its bottom and top to the floor, the ceiling and the other objects'
80
+ * tops and bottoms (sills and heads), whichever is nearest within `tol` inches, across and up separately. Off when
81
+ * Object snapping is (Layers).
82
+ */
83
+ export function snapObjectMove(self: { cab?: string; op?: string }, box: { u0: number; u1: number; z0: number; z1: number }, du: number, dz: number, tol: number): [number, number] {
84
+ const f = elev.frame;
85
+ if (!f || !settings.layers.objSnap) return [du, dz];
86
+ const others = cabExtents().filter(c => c.id !== self.cab), ops = elevOpenings(planSK(), f).filter(o => o.id !== self.op);
87
+ const xs = [f.face[0], f.face[1], ...others.flatMap(c => [c.u0, c.u1]), ...ops.flatMap(o => [o.u0, o.u1])];
88
+ const zs = [0, f.H, ...others.flatMap(c => [c.z0, c.z1]), ...ops.flatMap(o => [o.sill, o.head])];
89
+ const pull = (edges: number[], targets: number[]) => {
90
+ let best: number | null = null;
91
+ for (const e of edges) for (const t of targets) if (Math.abs(t - e) < tol && (best == null || Math.abs(t - e) < Math.abs(best))) best = t - e;
92
+ return best ?? 0;
93
+ };
94
+ return [du + pull([box.u0 + du, box.u1 + du], xs), dz + pull([box.z0 + dz, box.z1 + dz], zs)];
95
+ }
96
+
97
+ /** Place a cabinet (the standard size) for a tap at (u, z), and select it. Returns its id. */
98
+ export function addCabinet(u: number, z: number): string | null {
99
+ const spot = cabinetSpot(u, z);
100
+ if (!spot) { toast('That wall is too small for a ' + fmtLen(CAB.w) + ' × ' + fmtLen(CAB.h) + ' cabinet.', { warn: true }); return null; }
101
+ pushHistory();
102
+ const face = curFace(), id = uid('cab'), { u0, z0 } = spot;
103
+ const cb: Cabinet = {
104
+ bl: addPoint(face, u0, z0), br: addPoint(face, u0 + CAB.w, z0), tr: addPoint(face, u0 + CAB.w, z0 + CAB.h), tl: addPoint(face, u0, z0 + CAB.h), depth: CAB.d,
105
+ };
106
+ face.cabs = face.cabs ?? {};
107
+ face.cabs[id] = cb;
108
+ for (const [a, b] of [[cb.bl, cb.br], [cb.br, cb.tr], [cb.tr, cb.tl], [cb.tl, cb.bl]]) face.lines[addLine(face, a, b)].cab = id;
109
+ // square, and its standard size (not a measurement until it's entered)
110
+ const shape: Constraint[] = [
111
+ { type: 'h', a: cb.bl, b: cb.br, elev: true }, { type: 'h', a: cb.tl, b: cb.tr, elev: true },
112
+ { type: 'v', a: cb.bl, b: cb.tl, elev: true }, { type: 'v', a: cb.br, b: cb.tr, elev: true },
113
+ ];
114
+ for (const c of shape) tryAddConstraint(c, { silent: true });
115
+ const w = tryAddConstraint({ type: 'dist', kind: 'h', a: cb.bl, b: cb.br, value: CAB.w, driving: true, sign: 1, auto: true, elev: true }, { silent: true });
116
+ const h = tryAddConstraint({ type: 'dist', kind: 'v', a: cb.br, b: cb.tr, value: CAB.h, driving: true, sign: 1, auto: true, elev: true }, { silent: true });
117
+ if (w.ok && w.id) cb.w = w.id;
118
+ if (h.ok && h.id) cb.h = h.id;
119
+ // (placed, not selected: tap it to size it)
120
+ ed.sel = [];
121
+ afterEdit();
122
+ return id;
123
+ }
124
+
125
+ /** A level line all the way across the wall, `z` above the floor (a counter, a chair rail), held there by a height dimension. */
126
+ export function addHeightLine(z: number): string | null {
127
+ const f = elev.frame;
128
+ if (!f) return null;
129
+ if (z <= 0 || z >= f.H) { toast('Enter a height between the floor and the ceiling (' + fmtLen(f.H) + ').', { warn: true }); return null; }
130
+ pushHistory();
131
+ const face = curFace(), right = f.leftEnd === 'a' ? 'b' : 'a';
132
+ const pa = addPoint(face, f.face[0], z), pb = addPoint(face, f.face[1], z), id = addLine(face, pa, pb);
133
+ for (const c of [
134
+ { type: 'online', p: pa, l: endEdge(f.leftEnd) }, { type: 'online', p: pb, l: endEdge(right) }, { type: 'h', a: pa, b: pb },
135
+ { type: 'dist', kind: 'v', a: cornerAnchor(f.leftEnd), b: pa, value: z, driving: true, sign: 1 },
136
+ ] as Constraint[]) tryAddConstraint(c, { silent: true });
137
+ ed.sel = [];
138
+ afterEdit();
139
+ return id;
140
+ }
141
+
142
+ /** The face dimension between two points across ('h') or up ('v') the wall, if there is one. */
143
+ export function faceDimBetween(face: WallFace, kind: 'h' | 'v', a: string, b: string): string | null {
144
+ for (const [id, c] of Object.entries(face.cons)) {
145
+ if (c.type === 'dist' && c.kind === kind && ((c.a === a && c.b === b) || (c.a === b && c.b === a))) return id;
146
+ }
147
+ return null;
148
+ }
149
+
150
+ /**
151
+ * Dimension from `a` to `b`, across or up the wall: the keypad, then it's set — a driving dimension the wall view
152
+ * draws (along the floor or beside what it measures). An existing one between them is changed instead.
153
+ */
154
+ export function dimensionFace(kind: 'h' | 'v', a: string, b: string, title: string): void {
155
+ const face = curFace(), had = faceDimBetween(face, kind, a, b);
156
+ if (had) { editDim(had); return; }
157
+ if (!face.points[a] || !face.points[b]) return;
158
+ const cur = measure(face, { type: 'dist', kind, a, b });
159
+ openKeypad({
160
+ title, initial: fmtLen(cur), selectAll: true, laser: true,
161
+ onDone: v => {
162
+ if (v < 0) return;
163
+ const face = curFace(), pa = face.points[a], pb = face.points[b];
164
+ if (!pa || !pb) return;
165
+ pushHistory();
166
+ const sign = (kind === 'h' ? pb.x - pa.x : pb.y - pa.y) < -1e-9 ? -1 : 1;
167
+ const r = tryAddConstraint({ type: 'dist', kind, a, b, value: v, driving: true, sign, elev: true }, { onResolved: () => afterEdit() });
168
+ if (!r.ok) dropHistory();
169
+ // a grey chip that was selected is this dimension now (a selected cabinet stays selected)
170
+ else if (r.id && ed.sel.length === 1 && ed.sel[0].type === 'elev') ed.sel = [{ type: 'dim', id: r.id }];
171
+ afterEdit();
172
+ },
173
+ });
174
+ }
175
+
176
+ /** The floor point a cabinet's height above the floor is measured from. */
177
+ const floorAnchor = (): string => cornerAnchor(elev.frame?.leftEnd ?? 'a');
178
+
179
+ /** A cabinet's dimension that sets its width, height, or height off the floor (null if there's none). */
180
+ export function cabDim(face: WallFace, cabId: string, part: 'w' | 'h' | 'z'): string | null {
181
+ const cb = face.cabs?.[cabId];
182
+ if (!cb) return null;
183
+ if (part !== 'z') return cb[part] && face.cons[cb[part]!] ? cb[part]! : null;
184
+ for (const [id, c] of Object.entries(face.cons)) {
185
+ const ends = [c.a, c.b];
186
+ if (c.type === 'dist' && c.kind === 'v' && ends.some(v => !!v && !!face.points[v]?.anchor && face.points[v].y === 0) && ends.some(v => v === cb.bl || v === cb.br)) return id;
187
+ }
188
+ return null;
189
+ }
190
+
191
+ /** The keypad for a cabinet's width, height or height off the floor (its standard size until it's entered). */
192
+ export function editCabinet(cabId: string, part: 'w' | 'h' | 'z'): void {
193
+ const face = curFace(), cb = face.cabs?.[cabId];
194
+ if (!cb) return;
195
+ const had = cabDim(face, cabId, part);
196
+ if (had) { const keep = ed.sel; editDim(had); ed.sel = keep; updateCtx(); return; }
197
+ if (part === 'z') { dimensionFace('v', floorAnchor(), cb.bl, 'Cabinet off the floor'); return; }
198
+ const [a, b] = part === 'w' ? [cb.bl, cb.br] : [cb.br, cb.tr];
199
+ dimensionFace(part === 'w' ? 'h' : 'v', a, b, part === 'w' ? 'Cabinet width' : 'Cabinet height');
200
+ }
201
+
202
+ /** A cabinet's depth (out from the wall): a note on it, not drawn in the elevation. */
203
+ export function editCabinetDepth(cabId: string): void {
204
+ const cb = curFace().cabs?.[cabId];
205
+ if (!cb) return;
206
+ openKeypad({
207
+ title: 'Cabinet depth', sub: 'From the wall out into the room', initial: fmtLen(cb.depth), selectAll: true, laser: true,
208
+ onDone: v => { if (v <= 0) return; pushHistory(); cb.depth = v; cb.depthSet = true; afterEdit(); },
209
+ });
210
+ }
211
+
212
+ /** Delete cabinets (their corners, edges, shape and size go with them). */
213
+ export function deleteCabinets(ids: string[]): void {
214
+ const face = curFace();
215
+ for (const id of ids) {
216
+ const cb = face.cabs?.[id];
217
+ if (!cb) continue;
218
+ for (const p of cabPoints(cb)) delete face.points[p];
219
+ delete face.cabs[id];
220
+ }
221
+ }
222
+
223
+ /** Select a cabinet. */
224
+ export function selectCabinet(id: string): void {
225
+ ed.sel = [{ type: 'cab', id }];
226
+ updateCtx(); requestDraw();
227
+ }
@@ -6,22 +6,26 @@ import { seedProject } from '../model/demo';
6
6
  import { clearAllHistory } from '../model/history';
7
7
  import { projectFileName, projectFromFile, projectToFile } from '../model/projectfile';
8
8
  import { sessionState } from '../model/session';
9
- import { page, saveSoon, SK, store } from '../model/store';
9
+ import { settings } from '../model/settings';
10
+ import { page, planSK, saveSoon, store } from '../model/store';
10
11
  import type { Project } from '../model/types';
11
12
  import { fitView } from '../render/view';
12
- import { saveFile, toast, updateTopBar } from './ports';
13
+ import { refreshDrawerSoon, saveFile, toast, updateTopBar } from './ports';
14
+ import { closeElevation } from './elevation';
15
+ import { switchPage } from './project';
16
+ import { uid } from '../model/ids';
13
17
 
14
18
  /* Files in and out: PDF and DXF of the page, the whole project as a .boldr.json file, and starting over. */
15
19
 
16
20
  export function exportPdf(): void {
17
21
  const name = pdfFileName(store.project.name, page().name);
18
- saveFile(name, 'application/pdf', sketchToPdf(SK(), { project: store.project.name, subtitle: page().name }));
22
+ saveFile(name, 'application/pdf', sketchToPdf(planSK(), { project: store.project.name, subtitle: page().name, cabinets: settings.layers.cabinets }));
19
23
  toast('Exported ' + name);
20
24
  }
21
25
 
22
26
  export function exportDxf(): void {
23
27
  const name = dxfFileName(store.project.name, page().name);
24
- saveFile(name, 'application/dxf', sketchToDxf(SK()));
28
+ saveFile(name, 'application/dxf', sketchToDxf(planSK(), { cabinets: settings.layers.cabinets }));
25
29
  toast('Exported ' + name + ' (inches)');
26
30
  }
27
31
 
@@ -47,6 +51,38 @@ export function openProject(p: Project): void {
47
51
  toast('Opened ' + p.name, { action: { label: 'Undo', fn: () => { show(before.project, before.page); toast('Back to ' + before.project.name); } } });
48
52
  }
49
53
 
54
+ /**
55
+ * Open file (the ⋯ menu): a project file's pages are added to the drawing that's open — nothing in it is replaced —
56
+ * and the first one is shown. A page named like one already here takes the file's project name in front (and a
57
+ * number if it still clashes). The message's Undo takes them out again. Saved with the rest when the drawing is.
58
+ */
59
+ export function importPagesText(text: string): void {
60
+ let p: Project;
61
+ try { p = projectFromFile(text); } catch (e) { toast((e as Error).message, { warn: true }); return; }
62
+ closeElevation();
63
+ const names = new Set(store.project.pages.map(x => x.name)), from = p.name && p.name !== store.project.name ? p.name + ' · ' : '';
64
+ const added = p.pages.map(pg => {
65
+ let name = names.has(pg.name) ? from + pg.name : pg.name;
66
+ for (let n = 2, base = name; names.has(name); n++) name = base + ' ' + n;
67
+ names.add(name);
68
+ return { ...pg, id: uid('pg'), name, view: null };
69
+ });
70
+ const back = store.curPageId, ids = new Set(added.map(x => x.id));
71
+ store.project.pages.push(...added);
72
+ switchPage(added[0].id);
73
+ refreshDrawerSoon();
74
+ const n = added.length;
75
+ toast('Added ' + (n === 1 ? 'a page' : n + ' pages') + ' from ' + p.name, {
76
+ action: { label: 'Undo', fn: () => {
77
+ if (!store.project.pages.some(x => !ids.has(x.id))) return;
78
+ if (ids.has(store.curPageId)) switchPage(store.project.pages.some(x => x.id === back) ? back : store.project.pages.find(x => !ids.has(x.id))!.id);
79
+ store.project.pages = store.project.pages.filter(x => !ids.has(x.id));
80
+ updateTopBar(); refreshDrawerSoon(); saveSoon();
81
+ toast('Import taken out');
82
+ } },
83
+ });
84
+ }
85
+
50
86
  /** Open a project file's text; a message says what's wrong if it isn't one. */
51
87
  export function openProjectText(text: string): void {
52
88
  try { openProject(projectFromFile(text)); }
@@ -1,18 +1,26 @@
1
1
  import { redo, undo } from '../app/edit';
2
- import { ed } from '../app/state';
2
+ import { ed, type Preview, type Snap } from '../app/state';
3
3
  import { hyp } from '../model/geom';
4
4
  import { revertLast } from '../model/history';
5
5
  import { onSessionReset } from '../model/session';
6
- import { saveSoon } from '../model/store';
7
- import { panBy, requestDraw, s2w, zoomAt } from '../render/view';
6
+ import { saveViewSoon, setSaveIdle } from '../model/store';
7
+ import { paintPending, panBy, requestDraw, s2w, zoomAt } from '../render/view';
8
8
  import { tools, type PEv } from '../tools/registry';
9
- import { dismissPopovers, setCursor } from './ports';
9
+ import { closeKeypad, dismissConflict, dismissPopovers, interacting, setCursor, updateCtx, updateHint } from './ports';
10
+ import { intoWall } from './elevation';
11
+ import { keypadOnHost, keypadOpen, keypadSpec } from './keypad';
12
+ import { conflictOpen } from './conflict';
13
+ import { confirmCancel, confirmOpen } from './confirm';
14
+ import { readOnlyTap } from './linkActions';
15
+ import type { SelItem } from '../model/types';
10
16
 
11
17
  /*
12
18
  Pointer routing for the plan canvas, whatever delivers the pointers (DOM pointer events, React Native touches).
13
19
  Pen draws, fingers navigate: once a pen has been seen, touch only pans and zooms. Without a pen, one finger drives
14
20
  the tool and a second finger turns the action into a pan/pinch. Two-finger tap undoes, three-finger tap redoes.
15
21
  Mouse: middle or right button, or Space held, drags to pan; the wheel zooms.
22
+ With a sheet up over the drawing (the keypad, a conflict, a question), the drawing only pans and zooms, by any
23
+ pointer, and a tap on it closes the sheet. View only, a tap on a link's tile asks to open what it's for.
16
24
  */
17
25
 
18
26
  /**
@@ -28,17 +36,69 @@ export interface PointerIn {
28
36
  interface Gesture { t0: number; max: number; moved: number; last: GState | null }
29
37
  interface GState { x: number; y: number; d: number; n: number }
30
38
 
39
+ /**
40
+ * A host that pans and zooms the view itself (a device does it on its UI thread, so a pinch never waits for this
41
+ * thread): fingers still end a tool's action and two- and three-finger taps still undo and redo here, but the view is
42
+ * left to the host, which hands it back with setViewFromHost.
43
+ */
44
+ let hostNavigates = false;
45
+ export function setHostNavigation(on: boolean): void { hostNavigates = on; }
46
+
47
+ /**
48
+ * Past this far (screen px) pointers on the drawing are moving — a stroke, a drag, a pan or a pinch — not tapping. It
49
+ * tells a two-finger tap (undo) from a pan or pinch, and it's when the interface hears that a gesture is in motion
50
+ * (the `interacting` port: a phone's chrome steps aside) until the last pointer lifts — so the chrome never blinks on
51
+ * a tap.
52
+ */
53
+ const MOTION_PX = 14;
54
+
31
55
  const ptrs = new Map<number, PointerIn>();
32
- let penSeen = false, gesture: Gesture | null = null, primary: PointerIn | null = null, panDrag: { x: number; y: number; id: number } | null = null;
56
+ let penSeen = false, gesture: Gesture | null = null, primary: PointerIn | null = null;
57
+ /** close: a press under a sheet (see sheetUp) — a tap closes it; open: a view-only press — a tap opens a link's tile */
58
+ let panDrag: { x: number; y: number; id: number; moved: number; close?: boolean; open?: boolean } | null = null;
59
+ /** where the primary pointer went down, and whether a gesture on the drawing is in motion */
60
+ let primaryAt: { x: number; y: number } | null = null, inMotion = false;
61
+ /**
62
+ * What a press can change, as it was before the primary pointer went down: if a second finger turns the press into a
63
+ * pan or pinch, it's put back (see cancelPrimary). A tool's own state is kept a level deep — its presses set its fields,
64
+ * they don't change what's in them.
65
+ */
66
+ let beforePress: { tool: string; st: unknown; sel: SelItem[]; preview: Preview | null; snap: Snap | null } | null = null;
33
67
  let wheelT: ReturnType<typeof setTimeout> | undefined;
34
68
  export const keys = { spaceDown: false };
35
69
  onSessionReset(() => {
36
- ptrs.clear(); penSeen = false; gesture = null; primary = null; panDrag = null; keys.spaceDown = false;
70
+ ptrs.clear(); penSeen = false; gesture = null; primary = null; panDrag = null; keys.spaceDown = false; primaryAt = null; beforePress = null;
71
+ setMotion(false);
37
72
  clearTimeout(wheelT);
38
73
  });
39
74
 
75
+ // (a save waits until nothing is pressed on the drawing: see setSaveIdle)
76
+ setSaveIdle(() => ptrs.size === 0);
77
+
78
+ /**
79
+ * A sheet is up over the drawing: the keypad (not one that leaves the drawing live, nor the host's own, which covers
80
+ * it) or a conflict to resolve. On a tablet or a computer the drawing stays in reach around it (react/CadEditor.tsx):
81
+ * it can be panned and zoomed to see what's being measured, and a tap on it closes the sheet — nothing on it is
82
+ * picked or changed meanwhile. (A phone's sheet has a backdrop: the drawing gets no pointers at all.)
83
+ */
84
+ export const sheetUp = (): boolean => (keypadOpen() && !keypadSpec()?.modeless && !keypadOnHost()) || conflictOpen() || confirmOpen();
85
+
86
+ /** Pointers only move the view: a drawing that can't be edited, or one with a sheet up over it. */
87
+ export const navigationOnly = (): boolean => ed.readOnly || sheetUp();
88
+
89
+ function closeSheet(): void {
90
+ if (keypadOpen()) closeKeypad(); else if (confirmOpen()) confirmCancel(); else dismissConflict();
91
+ }
92
+
93
+ function setMotion(on: boolean): void {
94
+ if (on === inMotion) return;
95
+ inMotion = on;
96
+ interacting(on);
97
+ }
98
+
40
99
  function evOf(p: PointerIn): PEv {
41
- const [wx, wy] = s2w(p.x, p.y);
100
+ // in the wall view, tools work inside the wall
101
+ const [wx, wy] = intoWall(...s2w(p.x, p.y));
42
102
  return { sx: p.x, sy: p.y, wx, wy, touch: p.kind !== 'mouse', shift: !!p.shift };
43
103
  }
44
104
 
@@ -62,10 +122,14 @@ function updateGesture(): void {
62
122
  const s = gState(ts), L = g.last;
63
123
  if (!s || !L) { g.last = s; return; }
64
124
  if (s.n !== L.n) { g.last = s; return; }
65
- const dx = s.x - L.x, dy = s.y - L.y;
66
- g.moved += hyp(dx, dy);
67
- panBy(dx, dy);
68
- if (s.n >= 2 && L.d > 0 && s.d > 0) { g.moved += Math.abs(s.d - L.d); zoomAt(s.x, s.y, s.d / L.d); }
125
+ const dx = s.x - L.x, dy = s.y - L.y, pinch = s.n >= 2 && L.d > 0 && s.d > 0;
126
+ // (how far the fingers went tells a tap — undo, redo — from a pan or pinch, whoever moves the view)
127
+ g.moved += hyp(dx, dy) + (pinch ? Math.abs(s.d - L.d) : 0);
128
+ if (g.moved >= MOTION_PX) setMotion(true);
129
+ if (!hostNavigates) {
130
+ panBy(dx, dy);
131
+ if (pinch) zoomAt(s.x, s.y, s.d / L.d);
132
+ }
69
133
  g.last = s;
70
134
  }
71
135
 
@@ -73,25 +137,43 @@ function endGesture(t: number): void {
73
137
  const g = gesture;
74
138
  gesture = null;
75
139
  if (!g) return;
76
- if (g.moved < 14 && t - g.t0 < 380 && !ed.readOnly) { if (g.max === 2) undo(); else if (g.max >= 3) redo(); }
77
- saveSoon();
140
+ // (under a sheet a tap closes it, however many fingers: nothing is undone under it)
141
+ if (g.moved < MOTION_PX && sheetUp()) closeSheet();
142
+ // (view only: a finger's tap on a link's tile opens it, once the person says so)
143
+ else if (g.moved < MOTION_PX && ed.readOnly && g.max === 1 && g.last && t - g.t0 < 500) readOnlyTap(g.last.x, g.last.y, true);
144
+ else if (g.moved < MOTION_PX && t - g.t0 < 380 && !ed.readOnly) { if (g.max === 2) undo(); else if (g.max >= 3) redo(); }
145
+ saveViewSoon();
78
146
  }
79
147
 
80
- /** A second finger landed mid-action: abandon the primary pointer (reverting a drag in progress). */
148
+ /**
149
+ * A second finger landed mid-action: the first finger's press never happened. A drag in progress is reverted, and the
150
+ * tool, the selection and what's shown go back to how they were before it — so a shape being drawn keeps the points
151
+ * already placed (a rectangle's first corner, an arc's ends, a line's last point), nothing is left where the first
152
+ * finger landed (a line or rectangle to it, a mark under it), and the next tap carries on where it was.
153
+ */
81
154
  function cancelPrimary(): void {
82
- const t = curTool();
155
+ const t = curTool(), was = beforePress;
83
156
  if (t && t.st && t.st.dragging && t.st.drag) revertLast();
84
- if (t && t.st && t.st.down) t.st = {};
157
+ if (t && was && was.tool === ed.tool) {
158
+ t.st = was.st;
159
+ ed.sel = was.sel; ed.preview = was.preview; ed.snapState = was.snap;
160
+ } else if (t && t.reset) t.reset();
161
+ beforePress = null;
85
162
  primary = null;
86
163
  ed.boxSel = null;
87
- ed.preview = ed.tool === 'line' || ed.tool === 'wall' ? ed.preview : null;
88
- requestDraw();
164
+ updateCtx(); updateHint(); requestDraw();
89
165
  }
90
166
 
91
167
  function endPtr(e: PointerIn, cancel: boolean): void {
92
168
  const p = ptrs.get(e.id);
93
169
  ptrs.delete(e.id);
94
- if (panDrag && panDrag.id === e.id) { panDrag = null; setCursor(''); saveSoon(); return; }
170
+ if (panDrag && panDrag.id === e.id) {
171
+ const still = panDrag.moved < MOTION_PX && !cancel, close = !!panDrag.close && still, open = !!panDrag.open && still;
172
+ panDrag = null; setCursor(''); saveViewSoon();
173
+ if (close && sheetUp()) closeSheet();
174
+ else if (open) readOnlyTap(e.x, e.y, e.kind !== 'mouse');
175
+ return;
176
+ }
95
177
  if (gesture && p && p.kind === 'touch') { if (!touchList().length) endGesture(e.t); return; }
96
178
  if (primary && e.id === primary.id) {
97
179
  primary = null;
@@ -107,40 +189,74 @@ export function pointerDown(e: PointerIn): void {
107
189
  ed.lastPointerTouch = e.kind !== 'mouse';
108
190
  const p = { ...e };
109
191
  ptrs.set(e.id, p);
110
- if (e.kind === 'mouse' && (e.button === 1 || e.button === 2 || keys.spaceDown || ed.readOnly)) { panDrag = { x: p.x, y: p.y, id: e.id }; setCursor('grabbing'); return; }
192
+ // (under a sheet a pen moves the view as a mouse does, and a plain press of either that doesn't move closes it)
193
+ const under = sheetUp(), navOnly = under || ed.readOnly;
194
+ if ((e.kind === 'mouse' && (e.button === 1 || e.button === 2 || keys.spaceDown || navOnly)) || (e.kind === 'pen' && (under || ed.readOnly))) {
195
+ panDrag = { x: p.x, y: p.y, id: e.id, moved: 0, close: under && !e.button, open: ed.readOnly && !under && !e.button && !keys.spaceDown };
196
+ setCursor('grabbing');
197
+ return;
198
+ }
111
199
  if (e.kind === 'touch') {
112
200
  const touches = touchList();
113
- if (penSeen || touches.length >= 2 || ed.readOnly) {
201
+ if (penSeen || touches.length >= 2 || navOnly) {
114
202
  if (primary && primary.kind === 'touch') cancelPrimary();
115
- if (!gesture) startGesture(e.t); else gesture.max = Math.max(gesture.max, touches.length);
203
+ // (a finger joining one already moving the view: the pinch is measured from here, none of its first move lost)
204
+ if (!gesture) startGesture(e.t); else { gesture.max = Math.max(gesture.max, touches.length); gesture.last = gState(touches); }
116
205
  return;
117
206
  }
118
207
  }
119
- if (primary || ed.readOnly) return;
208
+ if (primary || navOnly) return;
120
209
  primary = p;
210
+ primaryAt = { x: e.x, y: e.y };
211
+ // (what it lands on is picked from the drawing as it is now, not as the last frame painted it)
212
+ paintPending();
121
213
  const t = curTool();
214
+ beforePress = { tool: ed.tool, st: t && t.st && typeof t.st === 'object' ? { ...t.st } : t?.st, sel: [...ed.sel], preview: ed.preview, snap: ed.snapState };
122
215
  if (t && t.down) t.down(evOf(e));
123
216
  }
124
217
 
125
218
  export function pointerMove(e: PointerIn): void {
126
219
  const p = ptrs.get(e.id);
127
- if (panDrag && panDrag.id === e.id) { panBy(e.x - panDrag.x, e.y - panDrag.y); panDrag.x = e.x; panDrag.y = e.y; return; }
220
+ if (panDrag && panDrag.id === e.id) {
221
+ panDrag.moved += hyp(e.x - panDrag.x, e.y - panDrag.y);
222
+ if (panDrag.moved >= MOTION_PX) setMotion(true);
223
+ panBy(e.x - panDrag.x, e.y - panDrag.y); panDrag.x = e.x; panDrag.y = e.y;
224
+ return;
225
+ }
128
226
  if (p) { p.x = e.x; p.y = e.y; }
129
227
  if (gesture && p && p.kind === 'touch') { updateGesture(); return; }
130
228
  const t = curTool();
131
229
  if (!t) return;
132
- if (primary && e.id === primary.id) { if (t.move) t.move(evOf(e)); }
133
- else if (!primary && e.kind !== 'touch' && !ed.readOnly) { ed.lastPointerTouch = false; if (t.hover) t.hover(evOf(e)); }
230
+ if (primary && e.id === primary.id) {
231
+ if (primaryAt && hyp(e.x - primaryAt.x, e.y - primaryAt.y) >= MOTION_PX) setMotion(true);
232
+ if (t.move) t.move(evOf(e));
233
+ }
234
+ else if (!primary && e.kind !== 'touch' && !navigationOnly()) { ed.lastPointerTouch = false; if (t.hover) t.hover(evOf(e)); }
235
+ }
236
+
237
+ export function pointerUp(e: PointerIn): void { endPtr(e, false); settle(); }
238
+ export function pointerCancel(e: PointerIn): void { endPtr(e, true); settle(); }
239
+
240
+ /** The last pointer lifted: nothing on the drawing is moving any more. */
241
+ function settle(): void {
242
+ if (ptrs.size) return;
243
+ primaryAt = null;
244
+ setMotion(false);
134
245
  }
135
246
 
136
- export const pointerUp = (e: PointerIn): void => endPtr(e, false);
137
- export const pointerCancel = (e: PointerIn): void => endPtr(e, true);
247
+ /**
248
+ * Every pointer the host still has down (a device says so as each new one lands): any other was lost — its lift
249
+ * never came — and is let go now, so it can't hold a gesture, or the chrome, open.
250
+ */
251
+ export function pointersStill(ids: number[]): void {
252
+ for (const [id, p] of [...ptrs]) if (!ids.includes(id)) pointerCancel({ ...p, t: Date.now() });
253
+ }
138
254
 
139
- /** A mouse left the canvas: drop hover highlights (the line tools keep their snap marker). */
255
+ /** A mouse left the canvas: drop hover highlights (the Line tool keeps its snap marker). */
140
256
  export function pointerLeave(kind: string): void {
141
257
  if (kind === 'mouse' && !primary) {
142
258
  ed.hover = null;
143
- if (ed.tool !== 'line' && ed.tool !== 'wall') ed.snapState = null;
259
+ if (ed.tool !== 'line') ed.snapState = null;
144
260
  requestDraw();
145
261
  }
146
262
  }
@@ -153,5 +269,5 @@ export function wheelZoom(x: number, y: number, dy: number, pinch: boolean, line
153
269
  if (pinch) zoomAt(x, y, Math.exp(-dy * 0.01));
154
270
  else zoomAt(x, y, Math.exp(-dy * (lines ? 0.06 : 0.0016)));
155
271
  clearTimeout(wheelT);
156
- wheelT = setTimeout(saveSoon, 400);
272
+ wheelT = setTimeout(saveViewSoon, 400);
157
273
  }