@reekon-tools/boldr-cad 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (547) hide show
  1. package/README.md +81 -9
  2. package/dist/app/edit.d.ts +12 -1
  3. package/dist/app/edit.js +96 -11
  4. package/dist/app/edit.js.map +1 -1
  5. package/dist/app/state.d.ts +31 -4
  6. package/dist/app/state.js +31 -2
  7. package/dist/app/state.js.map +1 -1
  8. package/dist/data/memory.d.ts +5 -0
  9. package/dist/data/memory.js +2 -2
  10. package/dist/data/memory.js.map +1 -1
  11. package/dist/data/provider.d.ts +19 -7
  12. package/dist/data/sync.d.ts +7 -2
  13. package/dist/data/sync.js +115 -64
  14. package/dist/data/sync.js.map +1 -1
  15. package/dist/editor/actions.d.ts +1 -2
  16. package/dist/editor/actions.js +391 -113
  17. package/dist/editor/actions.js.map +1 -1
  18. package/dist/editor/confirm.d.ts +23 -0
  19. package/dist/editor/confirm.js +35 -0
  20. package/dist/editor/confirm.js.map +1 -0
  21. package/dist/editor/elevation.d.ts +191 -90
  22. package/dist/editor/elevation.js +812 -616
  23. package/dist/editor/elevation.js.map +1 -1
  24. package/dist/editor/face.d.ts +73 -0
  25. package/dist/editor/face.js +269 -0
  26. package/dist/editor/face.js.map +1 -0
  27. package/dist/editor/files.d.ts +6 -0
  28. package/dist/editor/files.js +50 -4
  29. package/dist/editor/files.js.map +1 -1
  30. package/dist/editor/gestures.d.ts +18 -3
  31. package/dist/editor/gestures.js +145 -30
  32. package/dist/editor/gestures.js.map +1 -1
  33. package/dist/editor/keypad.d.ts +19 -4
  34. package/dist/editor/keypad.js +39 -9
  35. package/dist/editor/keypad.js.map +1 -1
  36. package/dist/editor/keys.js +36 -4
  37. package/dist/editor/keys.js.map +1 -1
  38. package/dist/editor/laser.d.ts +20 -4
  39. package/dist/editor/laser.js +104 -21
  40. package/dist/editor/laser.js.map +1 -1
  41. package/dist/editor/linkActions.d.ts +49 -0
  42. package/dist/editor/linkActions.js +210 -0
  43. package/dist/editor/linkActions.js.map +1 -0
  44. package/dist/editor/linkFollow.d.ts +2 -0
  45. package/dist/editor/linkFollow.js +53 -0
  46. package/dist/editor/linkFollow.js.map +1 -0
  47. package/dist/editor/linkWrite.d.ts +13 -0
  48. package/dist/editor/linkWrite.js +84 -0
  49. package/dist/editor/linkWrite.js.map +1 -0
  50. package/dist/editor/links.d.ts +84 -0
  51. package/dist/editor/links.js +130 -0
  52. package/dist/editor/links.js.map +1 -0
  53. package/dist/editor/markup.d.ts +29 -3
  54. package/dist/editor/markup.js +95 -10
  55. package/dist/editor/markup.js.map +1 -1
  56. package/dist/editor/mode.d.ts +40 -2
  57. package/dist/editor/mode.js +71 -4
  58. package/dist/editor/mode.js.map +1 -1
  59. package/dist/editor/ports.d.ts +36 -6
  60. package/dist/editor/ports.js +10 -1
  61. package/dist/editor/ports.js.map +1 -1
  62. package/dist/editor/project.d.ts +38 -6
  63. package/dist/editor/project.js +167 -27
  64. package/dist/editor/project.js.map +1 -1
  65. package/dist/editor/selection.d.ts +10 -0
  66. package/dist/editor/selection.js +62 -8
  67. package/dist/editor/selection.js.map +1 -1
  68. package/dist/editor/status.js +3 -0
  69. package/dist/editor/status.js.map +1 -1
  70. package/dist/editor/tooltips.d.ts +5 -0
  71. package/dist/editor/tooltips.js +48 -0
  72. package/dist/editor/tooltips.js.map +1 -0
  73. package/dist/editor/view3d.d.ts +17 -0
  74. package/dist/editor/view3d.js +135 -0
  75. package/dist/editor/view3d.js.map +1 -0
  76. package/dist/editor/view3dState.d.ts +39 -0
  77. package/dist/editor/view3dState.js +44 -0
  78. package/dist/editor/view3dState.js.map +1 -0
  79. package/dist/export/dxf.js +1 -1
  80. package/dist/export/dxf.js.map +1 -1
  81. package/dist/export/pdf.js +17 -12
  82. package/dist/export/pdf.js.map +1 -1
  83. package/dist/export/plan.d.ts +23 -6
  84. package/dist/export/plan.js +127 -44
  85. package/dist/export/plan.js.map +1 -1
  86. package/dist/index.d.ts +5 -0
  87. package/dist/index.js +2 -0
  88. package/dist/index.js.map +1 -1
  89. package/dist/model/constraints.js +15 -4
  90. package/dist/model/constraints.js.map +1 -1
  91. package/dist/model/demo.js +3 -3
  92. package/dist/model/demo.js.map +1 -1
  93. package/dist/model/elevation.d.ts +6 -9
  94. package/dist/model/elevation.js +5 -13
  95. package/dist/model/elevation.js.map +1 -1
  96. package/dist/model/faces.d.ts +58 -0
  97. package/dist/model/faces.js +197 -0
  98. package/dist/model/faces.js.map +1 -0
  99. package/dist/model/facesolve.d.ts +72 -0
  100. package/dist/model/facesolve.js +369 -0
  101. package/dist/model/facesolve.js.map +1 -0
  102. package/dist/model/history.d.ts +3 -2
  103. package/dist/model/history.js +17 -11
  104. package/dist/model/history.js.map +1 -1
  105. package/dist/model/images.d.ts +18 -0
  106. package/dist/model/images.js +26 -0
  107. package/dist/model/images.js.map +1 -0
  108. package/dist/model/links.d.ts +46 -0
  109. package/dist/model/links.js +73 -0
  110. package/dist/model/links.js.map +1 -0
  111. package/dist/model/markup.d.ts +46 -0
  112. package/dist/model/markup.js +71 -0
  113. package/dist/model/markup.js.map +1 -0
  114. package/dist/model/measurement.d.ts +11 -0
  115. package/dist/model/measurement.js +24 -0
  116. package/dist/model/measurement.js.map +1 -0
  117. package/dist/model/openings.d.ts +4 -0
  118. package/dist/model/openings.js +4 -0
  119. package/dist/model/openings.js.map +1 -1
  120. package/dist/model/planGeom.d.ts +72 -2
  121. package/dist/model/planGeom.js +184 -10
  122. package/dist/model/planGeom.js.map +1 -1
  123. package/dist/model/project.js +3 -2
  124. package/dist/model/project.js.map +1 -1
  125. package/dist/model/projectfile.js +2 -1
  126. package/dist/model/projectfile.js.map +1 -1
  127. package/dist/model/rooms.js +2 -2
  128. package/dist/model/rooms.js.map +1 -1
  129. package/dist/model/settings.d.ts +6 -0
  130. package/dist/model/settings.js +2 -2
  131. package/dist/model/settings.js.map +1 -1
  132. package/dist/model/sketch.d.ts +14 -0
  133. package/dist/model/sketch.js +143 -24
  134. package/dist/model/sketch.js.map +1 -1
  135. package/dist/model/smooth.d.ts +6 -0
  136. package/dist/model/smooth.js +52 -0
  137. package/dist/model/smooth.js.map +1 -0
  138. package/dist/model/store.d.ts +12 -0
  139. package/dist/model/store.js +35 -3
  140. package/dist/model/store.js.map +1 -1
  141. package/dist/model/types.d.ts +173 -8
  142. package/dist/model/wallfaces.d.ts +32 -0
  143. package/dist/model/wallfaces.js +133 -20
  144. package/dist/model/wallfaces.js.map +1 -1
  145. package/dist/model/wallshape.d.ts +15 -0
  146. package/dist/model/wallshape.js +89 -0
  147. package/dist/model/wallshape.js.map +1 -0
  148. package/dist/react/BottomBar.d.ts +9 -5
  149. package/dist/react/BottomBar.js +213 -24
  150. package/dist/react/BottomBar.js.map +1 -1
  151. package/dist/react/Buttons.d.ts +13 -2
  152. package/dist/react/Buttons.js +37 -5
  153. package/dist/react/Buttons.js.map +1 -1
  154. package/dist/react/CadEditor.d.ts +46 -3
  155. package/dist/react/CadEditor.js +77 -20
  156. package/dist/react/CadEditor.js.map +1 -1
  157. package/dist/react/ConfirmSheet.d.ts +4 -0
  158. package/dist/react/ConfirmSheet.js +31 -0
  159. package/dist/react/ConfirmSheet.js.map +1 -0
  160. package/dist/react/ConflictSheet.d.ts +2 -1
  161. package/dist/react/ConflictSheet.js +16 -8
  162. package/dist/react/ConflictSheet.js.map +1 -1
  163. package/dist/react/Drawer.d.ts +8 -2
  164. package/dist/react/Drawer.js +167 -38
  165. package/dist/react/Drawer.js.map +1 -1
  166. package/dist/react/Duck.d.ts +13 -0
  167. package/dist/react/Duck.js +45 -0
  168. package/dist/react/Duck.js.map +1 -0
  169. package/dist/react/Empty3D.d.ts +2 -0
  170. package/dist/react/Empty3D.js +39 -0
  171. package/dist/react/Empty3D.js.map +1 -0
  172. package/dist/react/FitScroll.d.ts +6 -0
  173. package/dist/react/FitScroll.js +14 -0
  174. package/dist/react/FitScroll.js.map +1 -0
  175. package/dist/react/Icon.native.d.ts +6 -0
  176. package/dist/react/Icon.native.js +4 -0
  177. package/dist/react/Icon.native.js.map +1 -1
  178. package/dist/react/Icon.web.d.ts +6 -0
  179. package/dist/react/Icon.web.js +4 -0
  180. package/dist/react/Icon.web.js.map +1 -1
  181. package/dist/react/KeypadSheet.d.ts +9 -2
  182. package/dist/react/KeypadSheet.js +174 -42
  183. package/dist/react/KeypadSheet.js.map +1 -1
  184. package/dist/react/LaserTray.js +8 -5
  185. package/dist/react/LaserTray.js.map +1 -1
  186. package/dist/react/MarkupPanel.d.ts +8 -0
  187. package/dist/react/MarkupPanel.js +67 -0
  188. package/dist/react/MarkupPanel.js.map +1 -0
  189. package/dist/react/PageThumb.native.d.ts +7 -2
  190. package/dist/react/PageThumb.native.js +19 -7
  191. package/dist/react/PageThumb.native.js.map +1 -1
  192. package/dist/react/PageThumb.web.d.ts +3 -2
  193. package/dist/react/PageThumb.web.js +4 -4
  194. package/dist/react/PageThumb.web.js.map +1 -1
  195. package/dist/react/PlanCanvas.native.d.ts +1 -1
  196. package/dist/react/PlanCanvas.native.js +134 -36
  197. package/dist/react/PlanCanvas.native.js.map +1 -1
  198. package/dist/react/PlanCanvas.web.js +16 -5
  199. package/dist/react/PlanCanvas.web.js.map +1 -1
  200. package/dist/react/Popovers.d.ts +12 -6
  201. package/dist/react/Popovers.js +53 -26
  202. package/dist/react/Popovers.js.map +1 -1
  203. package/dist/react/Rail.d.ts +24 -0
  204. package/dist/react/Rail.js +324 -34
  205. package/dist/react/Rail.js.map +1 -1
  206. package/dist/react/ShapeMenu.d.ts +7 -0
  207. package/dist/react/ShapeMenu.js +58 -0
  208. package/dist/react/ShapeMenu.js.map +1 -0
  209. package/dist/react/Sheet.d.ts +14 -4
  210. package/dist/react/Sheet.js +54 -9
  211. package/dist/react/Sheet.js.map +1 -1
  212. package/dist/react/Slider.d.ts +18 -0
  213. package/dist/react/Slider.js +27 -0
  214. package/dist/react/Slider.js.map +1 -0
  215. package/dist/react/Toast.js +14 -6
  216. package/dist/react/Toast.js.map +1 -1
  217. package/dist/react/ToolTip.d.ts +7 -0
  218. package/dist/react/ToolTip.js +43 -0
  219. package/dist/react/ToolTip.js.map +1 -0
  220. package/dist/react/TopBar.d.ts +6 -3
  221. package/dist/react/TopBar.js +48 -17
  222. package/dist/react/TopBar.js.map +1 -1
  223. package/dist/react/Tween.d.ts +7 -0
  224. package/dist/react/Tween.js +26 -0
  225. package/dist/react/Tween.js.map +1 -0
  226. package/dist/react/Tween.native.d.ts +6 -0
  227. package/dist/react/Tween.native.js +43 -0
  228. package/dist/react/Tween.native.js.map +1 -0
  229. package/dist/react/View3D.native.d.ts +4 -0
  230. package/dist/react/View3D.native.js +103 -0
  231. package/dist/react/View3D.native.js.map +1 -0
  232. package/dist/react/View3D.web.d.ts +4 -0
  233. package/dist/react/View3D.web.js +151 -0
  234. package/dist/react/View3D.web.js.map +1 -0
  235. package/dist/react/View3DHost.d.ts +143 -0
  236. package/dist/react/View3DHost.js +377 -0
  237. package/dist/react/View3DHost.js.map +1 -0
  238. package/dist/react/corners.d.ts +7 -0
  239. package/dist/react/corners.js +51 -0
  240. package/dist/react/corners.js.map +1 -0
  241. package/dist/react/easing.d.ts +15 -0
  242. package/dist/react/easing.js +10 -0
  243. package/dist/react/easing.js.map +1 -0
  244. package/dist/react/host.d.ts +7 -2
  245. package/dist/react/host.js +8 -3
  246. package/dist/react/host.js.map +1 -1
  247. package/dist/react/hostKeypad.d.ts +56 -0
  248. package/dist/react/hostKeypad.js +5 -0
  249. package/dist/react/hostKeypad.js.map +1 -0
  250. package/dist/react/motion.d.ts +77 -0
  251. package/dist/react/motion.js +173 -0
  252. package/dist/react/motion.js.map +1 -0
  253. package/dist/react/ports.js +90 -8
  254. package/dist/react/ports.js.map +1 -1
  255. package/dist/react/saving.d.ts +15 -0
  256. package/dist/react/saving.js +76 -0
  257. package/dist/react/saving.js.map +1 -0
  258. package/dist/react/store.d.ts +38 -1
  259. package/dist/react/store.js +31 -1
  260. package/dist/react/store.js.map +1 -1
  261. package/dist/react/theme.d.ts +10 -0
  262. package/dist/react/theme.js +8 -0
  263. package/dist/react/theme.js.map +1 -1
  264. package/dist/react/topRow.d.ts +22 -0
  265. package/dist/react/topRow.js +61 -0
  266. package/dist/react/topRow.js.map +1 -0
  267. package/dist/react/touch.d.ts +6 -0
  268. package/dist/react/touch.js +9 -0
  269. package/dist/react/touch.js.map +1 -0
  270. package/dist/render/ctx.d.ts +8 -0
  271. package/dist/render/draw.d.ts +33 -1
  272. package/dist/render/draw.js +593 -215
  273. package/dist/render/draw.js.map +1 -1
  274. package/dist/render/elevation.d.ts +65 -43
  275. package/dist/render/elevation.js +414 -140
  276. package/dist/render/elevation.js.map +1 -1
  277. package/dist/render/figmaIcons.d.ts +12 -0
  278. package/dist/render/figmaIcons.js +21 -0
  279. package/dist/render/figmaIcons.js.map +1 -0
  280. package/dist/render/hit.d.ts +22 -2
  281. package/dist/render/hit.js +168 -25
  282. package/dist/render/hit.js.map +1 -1
  283. package/dist/render/icons.js +69 -10
  284. package/dist/render/icons.js.map +1 -1
  285. package/dist/render/linkTile.d.ts +15 -0
  286. package/dist/render/linkTile.js +369 -0
  287. package/dist/render/linkTile.js.map +1 -0
  288. package/dist/render/markup.d.ts +11 -0
  289. package/dist/render/markup.js +112 -0
  290. package/dist/render/markup.js.map +1 -0
  291. package/dist/render/navigate.d.ts +88 -0
  292. package/dist/render/navigate.js +100 -0
  293. package/dist/render/navigate.js.map +1 -0
  294. package/dist/render/snap.d.ts +20 -1
  295. package/dist/render/snap.js +118 -25
  296. package/dist/render/snap.js.map +1 -1
  297. package/dist/render/squircle.d.ts +8 -0
  298. package/dist/render/squircle.js +71 -0
  299. package/dist/render/squircle.js.map +1 -0
  300. package/dist/render/view.d.ts +115 -2
  301. package/dist/render/view.js +244 -19
  302. package/dist/render/view.js.map +1 -1
  303. package/dist/skia/SkiaDrawCtx.d.ts +30 -2
  304. package/dist/skia/SkiaDrawCtx.js +198 -43
  305. package/dist/skia/SkiaDrawCtx.js.map +1 -1
  306. package/dist/skia/host.d.ts +18 -2
  307. package/dist/skia/host.js +46 -1
  308. package/dist/skia/host.js.map +1 -1
  309. package/dist/solver/equations.js +37 -2
  310. package/dist/solver/equations.js.map +1 -1
  311. package/dist/solver/gauss-newton.d.ts +17 -0
  312. package/dist/solver/gauss-newton.js +144 -36
  313. package/dist/solver/gauss-newton.js.map +1 -1
  314. package/dist/solver/linalg.d.ts +45 -2
  315. package/dist/solver/linalg.js +164 -10
  316. package/dist/solver/linalg.js.map +1 -1
  317. package/dist/solver/types.d.ts +5 -0
  318. package/dist/tools/annotate.d.ts +57 -1
  319. package/dist/tools/annotate.js +64 -43
  320. package/dist/tools/annotate.js.map +1 -1
  321. package/dist/tools/chamfer.d.ts +12 -0
  322. package/dist/tools/chamfer.js +114 -0
  323. package/dist/tools/chamfer.js.map +1 -0
  324. package/dist/tools/corner.d.ts +38 -0
  325. package/dist/tools/corner.js +99 -0
  326. package/dist/tools/corner.js.map +1 -0
  327. package/dist/tools/dim.d.ts +86 -9
  328. package/dist/tools/dim.js +581 -96
  329. package/dist/tools/dim.js.map +1 -1
  330. package/dist/tools/elevation.js +1 -1
  331. package/dist/tools/elevation.js.map +1 -1
  332. package/dist/tools/fillet.d.ts +9 -0
  333. package/dist/tools/fillet.js +86 -0
  334. package/dist/tools/fillet.js.map +1 -0
  335. package/dist/tools/freehand.d.ts +3 -2
  336. package/dist/tools/freehand.js +19 -12
  337. package/dist/tools/freehand.js.map +1 -1
  338. package/dist/tools/index.js +14 -4
  339. package/dist/tools/index.js.map +1 -1
  340. package/dist/tools/line.d.ts +8 -3
  341. package/dist/tools/line.js +66 -30
  342. package/dist/tools/line.js.map +1 -1
  343. package/dist/tools/modify.d.ts +13 -0
  344. package/dist/tools/modify.js +105 -25
  345. package/dist/tools/modify.js.map +1 -1
  346. package/dist/tools/openings.js +22 -15
  347. package/dist/tools/openings.js.map +1 -1
  348. package/dist/tools/registry.d.ts +8 -0
  349. package/dist/tools/registry.js +42 -2
  350. package/dist/tools/registry.js.map +1 -1
  351. package/dist/tools/select.d.ts +77 -3
  352. package/dist/tools/select.js +560 -74
  353. package/dist/tools/select.js.map +1 -1
  354. package/dist/tools/shapes.d.ts +14 -0
  355. package/dist/tools/shapes.js +154 -75
  356. package/dist/tools/shapes.js.map +1 -1
  357. package/dist/units/format.d.ts +10 -2
  358. package/dist/units/format.js +18 -3
  359. package/dist/units/format.js.map +1 -1
  360. package/dist/view3d/build.d.ts +35 -0
  361. package/dist/view3d/build.js +630 -0
  362. package/dist/view3d/build.js.map +1 -0
  363. package/dist/view3d/camera.d.ts +60 -0
  364. package/dist/view3d/camera.js +84 -0
  365. package/dist/view3d/camera.js.map +1 -0
  366. package/dist/view3d/fatLines.d.ts +11 -0
  367. package/dist/view3d/fatLines.js +83 -0
  368. package/dist/view3d/fatLines.js.map +1 -0
  369. package/dist/view3d/lines.d.ts +17 -0
  370. package/dist/view3d/lines.js +271 -0
  371. package/dist/view3d/lines.js.map +1 -0
  372. package/dist/view3d/mesh.d.ts +51 -0
  373. package/dist/view3d/mesh.js +101 -0
  374. package/dist/view3d/mesh.js.map +1 -0
  375. package/dist/view3d/poly.d.ts +16 -0
  376. package/dist/view3d/poly.js +109 -0
  377. package/dist/view3d/poly.js.map +1 -0
  378. package/dist/view3d/viewer.d.ts +86 -0
  379. package/dist/view3d/viewer.js +350 -0
  380. package/dist/view3d/viewer.js.map +1 -0
  381. package/package.json +15 -2
  382. package/src/app/edit.ts +83 -12
  383. package/src/app/main.ts +11 -5
  384. package/src/app/state.ts +38 -5
  385. package/src/data/memory.ts +2 -2
  386. package/src/data/provider.ts +15 -7
  387. package/src/data/sync.ts +87 -50
  388. package/src/editor/actions.ts +292 -83
  389. package/src/editor/confirm.ts +58 -0
  390. package/src/editor/elevation.ts +698 -478
  391. package/src/editor/face.ts +227 -0
  392. package/src/editor/files.ts +40 -4
  393. package/src/editor/gestures.ts +147 -31
  394. package/src/editor/keypad.ts +43 -11
  395. package/src/editor/keys.ts +25 -5
  396. package/src/editor/laser.ts +98 -20
  397. package/src/editor/linkActions.ts +194 -0
  398. package/src/editor/linkFollow.ts +43 -0
  399. package/src/editor/linkWrite.ts +77 -0
  400. package/src/editor/links.ts +181 -0
  401. package/src/editor/markup.ts +92 -8
  402. package/src/editor/mode.ts +83 -7
  403. package/src/editor/ports.ts +37 -6
  404. package/src/editor/project.ts +155 -26
  405. package/src/editor/selection.ts +54 -8
  406. package/src/editor/status.ts +2 -0
  407. package/src/editor/tooltips.ts +47 -0
  408. package/src/editor/view3d.ts +119 -0
  409. package/src/editor/view3dState.ts +61 -0
  410. package/src/export/dxf.ts +1 -1
  411. package/src/export/pdf.ts +15 -7
  412. package/src/export/plan.ts +119 -35
  413. package/src/index.ts +5 -0
  414. package/src/input/keyboard.ts +2 -4
  415. package/src/model/constraints.ts +13 -4
  416. package/src/model/demo.ts +3 -3
  417. package/src/model/elevation.ts +6 -16
  418. package/src/model/faces.ts +153 -0
  419. package/src/model/facesolve.ts +322 -0
  420. package/src/model/history.ts +19 -11
  421. package/src/model/images.ts +35 -0
  422. package/src/model/links.ts +77 -0
  423. package/src/model/markup.ts +80 -0
  424. package/src/model/measurement.ts +26 -0
  425. package/src/model/openings.ts +6 -0
  426. package/src/model/planGeom.ts +174 -8
  427. package/src/model/project.ts +3 -2
  428. package/src/model/projectfile.ts +2 -1
  429. package/src/model/rooms.ts +1 -1
  430. package/src/model/settings.ts +8 -2
  431. package/src/model/sketch.ts +101 -14
  432. package/src/model/smooth.ts +51 -0
  433. package/src/model/store.ts +35 -4
  434. package/src/model/types.ts +170 -8
  435. package/src/model/wallfaces.ts +126 -17
  436. package/src/model/wallshape.ts +97 -0
  437. package/src/react/BottomBar.tsx +263 -46
  438. package/src/react/Buttons.tsx +50 -9
  439. package/src/react/CadEditor.tsx +133 -30
  440. package/src/react/ConfirmSheet.tsx +38 -0
  441. package/src/react/ConflictSheet.tsx +19 -12
  442. package/src/react/Drawer.tsx +213 -110
  443. package/src/react/Duck.tsx +50 -0
  444. package/src/react/Empty3D.tsx +32 -0
  445. package/src/react/FitScroll.tsx +19 -0
  446. package/src/react/Icon.native.tsx +5 -0
  447. package/src/react/Icon.web.tsx +5 -0
  448. package/src/react/KeypadSheet.tsx +253 -60
  449. package/src/react/LaserTray.tsx +12 -8
  450. package/src/react/MarkupPanel.tsx +150 -0
  451. package/src/react/PageThumb.native.tsx +18 -7
  452. package/src/react/PageThumb.web.tsx +4 -4
  453. package/src/react/PlanCanvas.native.tsx +151 -34
  454. package/src/react/PlanCanvas.web.tsx +15 -5
  455. package/src/react/Popovers.tsx +116 -51
  456. package/src/react/Rail.tsx +360 -27
  457. package/src/react/ShapeMenu.tsx +66 -0
  458. package/src/react/Sheet.tsx +56 -11
  459. package/src/react/Slider.tsx +48 -0
  460. package/src/react/Toast.tsx +16 -9
  461. package/src/react/ToolTip.tsx +49 -0
  462. package/src/react/TopBar.tsx +97 -39
  463. package/src/react/Tween.native.tsx +36 -0
  464. package/src/react/Tween.tsx +24 -0
  465. package/src/react/View3D.native.tsx +131 -0
  466. package/src/react/View3D.web.tsx +131 -0
  467. package/src/react/View3DHost.ts +367 -0
  468. package/src/react/corners.ts +52 -0
  469. package/src/react/easing.ts +14 -0
  470. package/src/react/host.ts +8 -3
  471. package/src/react/hostKeypad.ts +66 -0
  472. package/src/react/motion.tsx +239 -0
  473. package/src/react/ports.ts +78 -9
  474. package/src/react/saving.ts +80 -0
  475. package/src/react/store.ts +35 -3
  476. package/src/react/theme.ts +8 -0
  477. package/src/react/topRow.ts +71 -0
  478. package/src/react/touch.ts +10 -0
  479. package/src/render/ctx.ts +8 -0
  480. package/src/render/draw.ts +437 -175
  481. package/src/render/elevation.ts +349 -143
  482. package/src/render/figmaIcons.ts +22 -0
  483. package/src/render/hit.ts +140 -21
  484. package/src/render/icons.ts +69 -10
  485. package/src/render/linkTile.ts +297 -0
  486. package/src/render/markup.ts +91 -0
  487. package/src/render/navigate.ts +114 -0
  488. package/src/render/snap.ts +105 -22
  489. package/src/render/squircle.ts +73 -0
  490. package/src/render/view.ts +255 -19
  491. package/src/skia/SkiaDrawCtx.ts +163 -41
  492. package/src/skia/host.ts +46 -1
  493. package/src/solver/equations.ts +37 -2
  494. package/src/solver/gauss-newton.ts +125 -28
  495. package/src/solver/linalg.ts +141 -5
  496. package/src/solver/types.ts +5 -0
  497. package/src/styles.css +91 -42
  498. package/src/tools/annotate.ts +58 -29
  499. package/src/tools/chamfer.ts +103 -0
  500. package/src/tools/corner.ts +99 -0
  501. package/src/tools/dim.ts +524 -92
  502. package/src/tools/elevation.ts +1 -1
  503. package/src/tools/fillet.ts +82 -0
  504. package/src/tools/freehand.ts +20 -16
  505. package/src/tools/index.ts +17 -4
  506. package/src/tools/line.ts +59 -23
  507. package/src/tools/modify.ts +86 -24
  508. package/src/tools/openings.ts +8 -13
  509. package/src/tools/registry.ts +27 -2
  510. package/src/tools/select.ts +424 -75
  511. package/src/tools/shapes.ts +119 -58
  512. package/src/ui/ctx.ts +2 -1
  513. package/src/ui/domPorts.ts +23 -4
  514. package/src/ui/drawer.ts +6 -26
  515. package/src/ui/elevation.ts +10 -101
  516. package/src/ui/keypad.ts +8 -14
  517. package/src/ui/laser.ts +2 -2
  518. package/src/ui/menu.ts +63 -17
  519. package/src/ui/noteinput.ts +3 -3
  520. package/src/ui/opacity.ts +108 -0
  521. package/src/ui/palette.ts +67 -12
  522. package/src/ui/rail.ts +21 -7
  523. package/src/ui/topbar.ts +4 -14
  524. package/src/units/format.ts +18 -3
  525. package/src/view3d/build.ts +607 -0
  526. package/src/view3d/camera.ts +113 -0
  527. package/src/view3d/fatLines.ts +83 -0
  528. package/src/view3d/lines.ts +258 -0
  529. package/src/view3d/mesh.ts +111 -0
  530. package/src/view3d/poly.ts +104 -0
  531. package/src/view3d/viewer.ts +348 -0
  532. package/dist/react/ElevCanvas.native.d.ts +0 -3
  533. package/dist/react/ElevCanvas.native.js +0 -58
  534. package/dist/react/ElevCanvas.native.js.map +0 -1
  535. package/dist/react/ElevCanvas.web.d.ts +0 -4
  536. package/dist/react/ElevCanvas.web.js +0 -44
  537. package/dist/react/ElevCanvas.web.js.map +0 -1
  538. package/dist/react/ElevationSheet.d.ts +0 -2
  539. package/dist/react/ElevationSheet.js +0 -38
  540. package/dist/react/ElevationSheet.js.map +0 -1
  541. package/dist/render/elevationView.d.ts +0 -3
  542. package/dist/render/elevationView.js +0 -100
  543. package/dist/render/elevationView.js.map +0 -1
  544. package/src/react/ElevCanvas.native.tsx +0 -68
  545. package/src/react/ElevCanvas.web.tsx +0 -42
  546. package/src/react/ElevationSheet.tsx +0 -88
  547. package/src/render/elevationView.ts +0 -73
