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