@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,45 +1,149 @@
1
- import { useEffect, useRef, useState } from 'react';
2
- import { Pressable, ScrollView, StyleSheet, Switch, Text, TextInput, useWindowDimensions, View } from 'react-native';
1
+ import { useEffect, useMemo, useRef, useState } from 'react';
3
2
  import {
4
- addPage, calibrateUnderlay, deletePage, deletePending, duplicatePage, focusSpace, LAYER_ROWS, layerOn, removeUnderlay,
5
- renamePage, renameProject, renameSpace, setUnderlayOpacity, spaceSchedule, switchPage, toggleLayer, toggleUnderlayLock,
3
+ Animated, Easing, Keyboard, PanResponder, Platform, Pressable, ScrollView, StyleSheet, Text, TextInput, useWindowDimensions, View,
4
+ } from 'react-native';
5
+ import {
6
+ addPage, deletePage, deletePending, duplicatePage, focusSpace, renamePage, renameSpace, spaceSchedule, switchPage,
6
7
  } from '../editor/project';
8
+ import { ed } from '../app/state';
7
9
  import type { DrawerTab } from '../editor/ports';
8
- import { settings } from '../model/settings';
9
10
  import { page, store } from '../model/store';
10
11
  import { fmtArea, fmtLen } from '../units';
11
- import { Icon } from './Icon';
12
+ import { Icon, RawSvg } from './Icon';
13
+ import { PLUS_SIZE, PLUS_SVG, TRASH_SIZE, TRASH_SVG } from '../render/figmaIcons';
12
14
  import { PageThumb } from './PageThumb';
13
15
  import { notify, ui, useEditorVersion } from './store';
14
16
  import { C, FONT } from './theme';
17
+ import { EASE, Segmented, Tap } from './motion';
18
+ import { NATIVE } from './easing';
19
+ import { createSheet } from './corners';
20
+
21
+ /**
22
+ * The Pages frame's sizes, scaled to the panel: Figma draws it 515 wide, the panel's content is about 370 (K). Its
23
+ * tiles are 180 tall there, with the name in a band at their foot below the drawing.
24
+ */
25
+ const K = 0.72, TILE_H = Math.round(180 * K), NAME_BAND = 34, THUMB_H = TILE_H - NAME_BAND - 2;
15
26
 
16
- const TABS: [DrawerTab, string][] = [['pages', 'Pages'], ['layers', 'Layers'], ['rooms', 'Spaces']];
27
+ /** Pages and Spaces, tabs of one panel (Layers is a menu of its own in the top bar). */
28
+ const TABS: [DrawerTab, string][] = [['pages', 'Pages'], ['rooms', 'Spaces']];
17
29
  const close = () => { ui.drawer = null; ui.renameRoom = null; notify(); };
18
30
 