@@ -0,0 +1,71 @@
1
+ /*
2
+ Figma's corner smoothing, at the 60% it calls "iOS" (Apple's continuous corners): a corner keeps its circular arc of
3
+ radius r in the middle, but shorter, and eases into each straight side over a longer run — (1 + smoothing) · r of
4
+ the side instead of r — so the curvature has no sudden step where the side ends. The construction is Figma's own
5
+ (as published in its "Desperately seeking squircles", and the MIT figma-squircle package).
6
+ */
7
+ /** How much every rounded corner in the editor is smoothed: Figma's 60%, "iOS". */
8
+ export const CORNER_SMOOTHING = 0.6;
9
+ const rad = (deg) => (deg * Math.PI) / 180;
10
+ /** One corner's measures: the run along each side (p), the two Béziers' (a, b, c, d), the arc's half-sweep (alpha). */
11
+ function cornerParams(r, smoothing, budget) {
12
+ // a corner can't run further along its sides than half the shorter side: less smoothing where it can't fit
13
+ const s = Math.max(0, Math.min(smoothing, budget / r - 1));
14
+ const p = Math.min((1 + s) * r, budget);
15
+ const arcMeasure = 90 * (1 - s);
16
+ const arcSection = Math.sin(rad(arcMeasure / 2)) * r * Math.SQRT2;
17
+ const alpha = (90 - arcMeasure) / 2;
18
+ const p3ToP4 = r * Math.tan(rad(alpha / 2));
19
+ const beta = 45 * s;
20
+ const c = p3ToP4 * Math.cos(rad(beta));
21
+ const d = c * Math.tan(rad(beta));
22
+ const b = (p - arcSection - c - d) / 3;
23
+ return { p, a: 2 * b, b, c, d, alpha: rad(alpha) };
24
+ }
25
+ /**
26
+ * A rounded rectangle's path with smoothed corners (x, y its top left; r each corner's radius). A pill or a circle —
27
+ * r at least half the shorter side — is left round: smoothing has nothing to ease into there, as in Figma.
28
+ */
29
+ export function smoothRectPath(c, x, y, w, h, r, smoothing = CORNER_SMOOTHING) {
30
+ const budget = Math.min(w, h) / 2;
31
+ r = Math.max(0, Math.min(r, budget));
32
+ c.beginPath();
33
+ if (r <= 0 || smoothing <= 0 || r >= budget - 1e-6) {
34
+ c.moveTo(x + r, y);
35
+ c.arcTo(x + w, y, x + w, y + h, r);
36
+ c.arcTo(x + w, y + h, x, y + h, r);
37
+ c.arcTo(x, y + h, x, y, r);
38
+ c.arcTo(x, y, x + w, y, r);
39
+ c.closePath();
40
+ return;
41
+ }
42
+ const k = cornerParams(r, smoothing, budget);
43
+ // each corner drawn in its own frame: u along the side coming into it, v along the side leaving it, the corner's
44
+ // vertex at the origin — then turned to its place, clockwise from the top right
45
+ const corners = [
46
+ { ox: x + w, oy: y, ux: [1, 0], vx: [0, 1], turn: 0 },
47
+ { ox: x + w, oy: y + h, ux: [0, 1], vx: [-1, 0], turn: Math.PI / 2 },
48
+ { ox: x, oy: y + h, ux: [-1, 0], vx: [0, -1], turn: Math.PI },
49
+ { ox: x, oy: y, ux: [0, -1], vx: [1, 0], turn: (3 * Math.PI) / 2 },
50
+ ];
51
+ const { p, a, b, c: cc, d, alpha } = k;
52
+ corners.forEach((q, i) => {
53
+ const at = (u, v) => [q.ox + q.ux[0] * u + q.vx[0] * v, q.oy + q.ux[1] * u + q.vx[1] * v];
54
+ const [sx, sy] = at(-p, 0);
55
+ if (i === 0)
56
+ c.moveTo(sx, sy);
57
+ else
58
+ c.lineTo(sx, sy);
59
+ // into the arc, along the side
60
+ const [c1x, c1y] = at(-p + a, 0), [c2x, c2y] = at(-p + a + b, 0), [e1x, e1y] = at(-p + a + b + cc, d);
61
+ c.bezierCurveTo(c1x, c1y, c2x, c2y, e1x, e1y);
62
+ // the arc: radius r about (−r, r), its middle (90° less twice alpha) — angles turned with the corner
63
+ const [cx, cy] = at(-r, r);
64
+ c.arc(cx, cy, r, -Math.PI / 2 + alpha + q.turn, -alpha + q.turn, false);
65
+ // out of the arc onto the next side (the first Bézier mirrored across the corner's diagonal)
66
+ const [c3x, c3y] = at(0, p - a - b), [c4x, c4y] = at(0, p - a), [ex, ey] = at(0, p);
67
+ c.bezierCurveTo(c3x, c3y, c4x, c4y, ex, ey);
68
+ });
69
+ c.closePath();
70
+ }
71
+ //# sourceMappingURL=squircle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"squircle.js","sourceRoot":"","sources":["../../src/render/squircle.ts"],"names":[],"mappings":"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"}
@@ -13,6 +13,7 @@ export declare const cvs: {
13
13
  dpr: number;
14
14
  };
