@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
@@ -1,38 +1,141 @@
1
- import { Canvas, Picture, type SkPicture } from '@shopify/react-native-skia';
2
- import { useEffect, useMemo, useRef, useState } from 'react';
1
+ import { Canvas, Group, Picture, Skia, type SkPicture, type Transforms3d } from '@shopify/react-native-skia';
2
+ import { memo, useEffect, useMemo, useRef } from 'react';
3
3
  import { View, type LayoutChangeEvent } from 'react-native';
4
4
  import { Gesture, GestureDetector, PointerType, type GestureTouchEvent, type TouchData } from 'react-native-gesture-handler';
5
+ import { runOnJS, useDerivedValue, useSharedValue } from 'react-native-reanimated';
5
6
  import { ed } from '../app/state';
6
- import { pointerCancel, pointerDown, pointerLeave, pointerMove, pointerUp, type PointerIn } from '../editor/gestures';
7
- import { drawPlan } from '../render/draw';
8
- import { cvs, fitView, setFrameRenderer, setImageLoader, setViewSize } from '../render/view';
7
+ import { navigationOnly, pointerCancel, pointerDown, pointerLeave, pointerMove, pointersStill, pointerUp, setHostNavigation } from '../editor/gestures';
8
+ import { drawPlanOverlay, drawPlanStatic, drawPlanUnder } from '../render/draw';
9
+ import {
10
+ carryTo, NAV_CANCEL, NAV_DOWN, NAV_MOVE, NAV_UP, navTouch, type NavState, type NavView, type UiView,
11
+ } from '../render/navigate';
12
+ import {
13
+ cvs, drawingChanged, fitView, imagesChanged, mainT, markDrawingKept, markImagesKept, paintPending, setFrameRenderer, setImageLoader, setTextMeasure, setViewFromHost, setViewSize, V, type Xform,
14
+ } from '../render/view';
9
15
  import { page } from '../model/store';
10
- import { recordPicture, skiaImageLoader } from '../skia/host';
16
+ import { recordLayers, skiaImageLoader, skiaTextMeasure } from '../skia/host';
11
17
 
12
18
  /*
13
- The plan on a device: each frame is recorded as a Skia picture by the shared renderer, and touches, Apple Pencil and
14
- a trackpad pointer reach editor/gestures.ts through one manual gesture (plus hover), as the browser's pointer events
15
- do. Pen draws and fingers navigate, as on the web.
19
+ The plan on a device. Pan and zoom never wait for the JavaScript thread: the fingers are followed on the UI thread
20
+ (a worklet), which moves the view and redraws at the screen's own rate (120 Hz on ProMotion), as a native app does.
21
+
22
+ The drawing itself is drawn on the JavaScript thread by the shared renderer, recorded as Skia pictures, and handed
23
+ to the canvas through shared values — so no frame goes through React. While a pinch runs, that thread draws the
24
+ plan again at the newest view as fast as it can, and until each new picture arrives the UI thread moves and scales
25
+ the last one to where the fingers have taken the view. Pictures reach past the screen's edges (OVERSCAN), so one
26
+ that has fallen a little behind a pinch out or a fling shows drawing, not an edge; what stays put on the screen (the
27
+ wall view's key plan, the conflict border) is drawn apart and never moves.
28
+
29
+ As a tool follows the pointer, only the moving parts (preview, snap marker, selection box) are drawn again; the
30
+ drawing is kept until it changes. Touches still reach editor/gestures.ts for the tools, Apple Pencil, and two- and
31
+ three-finger taps (undo, redo); it leaves the view to this canvas (setHostNavigation).
16
32
  */
17
33
 
18
- const kindOf = (t: PointerType): string => (t === PointerType.STYLUS ? 'pen' : t === PointerType.MOUSE ? 'mouse' : 'touch');
19
- const pin = (t: TouchData, kind: string): PointerIn => ({ id: t.id, kind, x: t.x, y: t.y, t: Date.now(), cx: t.absoluteX, cy: t.absoluteY });
34
+ /** How far past each edge of the screen a picture of the drawing reaches, as a share of the screen's longer side. */
35
+ const OVERSCAN = 0.5;
36
+ const EMPTY = Skia.Picture.MakePicture(null)!;
37
+
38
+ /** What the JavaScript thread drew last, and the view it drew it at. */
39
+ interface Frame {
40
+ /** the photos and plans under the drawing, the drawing, its moving parts, and what stays put on the screen (from each) */
41
+ under: SkPicture; base: SkPicture; overlay: SkPicture; baseFixed: SkPicture; overlayFixed: SkPicture;
42
+ /** the view it was drawn at, as a transform (s, ox, oy) on a W × H screen and as a view */
43
+ s: number; ox: number; oy: number; W: number; H: number; view: NavView;
44
+ /** the latest of the UI thread's views it was drawn at (see UiView) */
45
+ seq: number;
46
+ }
47
+ /** A touch as the UI thread hands it over: id, position on the canvas, position in the window. */
48
+ type Packed = [id: number, x: number, y: number, ax: number, ay: number];
49
+
50
+ /** The latest of the UI thread's views this thread has taken; each frame says which it includes. */
51
+ let hostSeq = 0;
20
52
 