19
- /** The project panel: pages, layers and the spaces schedule. */
20
- export function Drawer() {
21
- const version = useEditorVersion(), { width } = useWindowDimensions();
22
- const tab = ui.drawer;
23
- if (!tab) return null;
31
+ /** Sliding in it eases to a stop; sliding out it gathers pace as it goes. */
32
+ const SLIDE_IN = 240, SLIDE_OUT = 200, EASE_OUT = Easing.bezier(0.4, 0, 1, 1);
33
+ /** A phone's sheet rises the way iOS's own sheets do: quick off the mark, a long soft landing. */
34
+ const SHEET_IN = 320, SHEET_EASE = Easing.bezier(0.32, 0.72, 0, 1), SHEET_OUT = 220;
35
+ /** Taps on the scrim right after the sheet opens are the opening tap's ghost click (touch screens): ignore them. */
36
+ const GHOST_MS = 450;
37
+ /** A drag down the sheet's top that closes it: this far, or a flick. */
38
+ const CLOSE_DRAG = 80, CLOSE_FLICK = 0.5;
39
+
40
+ /**
41
+ * The project panel: pages and the spaces schedule. On a tablet or computer it slides in from the right; on a phone
42
+ * it's a bottom sheet the way BOLDR's are (a grey-1 sheet with a grey-4 handle over a dimmed drawing, closed by a drag
43
+ * down or a tap outside). Either way it slides back out when closed.
44
+ */
45
+ export function Drawer({ phone = false }: { phone?: boolean }) {
46
+ const version = useEditorVersion(), { width, height } = useWindowDimensions(), w = Math.min(400, width);
47
+ const open = !!ui.drawer;
48
+ // what it showed last stays while it slides away
49
+ const last = useRef<DrawerTab>('pages');
50
+ if (ui.drawer) last.current = ui.drawer;
51
+ const [present, setPresent] = useState(open);
52
+ // the phone's sheet slides its own height (measured as it opens), not the screen's: from a screen's height away the
53
+ // first part of the slide happened below the screen, and what showed rushed by in a few frames. While it slides,
54
+ // what's costly in it (the pages' thumbnails) waits: `settled`
55
+ const [travel, setTravel] = useState<number | null>(null), [settled, setSettled] = useState(open);
56
+ const measuring = phone && open && travel == null;
57
+ const x = useRef(new Animated.Value(open ? 0 : 1)).current;
58
+ // made once for its size, which only changes while it's still: a new one each render would detach the old, and a
59
+ // value with nothing attached stops its animation — the slide would end before it began
60
+ const slide = useMemo(() => x.interpolate({ inputRange: [0, 1], outputRange: [0, (phone ? travel ?? height : w) + 40] }), [x, w, height, phone, travel]);
61
+ const fade = useMemo(() => x.interpolate({ inputRange: [0, 1], outputRange: [1, 0] }), [x]);
62
+ // the phone's sheet: how far it's dragged down, when it opened, and how far the keyboard lifts it
63
+ const drag = useRef(new Animated.Value(0)).current, openedAt = useRef(0), sheet = useRef<View>(null), [lift, setLift] = useState(0);
64
+ const dropped = useMemo(() => Animated.add(slide, drag), [slide, drag]);
65
+ useEffect(() => {
66
+ if (open) { setPresent(true); openedAt.current = Date.now(); }
67
+ }, [open]);
68
+ useEffect(() => {
69
+ // (a phone's sheet first lays out below the screen, so it knows how far to come)
70
+ if (measuring) return;
71
+ // started on the next frame: a panel just put on screen is taken off and put back once more by React's strict
72
+ // mode as it mounts, and its animation stops when that leaves nothing attached to it
73
+ const frame = requestAnimationFrame(() => {
74
+ if (open) {
75
+ Animated.timing(x, { toValue: 0, duration: phone ? SHEET_IN : SLIDE_IN, easing: phone ? SHEET_EASE : EASE, useNativeDriver: NATIVE })
76
+ .start(({ finished }) => { if (finished) setSettled(true); });
77
+ } else {
78
+ Animated.timing(x, { toValue: 1, duration: phone ? SHEET_OUT : SLIDE_OUT, easing: EASE_OUT, useNativeDriver: NATIVE })
79
+ // (the thumbnails stay as they slide away; they're held back again the next time it opens)
80
+ .start(({ finished }) => { if (finished) { setPresent(false); setTravel(null); setSettled(false); drag.setValue(0); } });
81
+ }
82
+ });
83
+ return () => cancelAnimationFrame(frame);
84
+ }, [open, measuring, phone, x, drag]);
85
+ const pan = useMemo(() => {
86
+ const back = () => Animated.timing(drag, { toValue: 0, duration: SLIDE_IN, easing: EASE, useNativeDriver: NATIVE }).start();
87
+ return PanResponder.create({
88
+ // a drag down (not a tap on a tab, nor a sideways swipe) moves the sheet with the finger
89
+ onMoveShouldSetPanResponder: (_, g) => g.dy > 4 && Math.abs(g.dy) > Math.abs(g.dx),
90
+ onPanResponderMove: (_, g) => drag.setValue(Math.max(0, g.dy)),
91
+ onPanResponderRelease: (_, g) => { if (g.dy > CLOSE_DRAG || g.vy > CLOSE_FLICK) close(); else back(); },
92
+ onPanResponderTerminate: back,
93
+ });
94
+ }, [drag]);
95
+ // iOS doesn't resize the window for the keyboard (Android does): lift the sheet clear of it to rename a space
96
+ useEffect(() => {
97
+ if (!phone || Platform.OS !== 'ios') return;
98
+ const show = Keyboard.addListener('keyboardWillShow', e => {
99
+ sheet.current?.measureInWindow((_x, y, _w, h) => setLift(l => Math.max(0, y + h + l - e.endCoordinates.screenY)));
100
+ });
101
+ const hide = Keyboard.addListener('keyboardWillHide', () => setLift(0));
102
+ return () => { show.remove(); hide.remove(); };
103
+ }, [phone]);
104
+ if (!open && !present) return null;
105
+ const tab = last.current;
106
+ const body = (
107
+ <ScrollView style={phone && s.sheetScroll} contentContainerStyle={s.body} keyboardShouldPersistTaps="handled">
108
+ {tab === 'pages' && <Pages version={version} thumbs={settled || !phone} />}
109
+ {tab === 'rooms' && <Spaces />}
110
+ </ScrollView>
111
+ );
112
+ const tabs = (
113
+ <Segmented role="tab" value={tab} options={TABS} onPick={t => { ui.drawer = t; notify(); }}
114
+ style={s.tabs} optionStyle={s.tab} textStyle={s.tabText} onText={C.dark} offText={C.lg1} />
115
+ );
116
+ if (phone) {
117
+ return (
118
+ <>
119
+ <Animated.View pointerEvents={open ? 'auto' : 'none'} style={[StyleSheet.absoluteFill, s.scrim, { opacity: fade }]}>
120
+ <Pressable accessibilityRole="button" accessibilityLabel="Close panel" testID="drawerScrim" style={StyleSheet.absoluteFill}
121
+ onPress={() => { if (Date.now() - openedAt.current > GHOST_MS) close(); }} />
122
+ </Animated.View>
123
+ <Animated.View ref={sheet} pointerEvents={open ? 'auto' : 'none'} accessibilityLabel="Project panel" accessibilityViewIsModal
124
+ testID={open ? 'drawer' : undefined} style={[s.sheet, { bottom: lift, transform: [{ translateY: dropped }] }]}
125
+ // its height: how far it slides (taken while it's still — opening, or open and settled — never mid-slide)
126
+ onLayout={e => { const h = e.nativeEvent.layout.height; if (travel == null || (settled && open)) setTravel(t => (t != null && Math.abs(t - h) < 1 ? t : h)); }}>
127
+ {/* the handle and the tabs: a drag down either closes it */}
128
+ <View {...pan.panHandlers}>
129
+ <View style={s.handleWrap}><View style={[s.handle, { width: Math.round(width * 0.075) }]} /></View>
130
+ <View style={[s.head, s.sheetHead]}>{tabs}</View>
131
+ </View>
132
+ {body}
133
+ </Animated.View>
134
+ </>
135
+ );
136
+ }
24
137
  return (
25
- <View style={[s.drawer, { width: Math.min(350, width) }]} accessibilityLabel="Project panel" testID="drawer">
138
+ // (off by its shadow too, so nothing of it shows once it's out)
139
+ <Animated.View pointerEvents={open ? 'auto' : 'none'} accessibilityLabel="Project panel" testID={open ? 'drawer' : undefined}
140
+ style={[s.drawer, { width: w, transform: [{ translateX: slide }] }]}>
26
141
  <View style={s.head}>
27
- <View style={s.tabs} accessibilityRole="tablist">
28
- {TABS.map(([t, label]) => (
29
- <Pressable key={t} accessibilityRole="tab" accessibilityState={{ selected: tab === t }} onPress={() => { ui.drawer = t; notify(); }}
30
- style={[s.tab, tab === t && { backgroundColor: '#fff' }]}>
31
- <Text style={[s.tabText, tab === t && { color: C.dark }]}>{label}</Text>
32
- </Pressable>
33
- ))}
34
- </View>
35
- <Pressable accessibilityRole="button" accessibilityLabel="Close panel" onPress={close} style={s.close}><Icon name="close" /></Pressable>
142
+ {tabs}
143
+ <Tap accessibilityRole="button" accessibilityLabel="Close panel" onPress={close} style={s.close}><Icon name="close" /></Tap>
36
144
  </View>
37
- <ScrollView contentContainerStyle={s.body}>
38
- {tab === 'pages' && <Pages version={version} />}
39
- {tab === 'layers' && <Layers />}
40
- {tab === 'rooms' && <Spaces />}
41
- </ScrollView>
42
- </View>
145
+ {body}
146
+ </Animated.View>
43
147
  );
44
148
  }
45
149
 
@@ -58,83 +162,58 @@ function NameField({ value, onCommit, style, label, autoFocus, onFocus }: {
58
162
  );
59
163
  }
60
164
 
61
- function SmallButton({ label, onPress, danger }: { label: string; onPress: () => void; danger?: boolean }) {
165
+ /**
166
+ * The pages (Figma 2548:101990): the current page's name, then the pages — Duplicate and Delete for the current one
167
+ * (greyed out while it's the only page), + Page, and a tile for each, its drawing above its name, the current one
168
+ * ringed in yellow. A tablet's or a browser's
169
+ * side panel and a phone's bottom sheet show the same.
170
+ */
171
+ function Pages({ version, thumbs }: { version: number; thumbs: boolean }) {
172
+ const project = store.project, cur = page(), pending = deletePending();
173
+ // a project keeps at least one page: nothing to delete while there's only one
174
+ const lone = project.pages.length <= 1;
62
175
  return (
63
- <Pressable accessibilityRole="button" accessibilityLabel={label} onPress={onPress} style={s.small}>
64
- <Text style={[s.smallText, danger && { color: C.softRed }]}>{label}</Text>
65
- </Pressable>
66
- );
67
- }
68
-
69
- function Pages({ version }: { version: number }) {
70
- const project = store.project, cur = page();
71
- return (
72
- <>
73
- <NameField label="Project name" value={project.name} onCommit={renameProject} style={{ fontSize: 18 }} />
74
- <Text style={s.sec}>This page</Text>
75
- <NameField label="Page name" value={cur.name} onCommit={renamePage} style={{ fontSize: 15 }} />
76
- <View style={s.buttons}>
77
- <SmallButton label="Duplicate" onPress={duplicatePage} />
78
- <SmallButton label={deletePending() ? 'Tap again to delete' : 'Delete page'} onPress={deletePage} danger />
176
+ <View style={s.cards}>
177
+ <View style={s.current}>
178
+ <Text style={s.pcardLabel}>Current Page:</Text>
179
+ <NameField label="Page name" value={cur.name} onCommit={renamePage} style={s.curName} />
79
180
  </View>
80
- <View style={s.space}>
81
- <View style={s.spaceHead}>
82
- <Text style={[s.sec, { margin: 0, flex: 1 }]}>Pages</Text>
83
- <SmallButton label="+ Page" onPress={addPage} />
84
- </View>
85
- <View style={s.grid}>
86
- {project.pages.map(p => (
87
- <Pressable key={p.id} accessibilityRole="button" accessibilityLabel={p.name} onPress={() => switchPage(p.id)}
88
- style={[s.card, p.id === cur.id && { borderColor: C.yellow }]}>
89
- <PageThumb page={p} version={version} />
90
- <Text numberOfLines={1} style={s.cardText}>{p.name}</Text>
91
- </Pressable>
92
- ))}
181
+ <View style={s.pcard}>
182
+ <View style={s.pageBar}>
183
+ <View style={s.pageBarLeft}>
184
+ <Tap accessibilityRole="button" accessibilityLabel="Duplicate" onPress={duplicatePage} style={[s.pill, s.pillGrey]}>
185
+ <Text style={[s.pillText, { color: '#fff' }]}>Duplicate</Text>
186
+ </Tap>
187
+ {/* a first tap arms it (it says so), a second deletes the page */}
188
+ <Tap accessibilityRole="button" accessibilityLabel={pending ? 'Tap again to delete' : 'Delete page'} accessibilityState={{ disabled: lone }}
189
+ disabled={lone} onPress={deletePage} style={[s.pill, s.pillRed, pending && s.pillArmed, lone && s.pillOff]}>
190
+ <View style={lone && s.iconOff}><RawSvg xml={TRASH_SVG} width={TRASH_SIZE.width * K} height={TRASH_SIZE.height * K} /></View>
191
+ {pending && <Text style={[s.pillText, { color: '#fff' }]}>Delete?</Text>}
192
+ </Tap>
193
+ </View>
194
+ <Tap accessibilityRole="button" accessibilityLabel="+ Page" onPress={addPage} style={[s.pill, s.pillYellow]}>
195
+ <RawSvg xml={PLUS_SVG} width={PLUS_SIZE.width * K} height={PLUS_SIZE.height * K} />
196
+ <Text style={[s.pillText, { color: C.dark }]}>Page</Text>
197
+ </Tap>
93
198
  </View>
199
+ {project.pages.map(p => {
200
+ const on = p.id === cur.id;
201
+ return (
202
+ <Tap key={p.id} accessibilityRole="button" accessibilityLabel={p.name} accessibilityState={{ selected: on }} onPress={() => switchPage(p.id)}
203
+ style={[s.tile, on && s.tileOn]} testID="pageTile">
204
+ {/* (held back while the sheet slides in: recording and mounting them cost the slide its frames; and drawn
205
+ again only for what they show: the page being drawn on, when it's edited — not every tap, toast and
206
+ selection, for every page) */}
207
+ {thumbs ? <PageThumb page={p} version={on ? ed.editSeq : 0} height={THUMB_H} /> : <View style={{ height: THUMB_H }} />}
208
+ <Text numberOfLines={1} style={s.tileName}>{p.name}</Text>
209
+ </Tap>
210
+ );
211
+ })}
94
212
  </View>
95
- </>
96
- );
97
- }
98
-
99
- function SwitchRow({ label, sub, value, onChange }: { label: string; sub?: string; value: boolean; onChange: () => void }) {
100
- return (
101
- <View style={s.row}>
102
- <View style={{ flex: 1 }}>
103
- <Text style={s.rowText}>{label}</Text>
104
- {sub ? <Text style={s.rowSub}>{sub}</Text> : null}
105
- </View>
106
- <Switch accessibilityLabel={label} value={value} onValueChange={onChange} trackColor={{ true: C.blue, false: C.g3 }} thumbColor="#fff" />
107
213
  </View>
108
214
  );
109
215
  }
110
216
 
111
- function Layers() {
112
- const ul = page().underlay, op = Math.round((ul?.opacity ?? 0.6) * 100);
113
- return (
114
- <>
115
- <SwitchRow label="Photo or plan underlay" sub={ul ? 'Traced image under the drawing' : 'None on this page'} value={settings.layers.underlay} onChange={() => toggleLayer('underlay')} />
116
- {ul && (
117
- <>
118
- <View style={s.row}>
119
- <Text style={s.rowText}>Image opacity</Text>
120
- <View style={s.stepper}>
121
- <SmallButton label="−" onPress={() => setUnderlayOpacity(Math.max(0.1, (op - 10) / 100))} />
122
- <Text style={[s.rowText, { width: 44, textAlign: 'center' }]}>{op}%</Text>
123
- <SmallButton label="+" onPress={() => setUnderlayOpacity(Math.min(1, (op + 10) / 100))} />
124
- </View>
125
- </View>
126
- <View style={s.buttons}>
127
- <SmallButton label={ul.locked ? 'Unlock image' : 'Lock image'} onPress={toggleUnderlayLock} />
128
- <SmallButton label="Set scale" onPress={() => { close(); calibrateUnderlay(); }} />
129
- <SmallButton label="Remove image" onPress={removeUnderlay} danger />
130
- </View>
131
- </>
132
- )}
133
- {LAYER_ROWS.map(([k, label, sub]) => <SwitchRow key={k} label={label} sub={sub} value={layerOn(k)} onChange={() => toggleLayer(k)} />)}
134
- </>
135
- );
136
- }
137
-
138
217
  function Spaces() {
139
218
  const sched = spaceSchedule();
140
219
  if (!sched.rows.length) return <Text style={s.empty}>No spaces on this page yet. Close a loop of walls or lines, or pick the Space tool and tap inside a closed shape.</Text>;
@@ -160,24 +239,25 @@ function Spaces() {
160
239
  );
161
240
  }
162
241
 
163
- const s = StyleSheet.create({
164
- drawer: { position: 'absolute', top: 0, right: 0, bottom: 0, backgroundColor: C.g1, zIndex: 50, boxShadow: '-8px 0px 30px rgba(0, 0, 0, 0.3)' },
242
+ const s = createSheet({
243
+ // (a hairline along its top and left: it doesn't run into the page's header, the same grey, above it)
244
+ drawer: { position: 'absolute', top: 0, right: 0, bottom: 0, backgroundColor: C.g1, borderTopWidth: 1, borderLeftWidth: 1, borderColor: C.g2, zIndex: 50, boxShadow: '-8px 0px 30px rgba(0, 0, 0, 0.3)' },
245
+ // BOLDR's bottom sheet (AnimatedBottomSheet, surface="card"): gorhom's 15 pt corners and handle, over a 50% scrim
246
+ scrim: { backgroundColor: 'rgba(0,0,0,0.5)', zIndex: 49 },
247
+ sheet: { position: 'absolute', left: 0, right: 0, maxHeight: '85%', backgroundColor: C.g1, borderTopLeftRadius: 15, borderTopRightRadius: 15, zIndex: 50 },
248
+ handleWrap: { padding: 10 },
249
+ handle: { alignSelf: 'center', height: 4, borderRadius: 4, backgroundColor: C.g4 },
250
+ sheetHead: { paddingTop: 0 },
251
+ // (as tall as what's in it, up to the sheet's limit, then it scrolls)
252
+ sheetScroll: { flexGrow: 0, flexShrink: 1 },
165
253
  head: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingTop: 16, paddingBottom: 10, paddingLeft: 16, paddingRight: 14, gap: 10 },
166
- tabs: { flexDirection: 'row', backgroundColor: C.dark, borderRadius: 20, padding: 3 },
167
- tab: { paddingVertical: 8, paddingHorizontal: 14, borderRadius: 17 },
254
+ title: { fontFamily: FONT, fontSize: 17, fontWeight: '700', color: '#fff', paddingLeft: 2 },
255
+ tabs: { flexDirection: 'row', backgroundColor: C.dark, borderRadius: 999, padding: 3 },
256
+ tab: { paddingVertical: 8, paddingHorizontal: 14, borderRadius: 999 },
168
257
  tabText: { fontFamily: FONT, fontSize: 13, fontWeight: '600', color: C.lg1 },
169
258
  close: { width: 38, height: 38, borderRadius: 19, backgroundColor: C.g2, alignItems: 'center', justifyContent: 'center' },
170
259
  body: { paddingTop: 6, paddingHorizontal: 16, paddingBottom: 30 },
171
260
  field: { fontFamily: FONT, color: '#fff', fontWeight: '600', paddingVertical: 6, paddingHorizontal: 8, marginLeft: -8, borderRadius: 8, borderWidth: 1, borderColor: 'transparent' },
172
- sec: { fontFamily: FONT, fontSize: 13, fontWeight: '600', color: C.g4, marginTop: 18, marginBottom: 8 },
173
- buttons: { flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginTop: 10 },
174
- small: { height: 32, paddingHorizontal: 12, borderRadius: 16, backgroundColor: C.dark, justifyContent: 'center' },
175
- smallText: { fontFamily: FONT, fontSize: 12.5, fontWeight: '600', color: C.lg2 },
176
- space: { marginTop: 16, backgroundColor: C.g2, borderWidth: 1, borderColor: C.g3, borderRadius: 12, padding: 10 },
177
- spaceHead: { flexDirection: 'row', alignItems: 'center', gap: 6, marginBottom: 8 },
178
- grid: { flexDirection: 'row', flexWrap: 'wrap', gap: 8 },
179
- card: { width: '48%', flexGrow: 1, backgroundColor: C.dark, borderRadius: 9, padding: 6, borderWidth: 2, borderColor: 'transparent', gap: 5 },
180
- cardText: { fontFamily: FONT, fontSize: 12.5, fontWeight: '500', color: C.lg2, paddingHorizontal: 2 },
181
261
  row: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 12, paddingVertical: 10, borderBottomWidth: 1, borderBottomColor: C.g2 },
182
262
  rowText: { fontFamily: FONT, fontSize: 14, color: '#fff' },
183
263
  rowSub: { fontFamily: FONT, fontSize: 12, color: C.g4, marginTop: 2 },
@@ -186,5 +266,28 @@ const s = StyleSheet.create({
186
266
  nums: { flexDirection: 'row', gap: 16, marginTop: 2 },
187
267
  num: { fontFamily: FONT, fontSize: 13, color: C.lg1 },
188
268
  numB: { color: '#fff', fontWeight: '600' },
269
+ // the Pages frame (Figma 2548:101989), at K: two cards of grey-3-ringed #3A3B3C, 20 corners, 15 inside
270
+ cards: { gap: 20 * K },
271
+ pcard: { backgroundColor: C.card, borderWidth: 1, borderColor: C.g3, borderRadius: 20 * K, padding: 15 * K, gap: 15 * K },
272
+ pcardLabel: { fontFamily: FONT, fontSize: 18 * K, fontWeight: '600', color: C.g4 },
273
+ // the current page's name: its label over a dark field, 60 tall, editable in place — straight on the panel, no card
274
+ current: { gap: 15 * K },
275
+ curName: { fontSize: 21 * K, backgroundColor: C.dark, borderRadius: 15 * K, minHeight: 60 * K, paddingVertical: 0, paddingHorizontal: 15 * K, marginLeft: 0 },
276
+ pageBar: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 10 * K },
277
+ pageBarLeft: { flexDirection: 'row', alignItems: 'center', gap: 10 * K },
278
+ // 50-tall pills: Duplicate 125 wide, Delete 70, + Page 125
279
+ pill: { height: 50 * K, borderRadius: 100, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 10 * K, paddingHorizontal: 20 * K },
280
+ pillGrey: { backgroundColor: C.g3, minWidth: 125 * K },
281
+ pillRed: { backgroundColor: C.red, minWidth: 70 * K },
282
+ pillArmed: { paddingHorizontal: 15 * K },
283
+ // Delete with nothing to delete (the only page): grey-3 like Duplicate, its can faded
284
+ pillOff: { backgroundColor: C.g3 },
285
+ iconOff: { opacity: 0.45 },
286
+ pillYellow: { backgroundColor: C.yellow, minWidth: 125 * K },
287
+ pillText: { fontFamily: FONT, fontSize: 18 * K, fontWeight: '600' },
288
+ // a page: its drawing, its name at the foot; dark grey, ringed grey-3 (the current one 3 wide in yellow)
289
+ tile: { height: TILE_H, backgroundColor: C.dark, borderRadius: 10 * K, borderWidth: 1, borderColor: C.g3, overflow: 'hidden' },
290
+ tileOn: { borderWidth: 3, borderColor: C.yellow },
291
+ tileName: { fontFamily: FONT, fontSize: 18 * K, fontWeight: '600', color: '#fff', position: 'absolute', left: 15 * K, right: 15 * K, bottom: 15 * K - 2 },
189
292
  empty: { fontFamily: FONT, color: C.g4, fontSize: 13, lineHeight: 20, paddingVertical: 8 },
190
293
  });
@@ -0,0 +1,50 @@
1
+ import { useEffect, useRef, useSyncExternalStore, type ReactNode } from 'react';
2
+ import { Animated, Platform, StyleSheet, type View } from 'react-native';
3
+
4
+ /*
5
+ On a phone the floating chrome — the top bar, the tabs, the rail, the bottom bar and trash, the trays — steps aside
6
+ while a gesture on the drawing is in motion (a stroke, a drag, a pan or pinch that moved; never a tap: see
7
+ editor/gestures.ts), and comes back as the last finger lifts, so the drawing gets the whole of a small screen. As
8
+ BOLDR's annotation screen does it: out in 120 ms, back in 200 ms (a gesture that ends mid-fade turns round from
9
+ where it is), and untouchable while it's away. The fade runs on the native driver, so it stays smooth while the
10
+ JavaScript thread is busy redrawing the plan under a pinch.
11
+ */
12
+
13
+ let moving = false;
14
+ const listeners = new Set<() => void>();
15
+ const subscribe = (fn: () => void) => { listeners.add(fn); return () => { listeners.delete(fn); }; };
16
+ const isMoving = () => moving;
17
+
18
+ /** A gesture on the drawing is in motion, or not (the editor's `interacting` port). */
19
+ export function setInteracting(on: boolean): void {
20
+ if (on === moving) return;
21
+ moving = on;
22
+ listeners.forEach(fn => fn());
23
+ }
24
+
25
+ const OUT_MS = 120, IN_MS = 200;
26
+
27
+ /**
28
+ * The chrome inside steps aside while a gesture on the drawing moves, when `active` (a phone). `held`: it stays in
29
+ * view but out of reach — a sheet is up over the drawing (a tablet's or a computer's keypad): a tap on it reaches the
30
+ * drawing beneath, which closes the sheet, as a tap outside a sheet does.
31
+ */
32
+ export function Duck({ active, held = false, children }: { active: boolean; held?: boolean; children: ReactNode }) {
33
+ const away = useSyncExternalStore(subscribe, isMoving) && active, off = away || held;
34
+ const opacity = useRef(new Animated.Value(1)).current, ref = useRef<View>(null);
35
+ useEffect(() => {
36
+ Animated.timing(opacity, { toValue: away ? 0 : 1, duration: away ? OUT_MS : IN_MS, useNativeDriver: Platform.OS !== 'web' }).start();
37
+ }, [away, opacity]);
38
+ useEffect(() => {
39
+ // in a browser the chrome's own pass-through containers (box-none) turn their children back on, whatever is set
40
+ // above them: `inert` takes the whole of it out of reach (a device's pointerEvents="none" already does)
41
+ if (Platform.OS === 'web' && ref.current) (ref.current as unknown as HTMLElement).inert = off;
42
+ }, [off]);
43
+ // (an editor closed mid-gesture mustn't leave the next one's chrome away)
44
+ useEffect(() => () => setInteracting(false), []);
45
+ return (
46
+ <Animated.View ref={ref} pointerEvents={off ? 'none' : 'box-none'} aria-hidden={away || undefined} testID="chrome" style={[StyleSheet.absoluteFill, { opacity }]}>
47
+ {children}
48
+ </Animated.View>
49
+ );
50
+ }
@@ -0,0 +1,32 @@
1
+ import { Text, View } from 'react-native';
2
+ import { planSK } from '../model/store';
3
+ import { useEditorKey } from './store';
4
+ import { C, FONT } from './theme';
5
+ import { createSheet } from './corners';
6
+
7
+ /** Whether the plan has a wall to stand up (lines and guides stay flat on the plan). */
8
+ function hasWalls(): boolean {
9
+ const sk = planSK();
10
+ for (const id in sk.lines) { const l = sk.lines[id]; if (l.kind === 'wall' && !l.constr) return true; }
11
+ for (const id in sk.arcs) { const a = sk.arcs[id]; if (a.kind === 'wall' && !a.constr) return true; }
12
+ for (const id in sk.circles) { const c = sk.circles[id]; if (c.kind === 'wall' && !c.constr) return true; }
13
+ return false;
14
+ }
15
+
16
+ /** Over an empty 3D view: what it shows, and how to get walls into it. */
17
+ export function Empty3D() {
18
+ useEditorKey(() => String(hasWalls()));
19
+ if (hasWalls()) return null;
20
+ return (
21
+ <View pointerEvents="none" style={s.wrap} testID="empty3d">
22
+ <Text style={s.title}>No walls to stand up yet</Text>
23
+ <Text style={s.sub}>3D shows walls, with their doors, windows and cabinets. In Draft, draw with Walls on, or select lines and tap To wall.</Text>
24
+ </View>
25
+ );
26
+ }
27
+
28
+ const s = createSheet({
29
+ wrap: { position: 'absolute', left: 24, right: 24, top: 0, bottom: 0, alignItems: 'center', justifyContent: 'center' },
30
+ title: { fontFamily: FONT, fontSize: 16, fontWeight: '600', color: C.lg2, textAlign: 'center' },
31
+ sub: { fontFamily: FONT, fontSize: 13, color: C.g4, textAlign: 'center', marginTop: 6, maxWidth: 360 },
32
+ });
@@ -0,0 +1,19 @@
1
+ import { useState, type ReactNode } from 'react';
2
+ import { ScrollView, type StyleProp, type ViewStyle } from 'react-native';
3
+
4
+ /*
5
+ A menu's or panel's body: fixed in its box. It neither scrolls nor bounces while everything fits (an iOS ScrollView
6
+ drags and springs back even when there's nothing more to see); only when the screen is too short to hold it all —
7
+ a phone turned on its side — does it scroll, without the bounce.
8
+ */
9
+ export function FitScroll({ children, contentContainerStyle }: { children: ReactNode; contentContainerStyle?: StyleProp<ViewStyle> }) {
10
+ const [box, setBox] = useState(0), [content, setContent] = useState(0);
11
+ const over = content > box + 1;
12
+ return (
13
+ <ScrollView scrollEnabled={over} bounces={false} alwaysBounceVertical={false} overScrollMode="never"
14
+ showsVerticalScrollIndicator={over} contentContainerStyle={contentContainerStyle}
15
+ onLayout={e => setBox(e.nativeEvent.layout.height)} onContentSizeChange={(_, h) => setContent(h)}>
16
+ {children}
17
+ </ScrollView>
18
+ );
19
+ }
@@ -5,3 +5,8 @@ import { icon } from '../render/icons';
5
5
  export function Icon({ name, size = 24, color = '#fff' }: { name: string; size?: number; color?: string }) {
6
6
  return <SvgXml xml={icon(name, size)} width={size} height={size} color={color} />;
7
7
  }
8
+
9
+ /** An SVG as Figma exported it (render/figmaIcons.ts), shown at `width` × `height`. */
10
+ export function RawSvg({ xml, width, height }: { xml: string; width: number; height: number }) {
11
+ return <SvgXml xml={xml} width={width} height={height} />;
12
+ }
@@ -4,3 +4,8 @@ import { icon } from '../render/icons';
4
4
  export function Icon({ name, size = 24, color = '#fff' }: { name: string; size?: number; color?: string }) {
5
5
  return <span aria-hidden style={{ display: 'flex', color, lineHeight: 0 }} dangerouslySetInnerHTML={{ __html: icon(name, size) }} />;
6
6
  }
7
+
8
+ /** An SVG as Figma exported it (render/figmaIcons.ts), shown at `width` × `height`. */
9
+ export function RawSvg({ xml, width, height }: { xml: string; width: number; height: number }) {
10
+ return <span aria-hidden style={{ display: 'flex', width, height, lineHeight: 0 }} dangerouslySetInnerHTML={{ __html: xml.replace(/width="[^"]*" height="[^"]*"/, `width="${width}" height="${height}"`) }} />;
11
+ }