15
15
  export declare function initCanvas(el: HTMLCanvasElement): void;
16
+ /** The view of what's being edited: the page's plan, or the wall face open. */
16
17
  export declare function V(): View;
17
18
  export declare function mainT(): Xform;
18
19
  export declare const S: (T: Xform, x: number, y: number) => [number, number];
@@ -25,15 +26,49 @@ export declare function sketchBounds(sk: Sketch, inclOrigin?: boolean): {
25
26
  x1: number;
26
27
  y1: number;
27
28
  } | null;
29
+ /** The open wall's face, for the wall view to fit (registered by editor/elevation.ts; null on the plan). */
30
+ declare let wallBox: () => {
31
+ x0: number;
32
+ y0: number;
33
+ x1: number;
34
+ y1: number;
35
+ } | null;
36
+ export declare function setWallBox(fn: typeof wallBox): void;
28
37
  export declare function fitView(): void;
38
+ /** Size of the wall view's key plan (top right), pixels. */
39
+ export declare const keyPlanSize: (W: number) => number;
29
40
  /** Height of an image underlay in inches: its own, or from the picture's aspect ratio. */
30
41
  export declare function underlayH(u: Underlay): number;
31
42
  export type HandlePart = 'nw' | 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w';
32
- /** Screen positions of an underlay's resize handles: corners scale, side handles stretch. */
43
+ /** An image's middle, inches. */
44
+ export declare function underlayMid(u: Underlay): {
45
+ x: number;
46
+ y: number;
47
+ };
48
+ /** The point `lx` across and `ly` up from an image's middle in its own (unturned) frame, as it sits turned (inches). */
49
+ export declare function underlayAt(u: Underlay, lx: number, ly: number): {
50
+ x: number;
51
+ y: number;
52
+ };
53
+ /** A point (inches) as across and up from an image's middle, in its own unturned frame. */
54
+ export declare function underlayLocal(u: Underlay, x: number, y: number): {
55
+ x: number;
56
+ y: number;
57
+ };
58
+ /** An image's corners as it sits turned: top left, top right, bottom right, bottom left (inches). */
59
+ export declare function underlayCorners(u: Underlay): {
60
+ x: number;
61
+ y: number;
62
+ }[];
63
+ /**
64
+ * Screen positions of an underlay's resize handles: corners scale, side handles stretch. `ang`: how far the image is
65
+ * turned on screen (radians clockwise), for the side handles' bars to lie along its sides.
66
+ */
33
67
  export declare function underlayHandles(T: Xform, u: Underlay): {
34
68
  part: HandlePart;
35
69
  x: number;
36
70
  y: number;
71
+ ang: number;
37
72
  }[];
