@pascal-app/editor 1.0.7 → 1.1.1

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 (336) hide show
  1. package/package.json +9 -15
  2. package/src/components/editor/action-menu-button.tsx +1 -1
  3. package/src/components/editor/alignment-3d-guide-layer.tsx +1 -1
  4. package/src/components/editor/custom-camera-controls.tsx +101 -45
  5. package/src/components/editor/duplicate-node.ts +2 -1
  6. package/src/components/editor/editor-layout-v2.tsx +1 -1
  7. package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
  8. package/src/components/editor/first-person-controls.tsx +20 -1983
  9. package/src/components/editor/fit-scene-framing.test.ts +318 -0
  10. package/src/components/editor/fit-scene-framing.ts +142 -0
  11. package/src/components/editor/floating-action-menu.tsx +5 -3
  12. package/src/components/editor/floating-building-action-menu.tsx +1 -1
  13. package/src/components/editor/floor-openings-overlay.tsx +1 -1
  14. package/src/components/editor/floor-region-controls.tsx +8 -4
  15. package/src/components/editor/floorplan-navigation-presentation.test.ts +0 -40
  16. package/src/components/editor/floorplan-navigation-presentation.ts +0 -66
  17. package/src/components/editor/floorplan-panel.tsx +199 -30
  18. package/src/components/editor/floorplan-wall-typing-keys.test.tsx +152 -0
  19. package/src/components/editor/floorplan-wall-typing-keys.ts +79 -0
  20. package/src/components/editor/follow-pascal.test.tsx +1 -2
  21. package/src/components/editor/follow-pascal.tsx +1 -1
  22. package/src/components/editor/footprint-height-handle.tsx +2 -2
  23. package/src/components/editor/group-actions.ts +1 -2
  24. package/src/components/editor/group-floating-action-menu.tsx +1 -1
  25. package/src/components/editor/group-move-3d.ts +1 -2
  26. package/src/components/editor/group-rotate-handle.tsx +1 -2
  27. package/src/components/editor/group-transform-shared.ts +3 -1
  28. package/src/components/editor/handles/use-handle-drag.ts +1 -2
  29. package/src/components/editor/index.tsx +43 -13
  30. package/src/components/editor/inspector-extension-actions.tsx +1 -1
  31. package/src/components/editor/measurement-pill.tsx +1 -1
  32. package/src/components/editor/node-arrow-handles.tsx +2 -2
  33. package/src/components/editor/open-wall-ends-3d-layer.tsx +2 -3
  34. package/src/components/editor/open-wall-ends-hint.tsx +1 -1
  35. package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
  36. package/src/components/editor/quick-measurement-card.tsx +1 -1
  37. package/src/components/editor/room-controls.tsx +10 -6
  38. package/src/components/editor/room-handles-2d.tsx +3 -3
  39. package/src/components/editor/room-handles.tsx +4 -5
  40. package/src/components/editor/room-highlight.tsx +6 -105
  41. package/src/components/editor/room-labels.test.ts +8 -822
  42. package/src/components/editor/room-labels.tsx +41 -1670
  43. package/src/components/editor/room-webgpu-materials.test.ts +1 -1
  44. package/src/components/editor/selection-manager.tsx +5 -3
  45. package/src/components/editor/site-edge-labels.tsx +7 -3
  46. package/src/components/editor/snapshot-capture-overlay.tsx +2 -5
  47. package/src/components/editor/use-floorplan-background-placement.ts +2 -1
  48. package/src/components/editor/views/plugins-view.tsx +1 -1
  49. package/src/components/editor/views/view-bar.tsx +9 -9
  50. package/src/components/editor/wall-measurement-label.tsx +31 -15
  51. package/src/components/editor/wall-move-side-handles.tsx +11 -7
  52. package/src/components/editor/wall-pull-grip.tsx +2 -2
  53. package/src/components/editor/wall-region-handles.tsx +2 -2
  54. package/src/components/editor/wall-snap-beacon-layer.tsx +53 -32
  55. package/src/components/editor-2d/floorplan-group-move.tsx +3 -4
  56. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +13 -13
  57. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +2 -3
  58. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +2 -1
  59. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +23 -6
  60. package/src/components/editor-2d/renderers/floorplan-annotation-layout.test.ts +0 -37
  61. package/src/components/editor-2d/renderers/floorplan-annotation-layout.ts +0 -30
  62. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +13 -3
  63. package/src/components/editor-2d/renderers/floorplan-draft-measurement.tsx +7 -1
  64. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +14 -3
  65. package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.test.tsx +35 -0
  66. package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.tsx +65 -0
  67. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +30 -10
  68. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +39 -35
  69. package/src/components/systems/roof/roof-edit-system.test.tsx +15 -1
  70. package/src/components/systems/roof/roof-edit-system.tsx +33 -21
  71. package/src/components/tools/distribution/distribution-run-tool.test.tsx +371 -3
  72. package/src/components/tools/distribution/distribution-run-tool.tsx +151 -33
  73. package/src/components/tools/item/move-tool.tsx +1 -6
  74. package/src/components/tools/item/placement-history.test.tsx +1 -1
  75. package/src/components/tools/item/use-placement-coordinator.tsx +3 -4
  76. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +7 -3
  77. package/src/components/tools/registry/move-registry-node-tool.test.ts +42 -1
  78. package/src/components/tools/registry/move-registry-node-tool.tsx +39 -16
  79. package/src/components/tools/shared/cursor-sphere.tsx +1 -1
  80. package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
  81. package/src/components/tools/shared/placement-box.tsx +2 -6
  82. package/src/components/tools/shared/placement-coordinate-input.tsx +94 -0
  83. package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
  84. package/src/components/tools/shared/segment-angle.ts +0 -4
  85. package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
  86. package/src/components/tools/tool-manager.tsx +1 -24
  87. package/src/components/tools/wall/wall-typed-entry.test.ts +99 -0
  88. package/src/components/tools/wall/wall-typed-entry.ts +70 -0
  89. package/src/components/ui/action-menu/camera-actions.tsx +2 -2
  90. package/src/components/ui/action-menu/control-modes.tsx +1 -1
  91. package/src/components/ui/action-menu/follow-pascal-pill.tsx +1 -1
  92. package/src/components/ui/action-menu/index.tsx +3 -3
  93. package/src/components/ui/action-menu/measurement-control.tsx +3 -3
  94. package/src/components/ui/action-menu/placement.ts +0 -18
  95. package/src/components/ui/action-menu/point-ask-button.tsx +1 -1
  96. package/src/components/ui/action-menu/view-toggles.tsx +5 -5
  97. package/src/components/ui/controls/finish-swatch.tsx +1 -1
  98. package/src/components/ui/controls/material-paint-panel.tsx +2 -2
  99. package/src/components/ui/controls/scene-material-list.tsx +2 -2
  100. package/src/components/ui/controls/select-control.tsx +1 -1
  101. package/src/components/ui/controls/slider-control.tsx +18 -6
  102. package/src/components/ui/controls/terrain-sculpt-panel.tsx +1 -1
  103. package/src/components/ui/controls/tool-options-panel.tsx +74 -44
  104. package/src/components/ui/floating-level-selector.tsx +3 -3
  105. package/src/components/ui/helpers/contextual-helper-panel.tsx +1 -1
  106. package/src/components/ui/helpers/helper-manager.test.tsx +54 -1
  107. package/src/components/ui/helpers/helper-manager.tsx +55 -1
  108. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  109. package/src/components/ui/panels/collections/collections-popover.tsx +2 -2
  110. package/src/components/ui/panels/floor-foundation-panel.tsx +3 -3
  111. package/src/components/ui/panels/floor-opening-panel.tsx +5 -5
  112. package/src/components/ui/panels/mobile-selection-bar.tsx +28 -24
  113. package/src/components/ui/panels/multi-height-mode.tsx +3 -3
  114. package/src/components/ui/panels/multi-selection-panel.tsx +1 -1
  115. package/src/components/ui/panels/node-display.ts +1 -1
  116. package/src/components/ui/panels/panel-manager.tsx +5 -3
  117. package/src/components/ui/panels/panel-wrapper.tsx +42 -19
  118. package/src/components/ui/panels/parametric-inspector.tsx +1 -1
  119. package/src/components/ui/panels/room-construction-rows.tsx +7 -7
  120. package/src/components/ui/panels/room-name-options.ts +1 -5
  121. package/src/components/ui/panels/room-openings-field.tsx +1 -1
  122. package/src/components/ui/panels/room-panel.tsx +7 -9
  123. package/src/components/ui/primitives/color-dot.tsx +2 -2
  124. package/src/components/ui/primitives/opacity-control.tsx +2 -2
  125. package/src/components/ui/primitives/sidebar.tsx +3 -3
  126. package/src/components/ui/right-stack.test.ts +13 -6
  127. package/src/components/ui/right-stack.tsx +22 -11
  128. package/src/components/ui/sidebar/icon-rail.tsx +1 -1
  129. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
  130. package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +2 -2
  131. package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +1 -1
  132. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +2 -2
  133. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +1 -1
  134. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +2 -6
  135. package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +1 -1
  136. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +1 -1
  137. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +1 -1
  138. package/src/components/ui/sidebar/panels/site-panel/index.tsx +3 -10
  139. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +1 -1
  140. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +1 -1
  141. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +46 -7
  142. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +1 -1
  143. package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +1 -1
  144. package/src/components/ui/sidebar/panels/site-panel/tree-node-drag.tsx +14 -5
  145. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -2
  146. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +1 -1
  147. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +3 -3
  148. package/src/components/ui/sidebar/tab-bar.tsx +1 -1
  149. package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +2 -2
  150. package/src/components/ui/wall-type/wall-type-menu.tsx +1 -1
  151. package/src/components/viewer-overlay.tsx +1 -2
  152. package/src/hooks/use-auto-frame.ts +1 -2
  153. package/src/hooks/use-keyboard.ts +1 -2
  154. package/src/hooks/use-selected-room.ts +5 -7
  155. package/src/i18n/floor-footprint-name.ts +1 -1
  156. package/src/i18n/locales/en/messages.po +1036 -306
  157. package/src/i18n/locales/en/messages.ts +1 -1
  158. package/src/i18n/locales/en-XA/messages.ts +1 -1
  159. package/src/i18n/locales/es/messages.po +2267 -439
  160. package/src/i18n/locales/fr/messages.po +2267 -439
  161. package/src/i18n/locales/ja/messages.po +2267 -439
  162. package/src/i18n/locales/zh-CN/messages.po +2267 -439
  163. package/src/i18n/locales.test.ts +1 -1
  164. package/src/index.tsx +183 -178
  165. package/src/lib/below-grade.test.tsx +4 -4
  166. package/src/lib/camera-facing.test.ts +1 -1
  167. package/src/lib/camera-facing.ts +2 -3
  168. package/src/lib/contextual-help.ts +6 -0
  169. package/src/lib/editor-level-display.ts +1 -1
  170. package/src/lib/elevation-guides.ts +21 -6
  171. package/src/lib/elevator-support.ts +1 -72
  172. package/src/lib/floor-footprints.test.ts +3 -3
  173. package/src/lib/floor-footprints.ts +1 -9
  174. package/src/lib/floor-foundation-support.test.ts +8 -3
  175. package/src/lib/floor-opening-draft.ts +2 -2
  176. package/src/lib/floor-region-session.ts +1 -1
  177. package/src/lib/floorplan/annotation-visibility.test.ts +1 -1
  178. package/src/lib/floorplan/annotation-visibility.ts +1 -1
  179. package/src/lib/floorplan/floorplan-export.test.ts +1 -1
  180. package/src/lib/floorplan/floorplan-export.tsx +8 -11
  181. package/src/lib/floorplan/floorplan-extension.ts +4 -157
  182. package/src/lib/floorplan/floorplan-mode.ts +2 -1
  183. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +1 -1
  184. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +8 -6
  185. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +3 -4
  186. package/src/lib/floorplan/geometry.ts +2 -112
  187. package/src/lib/floorplan/index.ts +0 -9
  188. package/src/lib/floorplan/items.ts +2 -1
  189. package/src/lib/floorplan/open-wall-ends.ts +2 -1
  190. package/src/lib/floorplan/schedules.ts +6 -3
  191. package/src/lib/floorplan/sheet-export.ts +7 -3
  192. package/src/lib/floorplan/site-plan/site-parts.ts +7 -1
  193. package/src/lib/floorplan/site-plan/site-plan-layer.tsx +1 -1
  194. package/src/lib/follow-pascal-presentation.ts +1 -1
  195. package/src/lib/gesture-lifecycle.ts +3 -1
  196. package/src/lib/glb-export.ts +1 -1
  197. package/src/lib/interaction/scope.ts +1 -0
  198. package/src/lib/layer-thickness.ts +1 -1
  199. package/src/lib/level-selection.test.ts +18 -2
  200. package/src/lib/level-selection.ts +31 -8
  201. package/src/lib/linear-display.ts +1 -1
  202. package/src/lib/live-gesture-round-trip.test.ts +1 -1
  203. package/src/lib/material-paint.ts +1 -1
  204. package/src/lib/measurement-label.test.ts +1 -1
  205. package/src/lib/measurements.test.ts +7 -5
  206. package/src/lib/mezzanine-selection.test.ts +1 -1
  207. package/src/lib/node-action-movement.test.tsx +115 -0
  208. package/src/lib/node-action-movement.ts +2 -0
  209. package/src/lib/paint-region-mode.ts +1 -1
  210. package/src/lib/placement-metadata.ts +4 -0
  211. package/src/lib/point-ask/describe-target.ts +6 -8
  212. package/src/lib/point-ask/region.ts +1 -1
  213. package/src/lib/point-ask/session.ts +1 -2
  214. package/src/lib/print-shell-compiler.ts +12 -1
  215. package/src/lib/print-wall-solids.test.ts +11 -0
  216. package/src/lib/print-wall-solids.ts +9 -0
  217. package/src/lib/roof-duplication.test.ts +100 -0
  218. package/src/lib/roof-duplication.ts +30 -5
  219. package/src/lib/room-built-on.test.ts +2 -5
  220. package/src/lib/room-built-on.ts +1 -15
  221. package/src/lib/room-construction-commands.ts +3 -2
  222. package/src/lib/room-controls-phase6.test.ts +1 -1
  223. package/src/lib/room-handle-drag.ts +8 -37
  224. package/src/lib/room-name-catalog.test.ts +2 -1
  225. package/src/lib/room-name-messages.test.ts +2 -1
  226. package/src/lib/room-name-messages.ts +1 -118
  227. package/src/lib/room-name-storage.test.ts +2 -1
  228. package/src/lib/room-push-dimensions.test.ts +1 -1
  229. package/src/lib/room-selection-commands.ts +2 -2
  230. package/src/lib/room-selection.test.ts +6 -6
  231. package/src/lib/room-transform-session.ts +1 -2
  232. package/src/lib/room-zone-routing.ts +2 -2
  233. package/src/lib/scene-clipboard.ts +2 -1
  234. package/src/lib/selection-routing.test.ts +0 -28
  235. package/src/lib/selection-routing.ts +1 -40
  236. package/src/lib/sfx-bus.ts +8 -32
  237. package/src/lib/sfx-player.test.ts +72 -1
  238. package/src/lib/sfx-player.ts +49 -337
  239. package/src/lib/snapping-mode.test.ts +23 -1
  240. package/src/lib/viewer-selection.test.ts +38 -10
  241. package/src/lib/wall-pull.test.ts +1 -1
  242. package/src/lib/wall-pull.ts +1 -27
  243. package/src/lib/wall-region-list.ts +3 -1
  244. package/src/lib/window-interaction.test.ts +4 -2
  245. package/src/store/use-editor.tsx +22 -26
  246. package/src/store/use-floorplan-annotation-visibility.ts +5 -5
  247. package/src/store/use-placement-typing.test.ts +165 -0
  248. package/src/store/use-placement-typing.ts +186 -0
  249. package/src/store/use-wall-region-handles-selection.ts +1 -2
  250. package/src/components/editor/__bench__/room-label-placement.bench.ts +0 -108
  251. package/src/components/editor/first-person/build-collider-world.test.ts +0 -596
  252. package/src/components/editor/first-person/build-collider-world.ts +0 -549
  253. package/src/components/editor/first-person/ground-holes.test.ts +0 -128
  254. package/src/components/editor/first-person/ground-holes.ts +0 -130
  255. package/src/components/editor/first-person/mechanism-targets.test.ts +0 -162
  256. package/src/components/editor/first-person/mechanism-targets.ts +0 -88
  257. package/src/components/editor/first-person/terrain-collider.test.ts +0 -132
  258. package/src/components/editor/first-person/terrain-collider.ts +0 -212
  259. package/src/components/editor/floorplan-camera-sync.test.ts +0 -249
  260. package/src/components/editor/floorplan-camera-sync.ts +0 -213
  261. package/src/components/editor/localized-html.tsx +0 -22
  262. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.test.tsx +0 -236
  263. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +0 -708
  264. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +0 -385
  265. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +0 -670
  266. package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +0 -89
  267. package/src/components/editor-2d/renderers/floorplan-label-angle.ts +0 -106
  268. package/src/components/editor-2d/svg-paths.ts +0 -124
  269. package/src/components/tools/elevator/elevator-defaults.ts +0 -8
  270. package/src/components/tools/elevator/elevator-tool.tsx +0 -372
  271. package/src/components/tools/elevator/move-elevator-tool.tsx +0 -262
  272. package/src/components/ui/action-menu/action-button.tsx +0 -89
  273. package/src/components/ui/action-menu/follow-pascal-toggle.tsx +0 -42
  274. package/src/components/ui/primitives/button.tsx +0 -69
  275. package/src/components/ui/primitives/dropdown-menu.tsx +0 -232
  276. package/src/components/ui/primitives/popover.tsx +0 -42
  277. package/src/components/ui/primitives/switch.tsx +0 -29
  278. package/src/components/ui/primitives/tooltip.tsx +0 -57
  279. package/src/components/ui/scene-loader.tsx +0 -73
  280. package/src/components/viewer/floorplan-compass-button.tsx +0 -67
  281. package/src/components/viewer/floorplan-preview-geometry.test.ts +0 -107
  282. package/src/components/viewer/floorplan-preview-geometry.ts +0 -281
  283. package/src/components/viewer/floorplan-preview-navigation.test.ts +0 -43
  284. package/src/components/viewer/floorplan-preview-navigation.ts +0 -62
  285. package/src/components/viewer/floorplan-preview-visibility.test.ts +0 -35
  286. package/src/components/viewer/floorplan-preview-visibility.ts +0 -19
  287. package/src/components/viewer/floorplan-preview.tsx +0 -1065
  288. package/src/components/viewer/render-settings-menu.tsx +0 -294
  289. package/src/components/viewer/sky-control.tsx +0 -710
  290. package/src/components/viewer/use-viewer-camera-navigation-sync.ts +0 -140
  291. package/src/components/viewer/use-viewer-ui-sounds.test.ts +0 -15
  292. package/src/components/viewer/use-viewer-ui-sounds.ts +0 -46
  293. package/src/components/viewer/viewer-controls-bar.tsx +0 -496
  294. package/src/components/viewer/viewer-hover-label.tsx +0 -42
  295. package/src/components/viewer/viewer-rooms.tsx +0 -248
  296. package/src/components/viewer/viewer-scene-header.tsx +0 -286
  297. package/src/components/viewer/viewer-selection-manager.tsx +0 -124
  298. package/src/components/viewer/viewer-stage-modes.test.ts +0 -38
  299. package/src/components/viewer/viewer-stage-modes.ts +0 -49
  300. package/src/components/viewer/viewer-stage-switcher.tsx +0 -97
  301. package/src/components/viewer/viewer-stage.test.tsx +0 -47
  302. package/src/components/viewer/viewer-stage.tsx +0 -221
  303. package/src/components/walkthrough-hud.tsx +0 -193
  304. package/src/hooks/use-mobile.ts +0 -19
  305. package/src/i18n/level-name.ts +0 -39
  306. package/src/i18n/scene-theme-name.ts +0 -16
  307. package/src/lib/below-grade.ts +0 -133
  308. package/src/lib/camera-pose.test.ts +0 -237
  309. package/src/lib/camera-pose.ts +0 -172
  310. package/src/lib/door-interaction.ts +0 -102
  311. package/src/lib/floorplan/floorplan-extension.test.ts +0 -42
  312. package/src/lib/floorplan/floorplan-readonly.test.ts +0 -47
  313. package/src/lib/floorplan/floorplan-readonly.ts +0 -95
  314. package/src/lib/floorplan/marks.test.ts +0 -213
  315. package/src/lib/floorplan/marks.ts +0 -238
  316. package/src/lib/measurement-label.ts +0 -68
  317. package/src/lib/measurements.ts +0 -225
  318. package/src/lib/node-display-label.ts +0 -37
  319. package/src/lib/room-assembly-overlay.test.ts +0 -275
  320. package/src/lib/room-assembly-overlay.ts +0 -265
  321. package/src/lib/room-selection.ts +0 -417
  322. package/src/lib/scene-bounds.test.ts +0 -183
  323. package/src/lib/scene-bounds.ts +0 -169
  324. package/src/lib/typing-target.ts +0 -9
  325. package/src/lib/viewer-selection.ts +0 -595
  326. package/src/lib/window-interaction.ts +0 -142
  327. package/src/lib/zone-content.ts +0 -133
  328. package/src/store/camera-pose-store.test.ts +0 -29
  329. package/src/store/camera-pose-store.ts +0 -27
  330. package/src/store/navigation-sync-pose-store.test.ts +0 -34
  331. package/src/store/navigation-sync-pose-store.ts +0 -27
  332. package/src/store/use-audio.test.ts +0 -146
  333. package/src/store/use-audio.tsx +0 -169
  334. package/src/store/use-first-person-hud.ts +0 -40
  335. package/src/store/use-follow-pascal.test.ts +0 -62
  336. package/src/store/use-follow-pascal.ts +0 -53