21
- export function PlanCanvas() {
22
- const [picture, setPicture] = useState<SkPicture | null>(null);
53
+ /**
54
+ * Touches from the UI thread: the view first, if the fingers moved it, then each touch to the editor's gestures (as a
55
+ * pointer lands, `down` is every one still down: any other's lift was lost).
56
+ */
57
+ function touchesOnJS(type: number, kind: string, ts: Packed[], view: UiView | null, down: number[] | null): void {
58
+ if (view) { hostSeq = view.seq; setViewFromHost(view.s, view.cx, view.cy); }
59
+ if (down) pointersStill(down);
60
+ const fn = type === NAV_DOWN ? pointerDown : type === NAV_MOVE ? pointerMove : type === NAV_UP ? pointerUp : pointerCancel, t = Date.now();
61
+ for (const [id, x, y, ax, ay] of ts) fn({ id, kind, x, y, t, cx: ax, cy: ay });
62
+ // what a tool shows following the finger (a shape's outline, its marks, the snap), drawn now: left for the next frame
63
+ // it reached the screen a frame behind the finger. Only when nothing but those moving parts changed — the drawing
64
+ // itself, and the view, are drawn on the frame as before.
65
+ if (!view && !drawingChanged() && !imagesChanged()) paintPending();
66
+ }
67
+
68
+ const kindOf = (t: PointerType): string => {
69
+ 'worklet';
70
+ return t === PointerType.STYLUS ? 'pen' : t === PointerType.MOUSE ? 'mouse' : 'touch';
71
+ };
72
+ const pack = (ts: TouchData[]): Packed[] => {
73
+ 'worklet';
74
+ return ts.map(t => [t.id, t.x, t.y, t.absoluteX, t.absoluteY] as Packed);
75
+ };
76
+
77
+ export const PlanCanvas = memo(function PlanCanvas() {
23
78
  const first = useRef(true);
79
+ const frame = useSharedValue<Frame | null>(null);
80
+ const view = useSharedValue<UiView>({ s: 1, cx: 0, cy: 0, seq: 0 });
81
+ // the UI thread's own: the fingers down, whether they're navigating and where they were last, a pen seen
82
+ const nav = useSharedValue<NavState>({ fingers: {}, navigating: false, last: null, pen: false });
83
+ const readOnly = useSharedValue(false);
84
+
85
+ // the last pictures, carried from the view they were drawn at to the view the fingers have reached since
86
+ const shift = useDerivedValue<Transforms3d>(() => {
87
+ const f = frame.value, c = f && carryTo(f, view.value);
88
+ return c ? [{ translateX: c.tx }, { translateY: c.ty }, { scale: c.k }] : [];
89
+ });
90
+ const under = useDerivedValue(() => frame.value?.under ?? EMPTY);
91
+ const base = useDerivedValue(() => frame.value?.base ?? EMPTY);
92
+ const overlay = useDerivedValue(() => frame.value?.overlay ?? EMPTY);
93
+ const baseFixed = useDerivedValue(() => frame.value?.baseFixed ?? EMPTY);
94
+ const overlayFixed = useDerivedValue(() => frame.value?.overlayFixed ?? EMPTY);
24
95
 
25
96
  useEffect(() => {
26
97
  setImageLoader(skiaImageLoader);
98
+ // (a device has no browser canvas to measure a note's text with)
99
+ setTextMeasure(skiaTextMeasure);
100
+ setHostNavigation(true);
101
+ /**
102
+ * The drawing as last kept (and what of it stays put), the photos and plans under it, and the view and size they
103
+ * were drawn at. Each is drawn again only when it changed: turning or moving a photo redraws the photos alone,
104
+ * not every wall and dimension over them (a frame's worth of the JavaScript thread on a big plan).
105
+ */
106
+ let kept: { under: SkPicture; base: SkPicture; fixed: SkPicture; T: Xform; W: number; H: number } | null = null;
107
+ let wasReadOnly: boolean | null = null;
27
108
  setFrameRenderer(() => {
28
109
  const { W, H } = cvs;
29
110
  if (!W || !H) return;
30
- const { picture: p, result } = recordPicture(W, H, ctx => drawPlan(ctx, W, H));
31
- ed.hitChips = result;
32
- setPicture(p);
111
+ // (fingers only move the view: a drawing that can't be edited, or a sheet up over it — the keypad)
112
+ const navOnly = navigationOnly();
113
+ if (navOnly !== wasReadOnly) { wasReadOnly = navOnly; readOnly.value = navOnly; }
114
+ const T = mainT(), v = V();
115
+ // the drawing changed, the screen changed size, or the view moved (each step of a pinch: the UI thread carries
116
+ // the last picture along until this one arrives)
117
+ const moved = !kept || kept.W !== W || kept.H !== H || kept.T.s !== T.s || kept.T.ox !== T.ox || kept.T.oy !== T.oy;
118
+ const margin = Math.round(Math.max(W, H) * OVERSCAN);
119
+ let under = kept?.under, base = kept?.base, fixed = kept?.fixed;
120
+ if (moved || imagesChanged() || !under) {
121
+ under = recordLayers(W, H, margin, ctx => drawPlanUnder(ctx, W, H, { margin })).moving;
122
+ markImagesKept();
123
+ }
124
+ if (moved || drawingChanged() || !base || !fixed) {
125
+ const r = recordLayers(W, H, margin, (ctx, fx) => drawPlanStatic(ctx, W, H, { margin, fixed: fx, layer: 'above' }));
126
+ base = r.moving; fixed = r.fixed;
127
+ ed.hitChips = r.result;
128
+ markDrawingKept();
129
+ }
130
+ kept = { under, base, fixed, T, W, H };
131
+ const over = recordLayers(W, H, 0, (ctx, fx) => drawPlanOverlay(ctx, W, H, { fixed: fx }));
132
+ frame.value = {
133
+ under: kept.under, base: kept.base, overlay: over.moving, baseFixed: kept.fixed, overlayFixed: over.fixed,
134
+ s: T.s, ox: T.ox, oy: T.oy, W, H, view: { s: v.s, cx: v.cx, cy: v.cy }, seq: hostSeq,
135
+ };
33
136
  });
34
- return () => { setFrameRenderer(null); setImageLoader(null); };
35
- }, []);
137
+ return () => { setFrameRenderer(null); setImageLoader(null); setTextMeasure(null); setHostNavigation(false); };
138
+ }, [frame, readOnly]);
36
139
 
37
140
  const onLayout = (e: LayoutChangeEvent) => {
38
141
  const { width, height } = e.nativeEvent.layout;
@@ -42,25 +145,33 @@ export function PlanCanvas() {
42
145
  };
43
146
 
44
147
  const gesture = useMemo(() => {
148
+ /** A touch event on the UI thread: follow the fingers (render/navigate.ts), then hand the touches to this thread. */
149
+ const touch = (type: number, e: GestureTouchEvent) => {
150
+ 'worklet';
151
+ const kind = kindOf(e.pointerType), down = type === NAV_DOWN ? e.allTouches.map(t => t.id) : null;
152
+ const r = navTouch(nav.value, type, kind, e.changedTouches, down, readOnly.value, view.value, frame.value);
153
+ nav.value = r.st;
154
+ if (r.view !== view.value) view.value = r.view;
155
+ runOnJS(touchesOnJS)(type, kind, pack(e.changedTouches), r.moved ? r.view : null, down);
156
+ };
45
157
  const touches = Gesture.Manual()
46
- .runOnJS(true)
47
- .onTouchesDown((e: GestureTouchEvent, manager) => {
158
+ .onTouchesDown((e, manager) => {
159
+ 'worklet';
48
160
  manager.activate();
49
- const kind = kindOf(e.pointerType);
50
- for (const t of e.changedTouches) pointerDown(pin(t, kind));
161
+ touch(NAV_DOWN, e);
51
162
  })
52
- .onTouchesMove((e: GestureTouchEvent) => {
53
- const kind = kindOf(e.pointerType);
54
- for (const t of e.changedTouches) pointerMove(pin(t, kind));
163
+ .onTouchesMove(e => {
164
+ 'worklet';
165
+ touch(NAV_MOVE, e);
55
166
  })
56
- .onTouchesUp((e: GestureTouchEvent, manager) => {
57
- const kind = kindOf(e.pointerType);
58
- for (const t of e.changedTouches) pointerUp(pin(t, kind));
167
+ .onTouchesUp((e, manager) => {
168
+ 'worklet';
169
+ touch(NAV_UP, e);
59
170
  if (e.numberOfTouches === 0) manager.end();
60
171
  })
61
- .onTouchesCancelled((e: GestureTouchEvent, manager) => {
62
- const kind = kindOf(e.pointerType);
63
- for (const t of e.changedTouches) pointerCancel(pin(t, kind));
172
+ .onTouchesCancelled((e, manager) => {
173
+ 'worklet';
174
+ touch(NAV_CANCEL, e);
64
175
  manager.fail();
65
176
  });
66
177
  // a hovering Pencil (iPad Pro) or trackpad pointer shows snaps and highlights before it touches down
@@ -69,15 +180,21 @@ export function PlanCanvas() {
69
180
  .onUpdate(e => pointerMove({ id: -1, kind: kindOf(e.pointerType), x: e.x, y: e.y, t: Date.now() }))
70
181
  .onEnd(() => pointerLeave('mouse')); // drops hover highlights, whichever pointer hovered
71
182
  return Gesture.Simultaneous(touches, hover);
72
- }, []);
183
+ }, [frame, nav, readOnly, view]);
73
184
 
74
185
  return (
75
186
  <GestureDetector gesture={gesture}>
76
187
  <View style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }} onLayout={onLayout} accessibilityLabel="Drawing canvas" testID="cv">
77
188
  <Canvas style={{ flex: 1 }}>
78
- {picture && <Picture picture={picture} />}
189
+ <Group transform={shift}>
190
+ <Picture picture={under} />
191
+ <Picture picture={base} />
192
+ <Picture picture={overlay} />
193
+ </Group>
194
+ <Picture picture={baseFixed} />
195
+ <Picture picture={overlayFixed} />
79
196
  </Canvas>
80
197
  </View>
81
198
  </GestureDetector>
82
199
  );
83
- }
200
+ });
@@ -1,10 +1,9 @@
1
1
  import { useEffect, useRef } from 'react';
2
- import { elevKeyDown, elevOpen } from '../editor/elevation';
3
2
  import { pointerCancel, pointerDown, pointerLeave, pointerMove, pointerUp, wheelZoom, type PointerIn } from '../editor/gestures';
4
- import { keypadOpen } from '../editor/keypad';
5
3
  import { keyDown, keyUp, type KeyIn } from '../editor/keys';
6
4
  import { page } from '../model/store';
7
5
  import { fitView, initCanvas, resize, setFrameRenderer } from '../render/view';
6
+ import { PAGE_LOCK_ROOT } from './pageLock';
8
7
  import { setCursorTarget } from './ports';
9
8
 
10
9
  /*
@@ -15,6 +14,18 @@ import { setCursorTarget } from './ports';
15
14
 
16
15
  const keyIn = (e: KeyboardEvent): KeyIn => ({ key: e.key, shift: e.shiftKey, mod: e.metaKey || e.ctrlKey, alt: e.altKey });
17
16
 
17
+ /**
18
+ * Whether a key is the editor's: pressed in it, or with nothing focused — not typed into a field, nor pressed in the
19
+ * host's own UI over the drawing (a picker, a measurement's detail), which is outside the editor's root. Delete there
20
+ * would otherwise delete what's selected on the drawing.
21
+ */
22
+ function forEditor(e: KeyboardEvent): boolean {
23
+ const t = e.target instanceof HTMLElement ? e.target : null;
24
+ if (!t || t === document.body || t === document.documentElement) return true;
25
+ if (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.tagName === 'SELECT' || t.isContentEditable) return false;
26
+ return !!t.closest('#' + PAGE_LOCK_ROOT);
27
+ }
28
+
18
29
  export function PlanCanvas() {
19
30
  const ref = useRef<HTMLCanvasElement>(null);
20
31
  useEffect(() => {
@@ -52,10 +63,9 @@ export function PlanCanvas() {
52
63
  // capture phase: a key the editor uses (Enter on the keypad, Space to pan, …) must not also press whatever button
53
64
  // last took focus
54
65
  document.addEventListener('keydown', e => {
55
- const tag = (e.target && (e.target as HTMLElement).tagName) || '';
56
- if (tag === 'INPUT' || tag === 'TEXTAREA') return;
66
+ if (!forEditor(e)) return;
57
67
  const k = keyIn(e);
58
- if (!keypadOpen() && elevOpen() ? elevKeyDown(k) : keyDown(k)) { e.preventDefault(); e.stopPropagation(); }
68
+ if (keyDown(k)) { e.preventDefault(); e.stopPropagation(); }
59
69
  }, { capture: true, signal: ac.signal });
60
70
  document.addEventListener('keyup', e => keyUp(keyIn(e)), { capture: true, signal: ac.signal });
61
71
  return () => { ac.abort(); ro.disconnect(); setCursorTarget('plan', null); };
@@ -1,48 +1,85 @@
1
1
  import { useEffect, useRef, useState, type ReactNode } from 'react';
2
- import { Pressable, ScrollView, StyleSheet, Switch, Text, TextInput, View } from 'react-native';
3
- import { disarmReset, exportDxf, exportPdf, openProjectText, resetArmed, resetToDemo, saveProjectFile } from '../editor/files';
4
- import { NOTE_COLORS, setNoteColor } from '../editor/markup';
5
- import { setPreference, togglePreference } from '../editor/project';
6
- import { ed } from '../app/state';
2
+ import { Animated, Text, TextInput, View, type ViewStyle } from 'react-native';
3
+ import { disarmReset, exportDxf, exportPdf, importPagesText, openProjectText, resetArmed, resetToDemo, saveProjectFile } from '../editor/files';
4
+ import {
5
+ editDefaultWallHeight, LAYER_ROWS, layerOn, setDefaultWallHeight, setPrecision, setPreference, setUnits,
6
+ toggleLayer, togglePreference, WALL_PRESETS, type UnitChoice,
7
+ } from '../editor/project';
8
+ import { page } from '../model/store';
9
+ import { fitView } from '../render/view';
10
+ import { command3d, is3d } from '../editor/view3dState';
11
+ import { fmtLen, followingHostUnits } from '../units';
7
12
  import { settings } from '../model/settings';
13
+ import { Toggle } from './Buttons';
14
+ import { FitScroll } from './FitScroll';
8
15
  import { pickTextFile } from './files';
9
- import { notify, ui } from './store';
16
+ import { editorVersion, notify, ui, useEditorKey } from './store';
10
17
  import { C, FONT, shadow } from './theme';
18
+ import { Segmented, Tap, usePopup } from './motion';
19
+ import { createSheet } from './corners';
11
20
 
12
21
  const closeMenu = () => { ui.menu = false; disarmReset(); notify(); };
13
22
 
14
23
  function MenuLink({ label, onPress, danger }: { label: string; onPress: () => void; danger?: boolean }) {
15
24
  return (
16
- <Pressable accessibilityRole="button" accessibilityLabel={label} onPress={onPress} style={s.link}>
25
+ <Tap feel="dim" accessibilityRole="button" accessibilityLabel={label} onPress={onPress} style={s.link}>
17
26
  <Text style={[s.linkText, danger && { color: C.softRed }]}>{label}</Text>
18
- </Pressable>
27
+ </Tap>
19
28
  );
20
29
  }
21
30
 
22
31
  function Seg<T extends string | number>({ value, opts, onPick }: { value: T; opts: [T, string][]; onPick: (v: T) => void }) {
23
- return (
24
- <View style={s.seg}>
25
- {opts.map(([v, l]) => (
26
- <Pressable key={String(v)} accessibilityRole="button" accessibilityLabel={l} accessibilityState={{ selected: v === value }} onPress={() => onPick(v)}
27
- style={[s.segBtn, v === value && { backgroundColor: '#fff' }]}>
28
- <Text style={[s.segText, v === value && { color: C.dark }]}>{l}</Text>
29
- </Pressable>
30
- ))}
31
- </View>
32
- );
32
+ return <Segmented value={value} options={opts} onPick={onPick} style={s.seg} optionStyle={s.segBtn} textStyle={s.segText} onText={C.dark} offText={C.lg1} />;
33
33
  }
34
34
 
35
35
  const MRow = ({ label, children }: { label: string; children: ReactNode }) => (
36
36
  <View style={s.mrow}><Text style={s.rowText}>{label}</Text>{children}</View>
37
37
  );
38
38
 
39
- /** Settings and files (the ••• button). `units` false: units come from the host (the person's BOLDR settings). */
40
- export function Menu({ units = true, devTools = true, themeChoice = true }: { units?: boolean; devTools?: boolean; themeChoice?: boolean }) {
41
- if (!ui.menu) return null;
39
+ /**
40
+ * The top bar's menus: Layers, or ••• (on a phone Fit to screen, then exports, opening a file, settings). `accountUnits`: the host formats
41
+ * lengths (BOLDR: the person's account settings), offered as Units → Account beside the editor's own units.
42
+ */
43
+ export function Menu({ accountUnits = false, devTools = true, themeChoice = true, pickText = pickTextFile, readOnly = false, phone = false }: {
44
+ accountUnits?: boolean; devTools?: boolean; themeChoice?: boolean;
45
+ /** how to pick a .boldr.json file's text (a device's host passes its own); the browser's file picker by default */
46
+ pickText?: () => Promise<string | null>;
47
+ readOnly?: boolean;
48
+ /** a phone's top bar has no room for Fit to screen: it's the menu's first item there */
49
+ phone?: boolean;
50
+ }) {
51
+ // (open only briefly: while it is, it follows every change)
52
+ useEditorKey(() => (ui.menu ? ui.menu + editorVersion() : ''));
53
+ // two menus in the one spot, each dropping down from its button (top right) and kept built once opened, so it opens
54
+ // at once the next time; one swapped for the other fades across
55
+ const more = usePopup(ui.menu === 'more', { y: -6 }, 'top right', true), layers = usePopup(ui.menu === 'layers', { y: -6 }, 'top right', true);
56
+ return (
57
+ <>
58
+ {more.mounted && <MoreMenu open={ui.menu === 'more'} style={more.style} {...{ accountUnits, devTools, themeChoice, pickText, readOnly, phone }} />}
59
+ {layers.mounted && <LayersMenu open={ui.menu === 'layers'} style={layers.style} />}
60
+ </>
61
+ );
62
+ }
63
+
64
+ /** ••• : the exports, opening a file, and the settings. */
65
+ function MoreMenu({ open, style, accountUnits, devTools, themeChoice, pickText, readOnly, phone }: {
66
+ open: boolean; style: Animated.WithAnimatedObject<ViewStyle>;
67
+ accountUnits: boolean; devTools: boolean; themeChoice: boolean; pickText: () => Promise<string | null>; readOnly: boolean; phone: boolean;
68
+ }) {
42
69
  const run = (fn: () => void) => () => { closeMenu(); fn(); };
43
70
  return (
44
- <View style={s.menu} accessibilityLabel="Settings" testID="menu">
45
- <ScrollView>
71
+ <Animated.View style={[s.menu, style]} pointerEvents={open ? 'auto' : 'none'} accessibilityLabel="Settings" testID={open ? 'menu' : undefined}>
72
+ <FitScroll>
73
+ {phone && <MenuLink label="Fit to screen" onPress={run(() => (is3d() ? command3d('fit') : fitView()))} />}
74
+ <MenuLink label="Export page as PDF (.pdf)" onPress={run(exportPdf)} />
75
+ <MenuLink label="Export page as DXF (.dxf)" onPress={run(exportDxf)} />
76
+ <View style={s.hr} />
77
+ {!readOnly && (
78
+ <>
79
+ <MenuLink label="Open file… (adds its pages)" onPress={run(() => { pickText().then(t => { if (t != null) importPagesText(t); }, () => {}); })} />
80
+ <View style={s.hr} />
81
+ </>
82
+ )}
46
83
  {devTools && (
47
84
  <>
48
85
  <MenuLink label="Save project to file…" onPress={run(saveProjectFile)} />
@@ -50,21 +87,31 @@ export function Menu({ units = true, devTools = true, themeChoice = true }: { un
50
87
  <View style={s.hr} />
51
88
  </>
52
89
  )}
53
- <MenuLink label="Export page as PDF (.pdf)" onPress={run(exportPdf)} />
54
- <MenuLink label="Export page as DXF (.dxf)" onPress={run(exportDxf)} />
55
- <View style={s.hr} />
56
- {units && (
57
- <>
58
- <MRow label="Units"><Seg value={settings.unit} opts={[['ftin', 'ft-in'], ['in', 'in'], ['mm', 'mm']]} onPick={v => setPreference('unit', v)} /></MRow>
59
- <MRow label="Precision"><Seg value={settings.denom} opts={[[16, '1/16"'], [8, '1/8"'], [4, '1/4"']]} onPick={v => setPreference('denom', v)} /></MRow>
60
- </>
90
+ <MRow label="Units">
91
+ <Seg<UnitChoice>
92
+ value={accountUnits && followingHostUnits() ? 'account' : settings.unit}
93
+ opts={[...(accountUnits ? [['account', 'Account'] as [UnitChoice, string]] : []), ['ftin', 'ft-in'], ['in', 'in'], ['mm', 'mm']]}
94
+ onPick={setUnits}
95
+ />
96
+ </MRow>
97
+ {!(accountUnits && followingHostUnits()) && settings.unit !== 'mm' && (
98
+ <MRow label="Precision"><Seg value={settings.denom} opts={[[16, '1/16"'], [8, '1/8"'], [4, '1/4"']]} onPick={setPrecision} /></MRow>
61
99
  )}
62
- <MRow label="Wall height"><Seg value={settings.wallH} opts={[[96, "8'"], [108, "9'"], [120, "10'"]]} onPick={v => setPreference('wallH', v)} /></MRow>
63
- <MRow label="Snap to grid"><Switch accessibilityLabel="Snap to grid" value={settings.snapGrid} onValueChange={() => togglePreference('snapGrid')} trackColor={{ true: C.blue, false: C.g3 }} thumbColor="#fff" /></MRow>
100
+ {/* 8', 9', 10' in a tap; Custom (showing the height when it's one) opens the keypad */}
101
+ <MRow label="Wall height">
102
+ <Seg value={WALL_PRESETS.includes(settings.wallH) ? settings.wallH : -1}
103
+ opts={[[96, "8'"], [108, "9'"], [120, "10'"], [-1, WALL_PRESETS.includes(settings.wallH) ? 'Custom' : fmtLen(settings.wallH)]]}
104
+ onPick={v => { if (v === -1) { closeMenu(); editDefaultWallHeight(); } else if (setDefaultWallHeight(v)) notify(); }} />
105
+ </MRow>
106
+ <MRow label="Snap to grid"><Toggle label="Snap to grid" value={settings.snapGrid} onChange={() => togglePreference('snapGrid')} /></MRow>
107
+ {/* cabinets, doors and windows snapping to corners and each other */}
108
+ <MRow label="Object snapping"><Toggle label="Object snapping" value={layerOn('objSnap')} onChange={() => { toggleLayer('objSnap'); notify(); }} /></MRow>
64
109
  {themeChoice && (
65
110
  <MRow label="Theme"><Seg value={settings.theme} opts={[['system', 'Auto'], ['light', 'Light'], ['dark', 'Dark']]} onPick={v => setPreference('theme', v)} /></MRow>
66
111
  )}
67
- <MRow label="Auto-constrain while drawing"><Switch accessibilityLabel="Auto-constrain while drawing" value={settings.autoCons} onValueChange={() => togglePreference('autoCons')} trackColor={{ true: C.blue, false: C.g3 }} thumbColor="#fff" /></MRow>
112
+ <MRow label="Auto-constrain while drawing"><Toggle label="Auto-constrain while drawing" value={settings.autoCons} onChange={() => togglePreference('autoCons')} /></MRow>
113
+ {/* picking a tool says what it does, for a moment, under the tabs */}
114
+ <MRow label="Tool tips"><Toggle label="Tool tips" value={settings.toolTips !== false} onChange={() => { togglePreference('toolTips'); notify(); }} /></MRow>
68
115
  {devTools && (
69
116
  <>
70
117
  <View style={s.hr} />
@@ -72,28 +119,46 @@ export function Menu({ units = true, devTools = true, themeChoice = true }: { un
72
119
  onPress={() => { if (resetToDemo()) closeMenu(); else notify(); }} />
73
120
  </>
74
121
  )}
75
- </ScrollView>
76
- </View>
122
+ </FitScroll>
123
+ </Animated.View>
77
124
  );
78
125
  }
79
126
 
80
- /** The markup colors (from the rail's color dot or the bar's Color). */
81
- export function Palette() {
82
- if (!ui.palette) return null;
127
+ /** A layer's switch: its name, what it shows beneath, the toggle. */
128
+ function LayerRow({ label, sub, value, onChange }: { label: string; sub?: string; value: boolean; onChange: () => void }) {
83
129
  return (
84
- <View style={s.palette} testID="palette">
85
- {NOTE_COLORS.map(c => (
86
- <Pressable key={c} accessibilityRole="button" accessibilityLabel={'Color ' + c} onPress={() => { setNoteColor(c); ui.palette = false; notify(); }}
87
- style={[s.swatch, { backgroundColor: c }, c === ed.noteColor && { borderColor: '#fff', borderWidth: 3 }]} />
88
- ))}
130
+ <View style={s.mrow}>
131
+ <View style={{ flexShrink: 1 }}>
132
+ <Text style={s.rowText}>{label}</Text>
133
+ {sub ? <Text style={s.rowSub}>{sub}</Text> : null}
134
+ </View>
135
+ <Toggle label={label} value={value} onChange={onChange} />
89
136
  </View>
90
137
  );
91
138
  }
92
139
 
140
+ /** The Layers menu: what's shown — the photos and plans under the drawing, then each layer. (An image's own lock, scale and
141
+ * opacity are in its bar when it's selected.) */
142
+ function LayersMenu({ open, style }: { open: boolean; style: Animated.WithAnimatedObject<ViewStyle> }) {
143
+ const ims = page().underlays, n = ims.length;
144
+ return (
145
+ <Animated.View style={[s.menu, s.layers, style]} pointerEvents={open ? 'auto' : 'none'} accessibilityLabel="Layers" testID={open ? 'layersMenu' : undefined}>
146
+ <FitScroll>
147
+ <LayerRow label={n > 1 ? 'Photos and plans' : 'Photo or plan underlay'}
148
+ sub={n > 1 ? n + ' images under the drawing' : n ? 'Traced image under the drawing' : 'None on this page'} value={settings.layers.underlay}
149
+ onChange={() => { toggleLayer('underlay'); notify(); }} />
150
+ <View style={s.hr} />
151
+ {LAYER_ROWS.map(([k, label, sub]) => <LayerRow key={k} label={label} sub={sub} value={layerOn(k)} onChange={() => { toggleLayer(k); notify(); }} />)}
152
+ </FitScroll>
153
+ </Animated.View>
154
+ );
155
+ }
156
+
93
157
  /** Typing a note where the text tool was tapped: Enter places it, Esc cancels, leaving the field places it too. */
94
158
  export function NoteInput() {
159
+ useEditorKey(() => (ui.note ? String(editorVersion()) : ''));
95
160
  const n = ui.note, [text, setText] = useState(''), ref = useRef<TextInput>(null), done = useRef(false);
96
- useEffect(() => { setText(''); done.current = false; if (n) setTimeout(() => ref.current?.focus(), 30); }, [n]);
161
+ useEffect(() => { setText(n?.initial ?? ''); done.current = false; if (n) setTimeout(() => ref.current?.focus(), 30); }, [n]);
97
162
  if (!n) return null;
98
163
  const finish = (commit: boolean) => {
99
164
  if (done.current) return;
@@ -103,24 +168,24 @@ export function NoteInput() {
103
168
  if (commit && text.trim()) cb(text.trim());
104
169
  };
105
170
  return (
106
- <TextInput ref={ref} accessibilityLabel="Note text" placeholder="Type a note, then Enter" placeholderTextColor={C.g4} value={text} onChangeText={setText}
171
+ <TextInput ref={ref} accessibilityLabel="Note text" selectTextOnFocus={!!n.initial} placeholder="Type a note, then Enter" placeholderTextColor={C.g4} value={text} onChangeText={setText}
107
172
  onSubmitEditing={() => finish(true)} onBlur={() => finish(true)}
108
173
  onKeyPress={e => { if (e.nativeEvent.key === 'Escape') finish(false); }}
109
174
  style={[s.note, { left: n.x, top: n.y - 18 }]} />
110
175
  );
111
176
  }
112
177
 
113
- const s = StyleSheet.create({
178
+ const s = createSheet({
114
179
  menu: { position: 'absolute', top: 66, right: 12, width: 320, maxHeight: '80%', backgroundColor: C.g1, borderRadius: 15, paddingVertical: 8, zIndex: 45, ...shadow },
115
180
  link: { paddingVertical: 10, paddingHorizontal: 16 },
116
181
  linkText: { fontFamily: FONT, fontSize: 14, color: '#fff' },
117
182
  hr: { height: 1, backgroundColor: C.g2, marginVertical: 6 },
118
183
  mrow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingVertical: 8, paddingHorizontal: 16, gap: 10 },
184
+ layers: { width: 340 },
185
+ rowSub: { fontFamily: FONT, fontSize: 12, color: C.g4, marginTop: 2 },
119
186
  rowText: { fontFamily: FONT, fontSize: 14, color: '#fff', flexShrink: 1 },
120
- seg: { flexDirection: 'row', backgroundColor: C.dark, borderRadius: 16, padding: 3 },
121
- segBtn: { paddingVertical: 5, paddingHorizontal: 10, borderRadius: 13 },
187
+ seg: { flexDirection: 'row', backgroundColor: C.dark, borderRadius: 999, padding: 3 },
188
+ segBtn: { paddingVertical: 5, paddingHorizontal: 10, borderRadius: 999 },
122
189
  segText: { fontFamily: FONT, fontSize: 12.5, fontWeight: '600', color: C.lg1 },
123
- palette: { position: 'absolute', left: 70, top: 300, flexDirection: 'row', gap: 8, backgroundColor: C.g1, borderRadius: 24, padding: 8, zIndex: 46, ...shadow },
124
- swatch: { width: 32, height: 32, borderRadius: 16, borderWidth: 2, borderColor: 'rgba(255,255,255,0.4)' },
125
190
  note: { position: 'absolute', zIndex: 40, minWidth: 220, height: 36, paddingHorizontal: 10, borderRadius: 8, backgroundColor: '#fff', color: C.dark, fontFamily: FONT, fontSize: 14, borderWidth: 2, borderColor: C.purple },
126
191
  });