38
73
  /** A world-space box (y up: top > bottom). */
39
74
  export interface Box {
@@ -50,7 +85,62 @@ export declare function boxHandles(T: Xform, b: Box): {
50
85
  }[];
51
86
  /** Bounding box of a pen stroke or revision cloud. */
52
87
  export declare function noteBox(n: Note): Box | null;
53
- /** Handles for adjusting markup: arrow ends ('p0', 'p1'), or the box around a cloud or pen stroke. Text has none. */
88
+ /**
89
+ * A note's text is set at one size (NOTE_TEXT_SIZE px) and the whole note scaled to its size on the drawing: its
90
+ * letters are `ts` inches tall (NOTE_TEXT_IN unless set, from NOTE_TEXT_MIN_IN to NOTE_TEXT_MAX_IN), so it zooms with
91
+ * the drawing rather than staying one size on screen. (Set at one size, the text is measured — and on a device its
92
+ * font made — once, not at every step of a zoom.)
93
+ */
94
+ export { NOTE_TEXT_SIZE, NOTE_TEXT_IN, NOTE_TEXT_MIN_IN, NOTE_TEXT_MAX_IN, noteTextIn } from '../model/markup';
95
+ export declare const noteFont: (size: number) => string;
96
+ /** What measures text: a drawing context, or anything that sets a font and measures with it. */
97
+ export interface TextMeasure {
98
+ font: string;
99
+ measureText(s: string): {
100
+ width: number;
101
+ };
102
+ }
103
+ export declare function setTextMeasure(m: TextMeasure | null): void;
104
+ export declare const textMeasure: () => TextMeasure;
105
+ /**
106
+ * A note's box on screen at its size: left / top corner, width, height; `mid` is the text's middle line, `k` its scale
107
+ * from the unscaled size (padding and corners grow with it). Measured with `c` (the drawing's context when drawing).
108
+ */
109
+ export declare function noteTextBox(T: Xform, n: Note, c?: TextMeasure): {
110
+ x: number;
111
+ y: number;
112
+ w: number;
113
+ h: number;
114
+ mid: number;
115
+ k: number;
116
+ size: number;
117
+ };
118
+ /** How much a link's tile shows on the screen: all of it, its picture or icon alone, or a pin. */
119
+ export type LinkLod = 'full' | 'compact' | 'pin';
120
+ /** A tile on the screen: its square (top left, side), its middle, how much it shows, and its scale from the layout. */
121
+ export interface LinkBox {
122
+ x: number;
123
+ y: number;
124
+ s: number;
125
+ cx: number;
126
+ cy: number;
127
+ lod: LinkLod;
128
+ k: number;
129
+ }
130
+ export declare function linkBox(T: Xform, n: Note): LinkBox;
131
+ /** Where a leader leaves the tile toward screen point tx, ty: the edge of its square (null: the point's under it). */
132
+ export declare function leaderStart(b: LinkBox, tx: number, ty: number): [number, number] | null;
133
+ /** A tile's leader on the screen, from the tile's edge to its tip (null: none, or its tip is under the tile). */
134
+ export declare function leaderPath(T: Xform, n: Note, b?: LinkBox): [[number, number], [number, number]] | null;
135
+ /** A tile's handle: its leader's tip (tiles are sized together, by the Tile size slider, not one by one). */
136
+ export declare function linkHandles(T: Xform, n: Note): {
137
+ part: string;
138
+ x: number;
139
+ y: number;
140
+ }[];
141
+ /** A tile's box and leader tip on the screen, for fitting and box selection: left, top, right, bottom. */
142
+ export declare function linkExtent(T: Xform, n: Note): [number, number, number, number];
143
+ /** Handles for adjusting markup: a line's or arrow's ends ('p0', 'p1'), the box around a cloud, shape or pen stroke, a note's corners. */
54
144
  export declare function noteHandles(T: Xform, n: Note): {
55
145
  part: string;
56
146
  x: number;
@@ -70,10 +160,33 @@ export declare function setImageResolver(fn: ((ref: string) => Promise<string>)
70
160
  export declare const isStoredRef: (src: string) => boolean;
71
161
  /** `to` is the same picture as `from` (an upload's reference for the local copy): reuse what's loaded, no flicker. */
72
162
  export declare function aliasImage(from: string, to: string): void;
163
+ /** A new frame is being drawn: let go of the least recently used past those kept, if the last frame didn't draw them. */
164
+ export declare function nextThumbFrame(): void;
165
+ export declare function linkThumb(src: string): ImageLike;
73
166
  export declare function imgCache(src: string | undefined): ImageLike | null;
74
167
  export declare function setFrameRenderer(fn: (() => void) | null): void;
168
+ /** Whether the drawing (not only its moving parts) has changed since the host last kept it; the host then marks it kept. */
169
+ export declare const drawingChanged: () => boolean;
170
+ export declare function markDrawingKept(): void;
171
+ /** Whether the photos and plans under the drawing have changed since the host last kept them; it then marks them kept. */
172
+ export declare const imagesChanged: () => boolean;
173
+ export declare function markImagesKept(): void;
174
+ /**
175
+ * Paint a frame that's waiting, now. A press is tested against the tap targets as last painted (the chips), so one that
176
+ * comes between an edit and the next frame — a quick tap, a slow frame — would land on where a chip was, not where it is.
177
+ */
178
+ export declare function paintPending(): void;
179
+ /**
180
+ * The view as the host moved it itself — a device pans and zooms on its UI thread, at the screen's rate, and tells
181
+ * this thread where it got to: take it, and draw the plan there on the next frame.
182
+ */
183
+ export declare function setViewFromHost(s: number, cx: number, cy: number): void;
75
184
  /** Repaint on the next animation frame (several requests in one frame paint once). */
76
185
  export declare function requestDraw(): void;
186
+ /** Only the page's photos and plans changed (one turned, faded, moved or sized): the drawing over them can be reused. */
187
+ export declare function requestImageDraw(): void;
188
+ /** Only the moving parts changed (a tool's preview, the snap marker, the selection box): the rest can be reused. */
189
+ export declare function requestOverlayDraw(): void;
77
190
  /** The plan's size in display points, where no browser canvas measures it (a device). */
78
191
  export declare function setViewSize(W: number, H: number): void;
79
192
  export declare function resize(): void;
@@ -1,8 +1,13 @@
1
1
  import { clamp } from '../model/geom';
2
+ import { faceEdit } from '../model/faces';
2
3
  import { page, SK } from '../model/store';
4
+ import { CLOSED_SHAPES, isSegment, NOTE_TEXT_SIZE, noteTextIn } from '../model/markup';
5
+ import { LINK_FULL_PX, LINK_PIN_PX, LINK_TILE_PX, linkSide } from '../model/links';
3
6
  import { draw } from './draw';
4
7
  import { onSessionReset, sessionState } from '../model/session';
5
8
  import { refreshDrawerSoon } from '../editor/ports';
9
+ import { FONT } from './theme';
10
+ import { ZOOM_MAX, ZOOM_MIN } from './navigate';
6
11
  export const cvs = sessionState(() => ({
7
12
  cv: null,
8
13
  ctx: null,
@@ -14,8 +19,13 @@ export function initCanvas(el) {
14
19
  cvs.cv = el;
15
20
  cvs.ctx = el.getContext('2d');
16
21
  }
22
+ /** The pan/zoom of each wall face that's been open, by page and face. */
23
+ const faceViews = sessionState(() => ({}));
24
+ /** The view of what's being edited: the page's plan, or the wall face open. */
17
25
  export function V() {
18
26
  const pg = page();
27
+ if (faceEdit.key)
28
+ return (faceViews[pg.id + '|' + faceEdit.key] ??= { cx: 0, cy: 48, s: 2 });
19
29
  if (!pg.view)
20
30
  pg.view = { cx: 84, cy: 120, s: 2 };
21
31
  return pg.view;
@@ -32,16 +42,16 @@ export function s2w(sx, sy) {
32
42
  export function zoomAt(sx, sy, f) {
33
43
  const v = V();
34
44
  const [wx, wy] = s2w(sx, sy);
35
- v.s = clamp(v.s * f, 0.04, 400);
45
+ v.s = clamp(v.s * f, ZOOM_MIN, ZOOM_MAX);
36
46
  v.cx = wx - (sx - cvs.W / 2) / v.s;
37
47
  v.cy = wy + (sy - cvs.H / 2) / v.s;
38
- requestDraw();
48
+ requestViewDraw();
39
49
  }
40
50
  export function panBy(dx, dy) {
41
51
  const v = V();
42
52
  v.cx -= dx / v.s;
43
53
  v.cy += dy / v.s;
44
- requestDraw();
54
+ requestViewDraw();
45
55
  }
46
56
  export function sketchBounds(sk, inclOrigin = true) {
47
57
  let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
@@ -62,6 +72,14 @@ export function sketchBounds(sk, inclOrigin = true) {
62
72
  for (const n of sk.notes) {
63
73
  if (n.pts)
64
74
  n.pts.forEach(p => add(p[0], p[1]));
75
+ else if (n.type === 'link') {
76
+ // its tile, and what its leader points at
77
+ const h = linkSide(n) / 2, x = n.x ?? 0, y = n.y ?? 0;
78
+ add(x - h, y - h);
79
+ add(x + h, y + h);
80
+ if (n.leader)
81
+ add(n.leader[0], n.leader[1]);
82
+ }
65
83
  else
66
84
  add(n.x, n.y);
67
85
  }
@@ -69,14 +87,23 @@ export function sketchBounds(sk, inclOrigin = true) {
69
87
  return null;
70
88
  return { x0, y0, x1, y1 };
71
89
  }
90
+ /** The open wall's face, for the wall view to fit (registered by editor/elevation.ts; null on the plan). */
91
+ let wallBox = () => null;
92
+ export function setWallBox(fn) { wallBox = fn; }
72
93
  export function fitView() {
73
- const sk = SK(), pg = page();
94
+ const sk = SK(), pg = page(), wallFace = wallBox();
74
95
  let b = sketchBounds(sk);
75
- if (pg.underlay) {
76
- const u = pg.underlay, h = underlayH(u);
77
- const bb = { x0: u.x, y0: u.y - h, x1: u.x + u.w, y1: u.y };
78
- b = b ? { x0: Math.min(b.x0, bb.x0), y0: Math.min(b.y0, bb.y0), x1: Math.max(b.x1, bb.x1), y1: Math.max(b.y1, bb.y1) } : bb;
79
- }
96
+ // the wall view fits the wall itself (and what's drawn on it); the page's photos and plans are under the plan, not
97
+ // on the wall — counting them zoomed a wall out to take in a plan nobody could see there
98
+ if (faceEdit.key && wallFace)
99
+ b = b ? { x0: Math.min(b.x0, wallFace.x0), y0: Math.min(b.y0, wallFace.y0), x1: Math.max(b.x1, wallFace.x1), y1: Math.max(b.y1, wallFace.y1) } : wallFace;
100
+ if (!faceEdit.key)
101
+ for (const u of pg.underlays) {
102
+ // (as it sits, turned)
103
+ const cs = underlayCorners(u), xs = cs.map(q => q.x), ys = cs.map(q => q.y);
104
+ const bb = { x0: Math.min(...xs), y0: Math.min(...ys), x1: Math.max(...xs), y1: Math.max(...ys) };
105
+ b = b ? { x0: Math.min(b.x0, bb.x0), y0: Math.min(b.y0, bb.y0), x1: Math.max(b.x1, bb.x1), y1: Math.max(b.y1, bb.y1) } : bb;
106
+ }
80
107
  const v = V();
81
108
  if (!b || (b.x1 - b.x0 < 1 && b.y1 - b.y0 < 1)) {
82
109
  v.cx = 0;
@@ -86,8 +113,10 @@ export function fitView() {
86
113
  return;
87
114
  }
88
115
  const { W, H } = cvs;
89
- const phone = W < 760;
90
- const L = phone ? 78 : 110, R = phone ? 28 : 110, Tp = phone ? 200 : 150, Bt = 120;
116
+ const phone = W < 760, wall = faceEdit.key != null;
117
+ // a wall face: room for the dimensions along the floor and beside it, and for the key plan (top right)
118
+ const L = phone ? 78 : 110, R = wall ? (phone ? 64 : Math.max(110, keyPlanSize(W) + 40)) : phone ? 28 : 110;
119
+ const Tp = phone ? 200 : wall ? 110 : 150, Bt = wall ? (phone ? 210 : 190) : 120;
91
120
  const bw = Math.max(b.x1 - b.x0, 24), bh = Math.max(b.y1 - b.y0, 24);
92
121
  v.s = clamp(Math.min((W - L - R) / bw, (H - Tp - Bt) / bh), 0.08, 60);
93
122
  const scx = (L + W - R) / 2, scy = (Tp + H - Bt) / 2;
@@ -95,6 +124,8 @@ export function fitView() {
95
124
  v.cy = (b.y0 + b.y1) / 2 + (scy - H / 2) / v.s;
96
125
  requestDraw();
97
126
  }
127
+ /** Size of the wall view's key plan (top right), pixels. */
128
+ export const keyPlanSize = (W) => Math.min(150, Math.max(96, W * 0.16));
98
129
  /** Height of an image underlay in inches: its own, or from the picture's aspect ratio. */
99
130
  export function underlayH(u) {
100
131
  if (u.h)
@@ -102,9 +133,39 @@ export function underlayH(u) {
102
133
  const img = imgCache(u.src);
103
134
  return img && img.naturalWidth ? (u.w * img.naturalHeight) / img.naturalWidth : u.w * 0.7;
104
135
  }
105
- /** Screen positions of an underlay's resize handles: corners scale, side handles stretch. */
136
+ /*
137
+ An image turned (its `rot`) turns about its middle: x, y, w and h are its box before it's turned. These give its
138
+ points as they sit turned. The world's y runs up, so a turn clockwise as it's seen is the other way round in it.
139
+ */
140
+ const turnOf = (u) => (-(u.rot ?? 0) * Math.PI) / 180;
141
+ /** An image's middle, inches. */
142
+ export function underlayMid(u) {
143
+ return { x: u.x + u.w / 2, y: u.y - underlayH(u) / 2 };
144
+ }
145
+ /** The point `lx` across and `ly` up from an image's middle in its own (unturned) frame, as it sits turned (inches). */
146
+ export function underlayAt(u, lx, ly) {
147
+ const m = underlayMid(u), a = turnOf(u), c = Math.cos(a), s = Math.sin(a);
148
+ return { x: m.x + lx * c - ly * s, y: m.y + lx * s + ly * c };
149
+ }
150
+ /** A point (inches) as across and up from an image's middle, in its own unturned frame. */
151
+ export function underlayLocal(u, x, y) {
152
+ const m = underlayMid(u), a = turnOf(u), c = Math.cos(a), s = Math.sin(a), dx = x - m.x, dy = y - m.y;
153
+ return { x: dx * c + dy * s, y: -dx * s + dy * c };
154
+ }
155
+ /** Where an image's handles sit in its own frame (as fractions of its half width and half height), in boxHandles' order. */
156
+ const HANDLES = [['nw', -1, 1], ['n', 0, 1], ['ne', 1, 1], ['e', 1, 0], ['se', 1, -1], ['s', 0, -1], ['sw', -1, -1], ['w', -1, 0]];
157
+ /** An image's corners as it sits turned: top left, top right, bottom right, bottom left (inches). */
158
+ export function underlayCorners(u) {
159
+ const hw = u.w / 2, hh = underlayH(u) / 2;
160
+ return [[-1, 1], [1, 1], [1, -1], [-1, -1]].map(([i, j]) => underlayAt(u, i * hw, j * hh));
161
+ }
162
+ /**
163
+ * Screen positions of an underlay's resize handles: corners scale, side handles stretch. `ang`: how far the image is
164
+ * turned on screen (radians clockwise), for the side handles' bars to lie along its sides.
165
+ */
106
166
  export function underlayHandles(T, u) {
107
- return boxHandles(T, { left: u.x, right: u.x + u.w, top: u.y, bottom: u.y - underlayH(u) });
167
+ const hw = u.w / 2, hh = underlayH(u) / 2, ang = ((u.rot ?? 0) * Math.PI) / 180;
168
+ return HANDLES.map(([part, i, j]) => { const p = underlayAt(u, i * hw, j * hh), [x, y] = S(T, p.x, p.y); return { part, x, y, ang }; });
108
169
  }
109
170
  /** Screen positions of the 4 corner + 4 side handles of a box. */
110
171
  export function boxHandles(T, b) {
@@ -122,16 +183,96 @@ export function noteBox(n) {
122
183
  const xs = n.pts.map(p => p[0]), ys = n.pts.map(p => p[1]);
123
184
  return { left: Math.min(...xs), right: Math.max(...xs), top: Math.max(...ys), bottom: Math.min(...ys) };
124
185
  }
125
- /** Handles for adjusting markup: arrow ends ('p0', 'p1'), or the box around a cloud or pen stroke. Text has none. */
186
+ /**
187
+ * A note's text is set at one size (NOTE_TEXT_SIZE px) and the whole note scaled to its size on the drawing: its
188
+ * letters are `ts` inches tall (NOTE_TEXT_IN unless set, from NOTE_TEXT_MIN_IN to NOTE_TEXT_MAX_IN), so it zooms with
189
+ * the drawing rather than staying one size on screen. (Set at one size, the text is measured — and on a device its
190
+ * font made — once, not at every step of a zoom.)
191
+ */
192
+ export { NOTE_TEXT_SIZE, NOTE_TEXT_IN, NOTE_TEXT_MIN_IN, NOTE_TEXT_MAX_IN, noteTextIn } from '../model/markup';
193
+ export const noteFont = (size) => '600 ' + size + 'px ' + FONT;
194
+ /**
195
+ * What measures text outside drawing a frame (picking a note, its handles, a box select): the browser's canvas, or on
196
+ * a device — which has no browser canvas — the host's (Skia, with the drawing's own fonts: skia/host.ts), so a note
197
+ * is picked by the same box it's drawn in. Failing both, an estimate rather than a crash.
198
+ */
199
+ let hostMeasure = null;
200
+ export function setTextMeasure(m) { hostMeasure = m; }
201
+ const estimate = {
202
+ font: '',
203
+ measureText(s) { const size = Number(/([\d.]+)px/.exec(this.font)?.[1] ?? 14); return { width: s.length * size * 0.6 }; },
204
+ };
205
+ export const textMeasure = () => cvs.ctx ?? hostMeasure ?? estimate;
206
+ /**
207
+ * A note's box on screen at its size: left / top corner, width, height; `mid` is the text's middle line, `k` its scale
208
+ * from the unscaled size (padding and corners grow with it). Measured with `c` (the drawing's context when drawing).
209
+ */
210
+ export function noteTextBox(T, n, c = textMeasure()) {
211
+ const size = noteTextIn(n) * T.s, k = size / NOTE_TEXT_SIZE, [x, mid] = S(T, n.x, n.y);
212
+ c.font = noteFont(NOTE_TEXT_SIZE);
213
+ const w = (c.measureText(n.text).width + 16) * k;
214
+ return { x, y: mid - 14 * k, w, h: 28 * k, mid, k, size };
215
+ }
216
+ export function linkBox(T, n) {
217
+ const [cx, cy] = S(T, n.x ?? 0, n.y ?? 0), side = linkSide(n) * T.s;
218
+ const lod = side >= LINK_FULL_PX ? 'full' : side >= LINK_PIN_PX ? 'compact' : 'pin';
219
+ const s = Math.max(LINK_PIN_PX, side);
220
+ return { x: cx - s / 2, y: cy - s / 2, s, cx, cy, lod, k: s / LINK_TILE_PX };
221
+ }
222
+ /** Where a leader leaves the tile toward screen point tx, ty: the edge of its square (null: the point's under it). */
223
+ export function leaderStart(b, tx, ty) {
224
+ const dx = tx - b.cx, dy = ty - b.cy, h = b.s / 2, m = Math.max(Math.abs(dx), Math.abs(dy));
225
+ if (m <= h)
226
+ return null;
227
+ return [b.cx + (dx * h) / m, b.cy + (dy * h) / m];
228
+ }
229
+ /** A tile's leader on the screen, from the tile's edge to its tip (null: none, or its tip is under the tile). */
230
+ export function leaderPath(T, n, b = linkBox(T, n)) {
231
+ if (!n.leader)
232
+ return null;
233
+ const tip = S(T, n.leader[0], n.leader[1]), start = leaderStart(b, tip[0], tip[1]);
234
+ return start ? [start, tip] : null;
235
+ }
236
+ /** A tile's handle: its leader's tip (tiles are sized together, by the Tile size slider, not one by one). */
237
+ export function linkHandles(T, n) {
238
+ if (!n.leader)
239
+ return [];
240
+ const [x, y] = S(T, n.leader[0], n.leader[1]);
241
+ return [{ part: 'tip', x, y }];
242
+ }
243
+ /** A tile's box and leader tip on the screen, for fitting and box selection: left, top, right, bottom. */
244
+ export function linkExtent(T, n) {
245
+ const b = linkBox(T, n);
246
+ let l = b.x, t = b.y, r = b.x + b.s, bt = b.y + b.s;
247
+ if (n.leader) {
248
+ const [x, y] = S(T, n.leader[0], n.leader[1]);
249
+ l = Math.min(l, x);
250
+ r = Math.max(r, x);
251
+ t = Math.min(t, y);
252
+ bt = Math.max(bt, y);
253
+ }
254
+ return [l, t, r, bt];
255
+ }
256
+ /** Handles for adjusting markup: a line's or arrow's ends ('p0', 'p1'), the box around a cloud, shape or pen stroke, a note's corners. */
126
257
  export function noteHandles(T, n) {
127
- if (n.type === 'arrow' && n.pts && n.pts.length >= 2) {
258
+ if (n.type === 'link')
259
+ return linkHandles(T, n);
260
+ if (isSegment(n) && n.pts && n.pts.length >= 2) {
128
261
  const last = n.pts.length - 1;
129
262
  return [{ part: 'p0', p: n.pts[0] }, { part: 'p' + last, p: n.pts[last] }].map(({ part, p }) => { const [x, y] = S(T, p[0], p[1]); return { part, x, y }; });
130
263
  }
131
- if (n.type === 'cloud' || n.type === 'pen') {
264
+ if (n.type === 'cloud' || n.type === 'pen' || CLOSED_SHAPES.has(n.type)) {
132
265
  const b = noteBox(n);
133
266
  return b ? boxHandles(T, b) : [];
134
267
  }
268
+ if (n.type === 'text' && n.text) {
269
+ // a note scales as a whole (its text can't stretch): corners only
270
+ const b = noteTextBox(T, n);
271
+ return [
272
+ { part: 'nw', x: b.x, y: b.y }, { part: 'ne', x: b.x + b.w, y: b.y },
273
+ { part: 'se', x: b.x + b.w, y: b.y + b.h }, { part: 'sw', x: b.x, y: b.y + b.h },
274
+ ];
275
+ }
135
276
  return [];
136
277
  }
137
278
  const browserImage = (src, onLoad) => {
@@ -142,7 +283,7 @@ const browserImage = (src, onLoad) => {
142
283
  };
143
284
  let loadImage = browserImage;
144
285
  /** Load pictures some other way from now on (null: the browser's). */
145
- export function setImageLoader(fn) { loadImage = fn || browserImage; imgs.clear(); }
286
+ export function setImageLoader(fn) { loadImage = fn || browserImage; imgs.clear(); thumbs.clear(); }
146
287
  /** Turns a stored reference (`stored:…`, kept by a drawing in place of a URL) into a URL to load. */
147
288
  let resolveStored = null;
148
289
  export function setImageResolver(fn) { resolveStored = fn; imgs.clear(); }
@@ -154,6 +295,33 @@ export function aliasImage(from, to) {
154
295
  if (im && !imgs.has(to))
155
296
  imgs.set(to, im);
156
297
  }
298
+ /**
299
+ * Small pictures of linked files (a link tile's), by address: kept apart from the page's photos and plans, and only the
300
+ * last few dozen used — a drawing can link many — each drawn again as it arrives. What's let go goes between frames,
301
+ * and never what the last frame drew (so many on the screen at once don't load each other out, again and again).
302
+ */
303
+ const thumbs = new Map(), THUMBS_KEPT = 64;
304
+ let thumbFrame = 0;
305
+ /** A new frame is being drawn: let go of the least recently used past those kept, if the last frame didn't draw them. */
306
+ export function nextThumbFrame() {
307
+ for (const [k, o] of thumbs) {
308
+ if (thumbs.size <= THUMBS_KEPT || o.used === thumbFrame)
309
+ break;
310
+ thumbs.delete(k);
311
+ }
312
+ thumbFrame++;
313
+ }
314
+ export function linkThumb(src) {
315
+ let t = thumbs.get(src);
316
+ // (the most recently used last: the first is the one to let go)
317
+ if (t)
318
+ thumbs.delete(src);
319
+ else
320
+ t = { im: loadImage(src, () => requestDraw()), used: 0 };
321
+ t.used = thumbFrame;
322
+ thumbs.set(src, t);
323
+ return t.im;
324
+ }
157
325
  export function imgCache(src) {
158
326
  if (!src)
159
327
  return null;
@@ -179,17 +347,74 @@ let drawFrame = 0;
179
347
  /** What paints a frame: the browser canvas by default; a host can draw elsewhere (e.g. Skia on a device). */
180
348
  let paint = () => draw();
181
349
  export function setFrameRenderer(fn) { paint = fn || (() => draw()); }
182
- /** Repaint on the next animation frame (several requests in one frame paint once). */
183
- export function requestDraw() {
350
+ /*
351
+ What the next frame has to redraw, for a host that keeps the drawing as a picture (see PlanCanvas.native): the
352
+ drawing itself only when something in it changed (requestDraw), or when the view moved (the host compares the view
353
+ it last drew at); otherwise just the moving parts, as a tool follows the pointer (requestOverlayDraw). The page's
354
+ photos and plans are kept apart, under it: turning, fading, moving or sizing one redraws only them
355
+ (requestImageDraw), not every wall and dimension over them. A host that redraws everything each frame (the
356
+ browser) ignores this.
357
+ */
358
+ const frame = { drawingChanged: true, imagesChanged: true };
359
+ /** Whether the drawing (not only its moving parts) has changed since the host last kept it; the host then marks it kept. */
360
+ export const drawingChanged = () => frame.drawingChanged;
361
+ export function markDrawingKept() { frame.drawingChanged = false; }
362
+ /** Whether the photos and plans under the drawing have changed since the host last kept them; it then marks them kept. */
363
+ export const imagesChanged = () => frame.imagesChanged;
364
+ export function markImagesKept() { frame.imagesChanged = false; }
365
+ function schedule() {
184
366
  if (drawFrame)
185
367
  return;
186
368
  drawFrame = requestAnimationFrame(() => { drawFrame = 0; paint(); });
187
369
  }
370
+ /**
371
+ * Paint a frame that's waiting, now. A press is tested against the tap targets as last painted (the chips), so one that
372
+ * comes between an edit and the next frame — a quick tap, a slow frame — would land on where a chip was, not where it is.
373
+ */
374
+ export function paintPending() {
375
+ if (!drawFrame)
376
+ return;
377
+ cancelAnimationFrame(drawFrame);
378
+ drawFrame = 0;
379
+ paint();
380
+ }
381
+ /**
382
+ * The view as the host moved it itself — a device pans and zooms on its UI thread, at the screen's rate, and tells
383
+ * this thread where it got to: take it, and draw the plan there on the next frame.
384
+ */
385
+ export function setViewFromHost(s, cx, cy) {
386
+ const v = V();
387
+ v.s = s;
388
+ v.cx = cx;
389
+ v.cy = cy;
390
+ schedule();
391
+ }
392
+ /** Repaint on the next animation frame (several requests in one frame paint once). */
393
+ export function requestDraw() {
394
+ frame.drawingChanged = true;
395
+ frame.imagesChanged = true;
396
+ schedule();
397
+ }
398
+ /** Only the page's photos and plans changed (one turned, faded, moved or sized): the drawing over them can be reused. */
399
+ export function requestImageDraw() {
400
+ frame.imagesChanged = true;
401
+ schedule();
402
+ }
403
+ /** Only the moving parts changed (a tool's preview, the snap marker, the selection box): the rest can be reused. */
404
+ export function requestOverlayDraw() { schedule(); }
405
+ /**
406
+ * The view panned or zoomed: draw the next frame there. The drawing itself didn't change — a host that keeps it sees
407
+ * the view moved, and draws it again at the new one, with nothing waiting for the view to settle first.
408
+ */
409
+ function requestViewDraw() { schedule(); }
188
410
  onSessionReset(() => {
189
411
  if (drawFrame)
190
412
  cancelAnimationFrame(drawFrame);
191
413
  drawFrame = 0;
414
+ frame.drawingChanged = true;
415
+ frame.imagesChanged = true;
192
416
  imgs.clear();
417
+ thumbs.clear();
193
418
  });
194
419
  /** The plan's size in display points, where no browser canvas measures it (a device). */
195
420
  export function setViewSize(W, H) {