@@ -1,142 +0,0 @@
1
- import {
2
- type AnyNodeId,
3
- type InteractiveState,
4
- useInteractive,
5
- useScene,
6
- type WindowInteractiveState,
7
- } from '@pascal-app/core'
8
-
9
- export const WINDOW_TOGGLE_ANIMATION_MS = 520
10
-
11
- export type WindowOpenAnimationOptions = {
12
- persist?: boolean
13
- }
14
-
15
- export function isOperableWindowType(windowType: string | undefined) {
16
- return (
17
- windowType === 'sliding' ||
18
- windowType === 'casement' ||
19
- windowType === 'awning' ||
20
- windowType === 'hopper' ||
21
- windowType === 'tilt-turn' ||
22
- windowType === 'single-hung' ||
23
- windowType === 'double-hung' ||
24
- windowType === 'louvered'
25
- )
26
- }
27
-
28
- export function getDisplayedWindowValue(
29
- windowId: AnyNodeId,
30
- nodeValue: number | undefined,
31
- field: keyof WindowInteractiveState = 'operationState',
32
- interactive: InteractiveState = useInteractive.getState(),
33
- ) {
34
- const runtimeValue = interactive.windows[windowId]?.[field]
35
- if (runtimeValue !== undefined) return runtimeValue
36
-
37
- const queuedValue = interactive.windowAnimations[windowId]?.[field]?.from
38
- if (queuedValue !== undefined) return queuedValue
39
-
40
- return nodeValue ?? 0
41
- }
42
-
43
- function startWindowOpenAnimation(
44
- windowId: AnyNodeId,
45
- field: keyof WindowInteractiveState,
46
- from: number,
47
- to: number,
48
- options?: WindowOpenAnimationOptions,
49
- ) {
50
- useInteractive.getState().startWindowAnimation(windowId, {
51
- field,
52
- from,
53
- to,
54
- startedAt: null,
55
- durationMs: WINDOW_TOGGLE_ANIMATION_MS,
56
- persist: options?.persist ?? true,
57
- })
58
- }
59
-
60
- export function toggleWindowOpenState(windowId: AnyNodeId, options?: WindowOpenAnimationOptions) {
61
- const node = useScene.getState().nodes[windowId]
62
- if (
63
- node?.type !== 'window' ||
64
- node.openingKind === 'opening' ||
65
- !isOperableWindowType(node.windowType)
66
- ) {
67
- return
68
- }
69
-
70
- const currentOpenAmount = getDisplayedWindowValue(windowId, node.operationState)
71
- startWindowOpenAnimation(
72
- windowId,
73
- 'operationState',
74
- currentOpenAmount,
75
- currentOpenAmount >= 0.5 ? 0 : 1,
76
- options,
77
- )
78
- }
79
-
80
- export function closeWindowOpenState(windowId: AnyNodeId, options?: WindowOpenAnimationOptions) {
81
- const node = useScene.getState().nodes[windowId]
82
- if (
83
- node?.type !== 'window' ||
84
- node.openingKind === 'opening' ||
85
- !isOperableWindowType(node.windowType)
86
- ) {
87
- return
88
- }
89
-
90
- const currentOpenAmount = getDisplayedWindowValue(windowId, node.operationState)
91
- startWindowOpenAnimation(windowId, 'operationState', currentOpenAmount, 0, options)
92
- }
93
-
94
- export function openWindowOpenState(windowId: AnyNodeId, options?: WindowOpenAnimationOptions) {
95
- const node = useScene.getState().nodes[windowId]
96
- if (
97
- node?.type !== 'window' ||
98
- node.openingKind === 'opening' ||
99
- !isOperableWindowType(node.windowType)
100
- ) {
101
- return
102
- }
103
-
104
- const currentOpenAmount = getDisplayedWindowValue(windowId, node.operationState)
105
- startWindowOpenAnimation(windowId, 'operationState', currentOpenAmount, 1, options)
106
- }
107
-
108
- export type WindowShutterField = 'shutterOpen' | 'rollerShutterOpen'
109
-
110
- export function getDisplayedWindowShutterValue(
111
- windowId: AnyNodeId,
112
- field: WindowShutterField,
113
- nodeValue: number | undefined,
114
- state: InteractiveState = useInteractive.getState(),
115
- ): number {
116
- return getDisplayedWindowValue(windowId, nodeValue ?? 1, field, state)
117
- }
118
-
119
- /** Preview both shutter styles together, independently of the sash. */
120
- export function setWindowShuttersOpenState(
121
- windowId: AnyNodeId,
122
- on: boolean,
123
- options?: WindowOpenAnimationOptions,
124
- ): void {
125
- const node = useScene.getState().nodes[windowId]
126
- if (node?.type !== 'window' || node.source) return
127
- const fields: WindowShutterField[] = []
128
- if (node.shutters && node.shutters !== 'none') fields.push('shutterOpen')
129
- if (node.rollerShutter === 'box' || node.rollerShutter === 'concealed')
130
- fields.push('rollerShutterOpen')
131
- if (!fields.length) return
132
- useInteractive.getState().setMechanismPart(windowId, 'shutters', on)
133
- for (const field of fields) {
134
- startWindowOpenAnimation(
135
- windowId,
136
- field,
137
- getDisplayedWindowShutterValue(windowId, field, node[field]),
138
- on ? 1 : 0,
139
- options,
140
- )
141
- }
142
- }
@@ -1,133 +0,0 @@
1
- import {
2
- type AnyNode,
3
- type AnyNodeId,
4
- type CeilingNode,
5
- type ItemNode,
6
- pointInPolygon2D,
7
- pointOnSegment,
8
- type SlabNode,
9
- type WallNode,
10
- type ZoneNode,
11
- } from '@pascal-app/core'
12
-
13
- type Point2D = [number, number]
14
-
15
- const POINT_TOLERANCE = 0.5
16
- const COLLINEAR_TOLERANCE = 1e-6
17
- const SURFACE_POLYGON_TOLERANCE = 0.15
18
-
19
- function getPointToSegmentDistance(point: Point2D, start: Point2D, end: Point2D): number {
20
- const dx = end[0] - start[0]
21
- const dz = end[1] - start[1]
22
- const lengthSq = dx * dx + dz * dz
23
- if (lengthSq === 0) return Math.hypot(point[0] - start[0], point[1] - start[1])
24
-
25
- const rawT = ((point[0] - start[0]) * dx + (point[1] - start[1]) * dz) / lengthSq
26
- const t = Math.max(0, Math.min(1, rawT))
27
- const projected: Point2D = [start[0] + t * dx, start[1] + t * dz]
28
- return Math.hypot(point[0] - projected[0], point[1] - projected[1])
29
- }
30
-
31
- function pointInPolygonWithTolerance(point: Point2D, polygon: Point2D[]): boolean {
32
- if (pointInPolygon2D(point, polygon, { includeBoundary: true })) return true
33
- return polygon.some((start, index) => {
34
- const end = polygon[(index + 1) % polygon.length]
35
- return end ? getPointToSegmentDistance(point, start, end) <= POINT_TOLERANCE : false
36
- })
37
- }
38
-
39
- function polygonContainsWithTolerance(
40
- outer: Point2D[],
41
- inner: Point2D[],
42
- tolerance: number,
43
- ): boolean {
44
- return inner.every((point) => {
45
- if (pointInPolygon2D(point, outer, { includeBoundary: true })) return true
46
- return outer.some((start, index) => {
47
- const end = outer[(index + 1) % outer.length]
48
- return end ? getPointToSegmentDistance(point, start, end) <= tolerance : false
49
- })
50
- })
51
- }
52
-
53
- export function polygonMatchesZoneFootprint(
54
- surfacePolygon: Point2D[],
55
- footprint: Point2D[],
56
- ): boolean {
57
- if (surfacePolygon.length < 3) return false
58
- return (
59
- polygonContainsWithTolerance(footprint, surfacePolygon, SURFACE_POLYGON_TOLERANCE) &&
60
- polygonContainsWithTolerance(surfacePolygon, footprint, SURFACE_POLYGON_TOLERANCE)
61
- )
62
- }
63
-
64
- function areSegmentsCollinear(a: Point2D, b: Point2D, c: Point2D, d: Point2D): boolean {
65
- const abx = b[0] - a[0]
66
- const abz = b[1] - a[1]
67
- const acx = c[0] - a[0]
68
- const acz = c[1] - a[1]
69
- const adx = d[0] - a[0]
70
- const adz = d[1] - a[1]
71
- const crossC = abx * acz - abz * acx
72
- const crossD = abx * adz - abz * adx
73
- return Math.abs(crossC) <= COLLINEAR_TOLERANCE && Math.abs(crossD) <= COLLINEAR_TOLERANCE
74
- }
75
-
76
- function segmentsOverlap(a: Point2D, b: Point2D, c: Point2D, d: Point2D): boolean {
77
- const useX = Math.abs(b[0] - a[0]) >= Math.abs(b[1] - a[1])
78
- const a0 = useX ? a[0] : a[1]
79
- const a1 = useX ? b[0] : b[1]
80
- const c0 = useX ? c[0] : c[1]
81
- const c1 = useX ? d[0] : d[1]
82
- const minA = Math.min(a0, a1)
83
- const maxA = Math.max(a0, a1)
84
- const minC = Math.min(c0, c1)
85
- const maxC = Math.max(c0, c1)
86
- return Math.max(minA, minC) <= Math.min(maxA, maxC) + COLLINEAR_TOLERANCE
87
- }
88
-
89
- function wallLiesOnZoneBoundary(wall: WallNode, polygon: Point2D[]): boolean {
90
- return polygon.some((start, index) => {
91
- const end = polygon[(index + 1) % polygon.length]
92
- if (!end) return false
93
- return (
94
- areSegmentsCollinear(wall.start, wall.end, start, end) &&
95
- segmentsOverlap(wall.start, wall.end, start, end) &&
96
- pointOnSegment(wall.start, start, end, POINT_TOLERANCE) &&
97
- pointOnSegment(wall.end, start, end, POINT_TOLERANCE)
98
- )
99
- })
100
- }
101
-
102
- export function collectZoneContentIds(
103
- nodes: Readonly<Record<AnyNodeId, AnyNode>>,
104
- zone: ZoneNode,
105
- ): AnyNodeId[] {
106
- const levelId = zone.parentId
107
- if (!levelId) return []
108
-
109
- const footprint = zone.polygon.map((point) => [point[0], point[1]] as Point2D)
110
- const boundaryWalls = Object.values(nodes)
111
- .filter((node): node is WallNode => node.type === 'wall' && node.parentId === levelId)
112
- .filter((wall) => wallLiesOnZoneBoundary(wall, footprint))
113
- const surfaces = Object.values(nodes)
114
- .filter(
115
- (node): node is SlabNode | CeilingNode =>
116
- (node.type === 'slab' || node.type === 'ceiling') && node.parentId === levelId,
117
- )
118
- .filter((surface) => {
119
- const polygon = surface.polygon.map((point) => [point[0], point[1]] as Point2D)
120
- return polygonMatchesZoneFootprint(polygon, footprint)
121
- })
122
- const floorItems = Object.values(nodes)
123
- .filter((node): node is ItemNode => node.type === 'item' && node.parentId === levelId)
124
- .filter((item) => pointInPolygonWithTolerance([item.position[0], item.position[2]], footprint))
125
-
126
- return Array.from(
127
- new Set<AnyNodeId>([
128
- ...boundaryWalls.map((wall) => wall.id as AnyNodeId),
129
- ...surfaces.map((surface) => surface.id as AnyNodeId),
130
- ...floorItems.map((item) => item.id as AnyNodeId),
131
- ]),
132
- )
133
- }
@@ -1,29 +0,0 @@
1
- import { afterEach, describe, expect, test } from 'bun:test'
2
- import type { CameraPose } from '@pascal-app/core'
3
- import { cameraPoseStore, publishCameraPose, subscribeCameraPose } from './camera-pose-store'
4
-
5
- const pose: CameraPose = {
6
- position: [3, 4, 5],
7
- projection: 'perspective',
8
- target: [0, 1, 0],
9
- viewWidth: 12,
10
- }
11
-
12
- afterEach(() => {
13
- cameraPoseStore.setState({ pose: null })
14
- })
15
-
16
- describe('camera pose store', () => {
17
- test('replays the latest pose and streams later poses to focused subscribers', () => {
18
- publishCameraPose(pose)
19
- const received: CameraPose[] = []
20
- const unsubscribe = subscribeCameraPose((nextPose) => received.push(nextPose))
21
- const nextPose = { ...pose, viewWidth: 8 }
22
-
23
- publishCameraPose(nextPose)
24
- unsubscribe()
25
- publishCameraPose({ ...pose, viewWidth: 4 })
26
-
27
- expect(received).toEqual([pose, nextPose])
28
- })
29
- })
@@ -1,27 +0,0 @@
1
- import type { CameraPose } from '@pascal-app/core'
2
- import { subscribeWithSelector } from 'zustand/middleware'
3
- import { createStore } from 'zustand/vanilla'
4
-
5
- type CameraPoseState = {
6
- pose: CameraPose | null
7
- }
8
-
9
- export const cameraPoseStore = createStore<CameraPoseState>()(
10
- subscribeWithSelector<CameraPoseState>(() => ({ pose: null })),
11
- )
12
-
13
- export function publishCameraPose(pose: CameraPose) {
14
- cameraPoseStore.setState({ pose })
15
- }
16
-
17
- export function subscribeCameraPose(listener: (pose: CameraPose) => void) {
18
- const currentPose = cameraPoseStore.getState().pose
19
- if (currentPose) listener(currentPose)
20
-
21
- return cameraPoseStore.subscribe(
22
- (state) => state.pose,
23
- (pose) => {
24
- if (pose) listener(pose)
25
- },
26
- )
27
- }
@@ -1,34 +0,0 @@
1
- import { afterEach, describe, expect, test } from 'bun:test'
2
- import {
3
- navigationSyncPoseStore,
4
- publishNavigationSyncPoseToStore,
5
- subscribeNavigationSyncPose,
6
- } from './navigation-sync-pose-store'
7
- import type { NavigationSyncPose } from './use-editor'
8
-
9
- const pose = {
10
- source: '2d' as const,
11
- revision: 1,
12
- target: [1, 2, 3] as [number, number, number],
13
- azimuth: 0.5,
14
- viewWidth: 12,
15
- }
16
-
17
- afterEach(() => {
18
- navigationSyncPoseStore.setState({ pose: null })
19
- })
20
-
21
- describe('navigation sync pose store', () => {
22
- test('replays the latest pose and streams only later pose writes', () => {
23
- publishNavigationSyncPoseToStore(pose)
24
- const received: NavigationSyncPose[] = []
25
- const unsubscribe = subscribeNavigationSyncPose((nextPose) => received.push(nextPose))
26
- const nextPose = { ...pose, revision: 2, viewWidth: 8 }
27
-
28
- publishNavigationSyncPoseToStore(nextPose)
29
- unsubscribe()
30
- publishNavigationSyncPoseToStore({ ...pose, revision: 3 })
31
-
32
- expect(received).toEqual([pose, nextPose])
33
- })
34
- })
@@ -1,27 +0,0 @@
1
- import { subscribeWithSelector } from 'zustand/middleware'
2
- import { createStore } from 'zustand/vanilla'
3
- import type { NavigationSyncPose } from './use-editor'
4
-
5
- type NavigationSyncPoseState = {
6
- pose: NavigationSyncPose | null
7
- }
8
-
9
- export const navigationSyncPoseStore = createStore<NavigationSyncPoseState>()(
10
- subscribeWithSelector<NavigationSyncPoseState>(() => ({ pose: null })),
11
- )
12
-
13
- export function publishNavigationSyncPoseToStore(pose: NavigationSyncPose) {
14
- navigationSyncPoseStore.setState({ pose })
15
- }
16
-
17
- export function subscribeNavigationSyncPose(listener: (pose: NavigationSyncPose) => void) {
18
- const currentPose = navigationSyncPoseStore.getState().pose
19
- if (currentPose) listener(currentPose)
20
-
21
- return navigationSyncPoseStore.subscribe(
22
- (state) => state.pose,
23
- (pose) => {
24
- if (pose) listener(pose)
25
- },
26
- )
27
- }
@@ -1,146 +0,0 @@
1
- import { afterAll, beforeEach, describe, expect, test } from 'bun:test'
2
- import useAudio, { AUDIO_MUTED_STORAGE_KEY, AUDIO_SETTINGS_STORAGE_KEY } from './use-audio'
3
-
4
- function memoryStorage() {
5
- const items = new Map<string, string>()
6
- return {
7
- items,
8
- getItem: (key: string) => items.get(key) ?? null,
9
- setItem: (key: string, value: string) => void items.set(key, value),
10
- removeItem: (key: string) => void items.delete(key),
11
- }
12
- }
13
-
14
- const originals = {
15
- localStorage: Object.getOwnPropertyDescriptor(globalThis, 'localStorage'),
16
- sessionStorage: Object.getOwnPropertyDescriptor(globalThis, 'sessionStorage'),
17
- }
18
-
19
- function use(name: 'localStorage' | 'sessionStorage', storage: ReturnType<typeof memoryStorage>) {
20
- Object.defineProperty(globalThis, name, { configurable: true, value: storage })
21
- }
22
-
23
- /** One browser profile: its tabs share `localStorage`, each has its own `sessionStorage`. */
24
- let browser = memoryStorage()
25
-
26
- /** Another tab of the profile saves the settings: the browser stores them and tells this tab. */
27
- function savedInAnotherTab(state: Record<string, unknown>) {
28
- const value = JSON.stringify({ state, version: 0 })
29
- browser.setItem(AUDIO_SETTINGS_STORAGE_KEY, value)
30
- globalThis.dispatchEvent(
31
- Object.assign(new Event('storage'), { key: AUDIO_SETTINGS_STORAGE_KEY, newValue: value }),
32
- )
33
- }
34
-
35
- /** The editor loading in a tab: a new one, or the same one reloaded (its session kept). */
36
- async function loadIn(tab: ReturnType<typeof memoryStorage>) {
37
- use('sessionStorage', tab)
38
- await useAudio.persist.rehydrate()
39
- return useAudio.getState()
40
- }
41
-
42
- beforeEach(() => {
43
- browser = memoryStorage()
44
- use('localStorage', browser)
45
- })
46
-
47
- afterAll(() => {
48
- for (const [name, descriptor] of Object.entries(originals)) {
49
- if (descriptor) Object.defineProperty(globalThis, name, descriptor)
50
- else Reflect.deleteProperty(globalThis, name)
51
- }
52
- })
53
-
54
- describe('muting a tab', () => {
55
- test('a tab keeps its own mute across reloads, and a new tab starts from the last choice', async () => {
56
- const first = memoryStorage()
57
- const second = memoryStorage()
58
-
59
- expect((await loadIn(first)).muted).toBe(false)
60
- useAudio.getState().toggleMute()
61
-
62
- expect((await loadIn(second)).muted).toBe(true)
63
- useAudio.getState().setMuted(false)
64
-
65
- expect((await loadIn(first)).muted).toBe(true)
66
- expect((await loadIn(memoryStorage())).muted).toBe(false)
67
- })
68
-
69
- test("another tab's choice does not change a tab that never chose", async () => {
70
- const quiet = memoryStorage()
71
- expect((await loadIn(quiet)).muted).toBe(false)
72
-
73
- await loadIn(memoryStorage())
74
- useAudio.getState().setMuted(true)
75
-
76
- expect((await loadIn(quiet)).muted).toBe(false)
77
- })
78
-
79
- test("volumes are every tab's and never carry a mute", async () => {
80
- const muting = memoryStorage()
81
- await loadIn(muting)
82
- useAudio.getState().setMuted(true)
83
-
84
- await loadIn(memoryStorage())
85
- useAudio.getState().setMuted(false)
86
- await loadIn(muting)
87
- useAudio.getState().setSfxVolume(20)
88
-
89
- expect(JSON.parse(browser.getItem(AUDIO_SETTINGS_STORAGE_KEY)!).state).not.toHaveProperty(
90
- 'muted',
91
- )
92
- expect(browser.getItem(AUDIO_MUTED_STORAGE_KEY)).toBe('false')
93
- const next = await loadIn(memoryStorage())
94
- expect(next.sfxVolume).toBe(20)
95
- expect(next.muted).toBe(false)
96
- })
97
-
98
- test("a volume another tab sets reaches this one at once, and this tab's saves carry it", async () => {
99
- await loadIn(memoryStorage())
100
- useAudio.getState().setSfxVolume(30)
101
- useAudio.getState().setRadioPlaying(true)
102
- const stored = JSON.parse(browser.getItem(AUDIO_SETTINGS_STORAGE_KEY)!)
103
-
104
- savedInAnotherTab({ ...stored.state, isRadioPlaying: false, muted: true, sfxVolume: 80 })
105
-
106
- expect(useAudio.getState()).toMatchObject({ isRadioPlaying: true, muted: false, sfxVolume: 80 })
107
- const setElsewhere = browser.getItem(AUDIO_SETTINGS_STORAGE_KEY)
108
- useAudio.getState().setMuted(true)
109
- expect(browser.getItem(AUDIO_SETTINGS_STORAGE_KEY)).toBe(setElsewhere)
110
- // The radio pausing as the tab is hidden saves every setting: the other tab's volume stays.
111
- useAudio.getState().setRadioPlaying(false)
112
- useAudio.getState().setMasterVolume(60)
113
- expect(JSON.parse(browser.getItem(AUDIO_SETTINGS_STORAGE_KEY)!).state).toMatchObject({
114
- masterVolume: 60,
115
- sfxVolume: 80,
116
- })
117
- })
118
-
119
- test("a mute saved when it was every tab's still holds, in settings the old build still reads", async () => {
120
- browser.setItem(
121
- AUDIO_SETTINGS_STORAGE_KEY,
122
- JSON.stringify({ state: { masterVolume: 40, muted: true, sfxVolume: 50 }, version: 0 }),
123
- )
124
-
125
- const state = await loadIn(memoryStorage())
126
-
127
- expect(state.muted).toBe(true)
128
- expect(state.masterVolume).toBe(40)
129
- expect((await loadIn(memoryStorage())).muted).toBe(true)
130
- useAudio.getState().setSfxVolume(45)
131
- const saved = JSON.parse(browser.getItem(AUDIO_SETTINGS_STORAGE_KEY)!)
132
- expect(saved).toMatchObject({ state: { masterVolume: 40, sfxVolume: 45 }, version: 0 })
133
- expect(saved.state).not.toHaveProperty('muted')
134
- })
135
-
136
- test('a tab still on the old settings does not undo a choice made since', async () => {
137
- await loadIn(memoryStorage())
138
- useAudio.getState().setMuted(false)
139
- browser.setItem(
140
- AUDIO_SETTINGS_STORAGE_KEY,
141
- JSON.stringify({ state: { muted: true, sfxVolume: 50 }, version: 0 }),
142
- )
143
-
144
- expect((await loadIn(memoryStorage())).muted).toBe(false)
145
- })
146
- })