@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,496 +0,0 @@
1
- 'use client'
2
-
3
- import { emitter, type PascalI18n, usePascalI18n } from '@pascal-app/core'
4
- import { CLAY_PALETTE, getSceneTheme, SCENE_THEMES, useViewer } from '@pascal-app/viewer'
5
- import {
6
- Camera,
7
- Check,
8
- Contrast,
9
- Diamond,
10
- Drone,
11
- Eye,
12
- EyeOff,
13
- Footprints,
14
- Layers,
15
- Layers2,
16
- Palette,
17
- SlidersHorizontal,
18
- Square,
19
- SwatchBook,
20
- } from 'lucide-react'
21
- import { useIsMobile } from '../../hooks/use-mobile'
22
- import { sceneThemeName } from '../../i18n/scene-theme-name'
23
- import { cn } from '../../lib/utils'
24
- import { ActionButton } from '../ui/action-menu/action-button'
25
- import { FollowPascalToggle } from '../ui/action-menu/follow-pascal-toggle'
26
- import {
27
- DropdownMenu,
28
- DropdownMenuContent,
29
- DropdownMenuItem,
30
- DropdownMenuSeparator,
31
- DropdownMenuSub,
32
- DropdownMenuSubContent,
33
- DropdownMenuSubTrigger,
34
- DropdownMenuTrigger,
35
- } from '../ui/primitives/dropdown-menu'
36
- import { TooltipProvider } from '../ui/primitives/tooltip'
37
- import { DaylightMenu, EdgesToggleItem, QualityMenu } from './render-settings-menu'
38
- import { SKY_DOCK_HEIGHT, SkyControl } from './sky-control'
39
-
40
- const wallModeConfig = {
41
- up: {
42
- icon: '/icons/room.webp',
43
- },
44
- cutaway: {
45
- icon: '/icons/wallcut.webp',
46
- },
47
- down: {
48
- icon: '/icons/walllow.webp',
49
- },
50
- } as const
51
-
52
- function levelModeLabel(
53
- mode: 'stacked' | 'exploded' | 'solo' | 'manual',
54
- i18n: PascalI18n,
55
- ): string {
56
- if (mode === 'stacked') return i18n._({ id: 'viewer.levels.stacked', message: 'Stacked' })
57
- if (mode === 'exploded') return i18n._({ id: 'viewer.levels.exploded', message: 'Exploded' })
58
- if (mode === 'solo') return i18n._({ id: 'viewer.levels.solo', message: 'Solo' })
59
- return i18n._({ id: 'viewer.levels.manual', message: 'Manual' })
60
- }
61
-
62
- function wallModeLabel(mode: keyof typeof wallModeConfig, i18n: PascalI18n): string {
63
- if (mode === 'up') return i18n._({ id: 'viewer.walls.fullHeight', message: 'Full height' })
64
- if (mode === 'down') return i18n._({ id: 'viewer.walls.low', message: 'Low' })
65
- return i18n._({ id: 'viewer.walls.cutaway', message: 'Cutaway' })
66
- }
67
-
68
- // Keep the dropdown open when flipping an in-place toggle row.
69
- const keepOpen = (event: Event, fn: () => void) => {
70
- event.preventDefault()
71
- fn()
72
- }
73
-
74
- // Scans + guides folded into one control. A baked GLB carries none of its own,
75
- // but the GLB viewer re-adds them from scene data when the privacy flags allow,
76
- // so the toggle shows for whichever exist.
77
- function VisibilityMenu({
78
- canShowScans,
79
- canShowGuides,
80
- }: {
81
- canShowScans: boolean
82
- canShowGuides: boolean
83
- }) {
84
- const i18n = usePascalI18n()
85
- const showScans = useViewer((s) => s.showScans)
86
- const showGuides = useViewer((s) => s.showGuides)
87
- return (
88
- <DropdownMenu>
89
- <DropdownMenuTrigger asChild>
90
- <ActionButton
91
- className="hover:bg-white/5 hover:text-foreground"
92
- label={i18n._({ id: 'viewer.visibility.title', message: 'Visibility' })}
93
- size="icon"
94
- tooltipClassName="dark"
95
- tooltipSide="top"
96
- variant="ghost"
97
- >
98
- <Eye className="h-5 w-5" />
99
- </ActionButton>
100
- </DropdownMenuTrigger>
101
- <DropdownMenuContent align="center" className="dark min-w-44" side="top">
102
- {canShowScans && (
103
- <DropdownMenuItem
104
- onSelect={(e) => keepOpen(e, () => useViewer.getState().setShowScans(!showScans))}
105
- >
106
- <img alt="" className="h-4 w-4 object-contain" src="/icons/mesh.webp" />
107
- <span>{i18n._({ id: 'viewer.visibility.scans', message: 'Scans' })}</span>
108
- {showScans ? (
109
- <Eye className="ml-auto h-4 w-4 text-foreground" />
110
- ) : (
111
- <EyeOff className="ml-auto h-4 w-4 text-muted-foreground" />
112
- )}
113
- </DropdownMenuItem>
114
- )}
115
- {canShowGuides && (
116
- <DropdownMenuItem
117
- onSelect={(e) => keepOpen(e, () => useViewer.getState().setShowGuides(!showGuides))}
118
- >
119
- <img alt="" className="h-4 w-4 object-contain" src="/icons/floorplan.webp" />
120
- <span>{i18n._({ id: 'viewer.visibility.guides', message: 'Guides' })}</span>
121
- {showGuides ? (
122
- <Eye className="ml-auto h-4 w-4 text-foreground" />
123
- ) : (
124
- <EyeOff className="ml-auto h-4 w-4 text-muted-foreground" />
125
- )}
126
- </DropdownMenuItem>
127
- )}
128
- </DropdownMenuContent>
129
- </DropdownMenu>
130
- )
131
- }
132
-
133
- // One "Display" button gathering shadows, camera projection, colors, render
134
- // mode, scene theme and edges.
135
- function DisplayMenu() {
136
- const i18n = usePascalI18n()
137
- const cameraMode = useViewer((s) => s.cameraMode)
138
- const textures = useViewer((s) => s.textures)
139
- const shadows = useViewer((s) => s.shadows)
140
- const sceneTheme = useViewer((s) => s.sceneTheme)
141
- const activeTheme = getSceneTheme(sceneTheme)
142
- return (
143
- <DropdownMenu>
144
- <DropdownMenuTrigger asChild>
145
- <ActionButton
146
- className="hover:bg-white/5 hover:text-foreground"
147
- label={i18n._({ id: 'viewer.display.title', message: 'Display settings' })}
148
- size="icon"
149
- tooltipClassName="dark"
150
- tooltipSide="top"
151
- variant="ghost"
152
- >
153
- <SlidersHorizontal className="h-5 w-5" />
154
- </ActionButton>
155
- </DropdownMenuTrigger>
156
- <DropdownMenuContent align="center" className="dark min-w-56" side="top">
157
- <DropdownMenuItem
158
- onSelect={(e) => keepOpen(e, () => useViewer.getState().setShadows(!shadows))}
159
- >
160
- <Contrast className="h-4 w-4" />
161
- <span>{i18n._({ id: 'viewer.display.shadows', message: 'Shadows' })}</span>
162
- <span className="ml-auto text-muted-foreground text-xs">
163
- {shadows
164
- ? i18n._({ id: 'viewer.common.on', message: 'On' })
165
- : i18n._({ id: 'viewer.common.off', message: 'Off' })}
166
- </span>
167
- </DropdownMenuItem>
168
- <DropdownMenuItem
169
- onSelect={(e) =>
170
- keepOpen(e, () =>
171
- useViewer
172
- .getState()
173
- .setCameraMode(cameraMode === 'perspective' ? 'orthographic' : 'perspective'),
174
- )
175
- }
176
- >
177
- <Camera className="h-4 w-4" />
178
- <span>{i18n._({ id: 'viewer.display.camera', message: 'Camera' })}</span>
179
- <span className="ml-auto text-muted-foreground text-xs">
180
- {cameraMode === 'perspective'
181
- ? i18n._({ id: 'viewer.display.perspective', message: 'Perspective' })
182
- : i18n._({ id: 'viewer.display.orthographic', message: 'Orthographic' })}
183
- </span>
184
- </DropdownMenuItem>
185
- <DropdownMenuItem
186
- onSelect={(e) => keepOpen(e, () => useViewer.getState().setTextures(!textures))}
187
- >
188
- {textures ? <Palette className="h-4 w-4" /> : <Square className="h-4 w-4" />}
189
- <span>{i18n._({ id: 'viewer.display.colors', message: 'Colors' })}</span>
190
- <span className="ml-auto text-muted-foreground text-xs">
191
- {textures
192
- ? i18n._({ id: 'viewer.display.colored', message: 'Colored' })
193
- : i18n._({ id: 'viewer.display.monochrome', message: 'Monochrome' })}
194
- </span>
195
- </DropdownMenuItem>
196
-
197
- <DropdownMenuSeparator />
198
-
199
- <QualityMenu subContentClassName="dark min-w-56" />
200
-
201
- {textures ? (
202
- <DaylightMenu subContentClassName="dark min-w-48" />
203
- ) : (
204
- <>
205
- <DropdownMenuSub>
206
- <DropdownMenuSubTrigger>
207
- <SwatchBook className="h-4 w-4" />
208
- <span>{i18n._({ id: 'viewer.theme.title', message: 'Scene theme' })}</span>
209
- <span className="ml-auto truncate text-muted-foreground text-xs">
210
- {sceneThemeName(activeTheme.id, activeTheme.name, i18n)}
211
- </span>
212
- </DropdownMenuSubTrigger>
213
- <DropdownMenuSubContent className="dark min-w-48">
214
- {SCENE_THEMES.map((theme) => {
215
- const swatches = (['wall', 'roof', 'floor', 'glazing'] as const).map(
216
- (role) => theme.clayTints?.[role] ?? CLAY_PALETTE[role],
217
- )
218
- return (
219
- <DropdownMenuItem
220
- className="gap-2"
221
- key={theme.id}
222
- onSelect={() => useViewer.getState().setSceneTheme(theme.id)}
223
- >
224
- <span
225
- className="grid h-5 w-5 shrink-0 grid-cols-2 overflow-hidden rounded-sm border border-black/10"
226
- style={{ backgroundColor: theme.background }}
227
- >
228
- {swatches.map((color, index) => (
229
- <span key={`${theme.id}-${index}`} style={{ backgroundColor: color }} />
230
- ))}
231
- </span>
232
- <span>{sceneThemeName(theme.id, theme.name, i18n)}</span>
233
- {sceneTheme === theme.id ? <Check className="ml-auto h-4 w-4" /> : null}
234
- </DropdownMenuItem>
235
- )
236
- })}
237
- </DropdownMenuSubContent>
238
- </DropdownMenuSub>
239
- <EdgesToggleItem />
240
- </>
241
- )}
242
- </DropdownMenuContent>
243
- </DropdownMenu>
244
- )
245
- }
246
-
247
- export type ViewerControlsBarProps = {
248
- canShowScans?: boolean
249
- canShowGuides?: boolean
250
- /** A baked GLB is the active artifact: hide controls it can't honor (wall
251
- * modes aren't baked into the GLB). */
252
- glbActive?: boolean
253
- /** In GLB mode, whether scans/guides were re-added from scene data — so the
254
- * visibility control surfaces the matching toggle even though the artifact
255
- * itself carries none. */
256
- glbHasScans?: boolean
257
- glbHasGuides?: boolean
258
- walkthroughActive?: boolean
259
- onWalkthroughToggle: () => void
260
- /** The free-flying drone camera; its button shows only with a handler, and never on touch. */
261
- droneActive?: boolean
262
- onDroneToggle?: () => void
263
- className?: string
264
- }
265
-
266
- export const ViewerControlsBar = ({
267
- canShowScans = true,
268
- canShowGuides = true,
269
- glbActive = false,
270
- glbHasScans = false,
271
- glbHasGuides = false,
272
- walkthroughActive = false,
273
- onWalkthroughToggle,
274
- droneActive = false,
275
- onDroneToggle,
276
- className,
277
- }: ViewerControlsBarProps) => {
278
- const i18n = usePascalI18n()
279
- const levelMode = useViewer((s) => s.levelMode)
280
- const wallMode = useViewer((s) => s.wallMode)
281
- const textures = useViewer((s) => s.textures)
282
- const isMobile = useIsMobile()
283
- // Sessions may carry a stale mode outside the cycle (e.g. the retired
284
- // 'translucent'); render and cycle it as cutaway instead of crashing.
285
- const safeWallMode = (
286
- wallMode in wallModeConfig ? wallMode : 'cutaway'
287
- ) as keyof typeof wallModeConfig
288
-
289
- return (
290
- <>
291
- {textures && !isMobile ? (
292
- // The sky (time and weather) beside the compass in the scene's bottom-left corner.
293
- <SkyControl dark height={SKY_DOCK_HEIGHT.viewer} style={{ left: 52 }} />
294
- ) : null}
295
- <div
296
- className={cn(
297
- 'dark absolute bottom-4 left-1/2 z-20 -translate-x-1/2 text-foreground sm:bottom-6',
298
- className,
299
- )}
300
- >
301
- <TooltipProvider delayDuration={0}>
302
- <div
303
- data-surface="viewer-dock"
304
- className="corner-smooth pointer-events-auto flex h-12 max-w-[calc(100vw-1rem)] flex-row items-center justify-center gap-0.5 overflow-hidden rounded-2xl border border-border/40 bg-background/95 p-1 shadow-elevation-4 backdrop-blur-xl transition-colors duration-200 ease-out sm:h-14 sm:gap-1.5 sm:p-1.5"
305
- >
306
- {((canShowScans && (!glbActive || glbHasScans)) ||
307
- (canShowGuides && (!glbActive || glbHasGuides))) && (
308
- <>
309
- <VisibilityMenu
310
- canShowGuides={canShowGuides && (!glbActive || glbHasGuides)}
311
- canShowScans={canShowScans && (!glbActive || glbHasScans)}
312
- />
313
- <div className="mx-1 h-5 w-px bg-border/40" />
314
- </>
315
- )}
316
-
317
- {/* Level mode */}
318
- <ActionButton
319
- className={
320
- levelMode === 'stacked'
321
- ? 'hover:bg-white/5 hover:text-amber-400'
322
- : 'bg-amber-500/20 text-amber-400'
323
- }
324
- label={i18n._({
325
- id: 'viewer.levels.label',
326
- message: 'Levels: {mode}',
327
- values: { mode: levelModeLabel(levelMode, i18n) },
328
- })}
329
- onClick={() => {
330
- if (levelMode === 'manual') return useViewer.getState().setLevelMode('stacked')
331
- const modes: ('stacked' | 'exploded' | 'solo')[] = ['stacked', 'exploded', 'solo']
332
- const nextIndex = (modes.indexOf(levelMode as any) + 1) % modes.length
333
- useViewer.getState().setLevelMode(modes[nextIndex] ?? 'stacked')
334
- }}
335
- size="icon"
336
- tooltipClassName="dark"
337
- tooltipSide="top"
338
- variant="ghost"
339
- >
340
- {levelMode === 'solo' && <Diamond className="h-6 w-6" />}
341
- {levelMode === 'exploded' && <Layers2 className="h-6 w-6" />}
342
- {(levelMode === 'stacked' || levelMode === 'manual') && (
343
- <Layers className="h-6 w-6" />
344
- )}
345
- </ActionButton>
346
-
347
- {/* Wall mode — parametric only; baked GLB walls are fixed-height. */}
348
- {!glbActive && (
349
- <ActionButton
350
- className={
351
- safeWallMode === 'cutaway'
352
- ? 'opacity-60 grayscale hover:bg-white/5 hover:opacity-100 hover:grayscale-0'
353
- : 'bg-white/10'
354
- }
355
- label={i18n._({
356
- id: 'viewer.walls.label',
357
- message: 'Walls: {mode}',
358
- values: { mode: wallModeLabel(safeWallMode, i18n) },
359
- })}
360
- onClick={() => {
361
- const modes: ('cutaway' | 'up' | 'down')[] = ['cutaway', 'up', 'down']
362
- const nextIndex = (modes.indexOf(safeWallMode) + 1) % modes.length
363
- useViewer.getState().setWallMode(modes[nextIndex] ?? 'cutaway')
364
- }}
365
- size="icon"
366
- tooltipClassName="dark"
367
- tooltipSide="top"
368
- variant="ghost"
369
- >
370
- <img
371
- alt={wallModeLabel(safeWallMode, i18n)}
372
- className="h-[28px] w-[28px]"
373
- height={28}
374
- src={wallModeConfig[safeWallMode].icon}
375
- width={28}
376
- />
377
- </ActionButton>
378
- )}
379
-
380
- <div className="mx-1 h-5 w-px bg-border/40" />
381
-
382
- <DisplayMenu />
383
-
384
- <div className="mx-1 h-5 w-px bg-border/40" />
385
-
386
- {/* Walkthrough */}
387
- <ActionButton
388
- className={
389
- walkthroughActive
390
- ? 'bg-emerald-500/20 text-emerald-400'
391
- : 'hover:bg-white/5 hover:text-emerald-400'
392
- }
393
- label={i18n._({
394
- id: 'viewer.walkthrough.label',
395
- message: 'Walkthrough: {state}',
396
- values: {
397
- state: walkthroughActive
398
- ? i18n._({ id: 'viewer.common.on', message: 'On' })
399
- : i18n._({ id: 'viewer.common.off', message: 'Off' }),
400
- },
401
- })}
402
- onClick={onWalkthroughToggle}
403
- size="icon"
404
- tooltipClassName="dark"
405
- tooltipSide="top"
406
- variant="ghost"
407
- >
408
- <Footprints className="h-6 w-6" />
409
- </ActionButton>
410
-
411
- {/* Drone — flying needs a keyboard. */}
412
- {onDroneToggle && !isMobile && (
413
- <ActionButton
414
- className={
415
- droneActive
416
- ? 'bg-emerald-500/20 text-emerald-400'
417
- : 'hover:bg-white/5 hover:text-emerald-400'
418
- }
419
- label={i18n._({
420
- id: 'viewer.drone.label',
421
- message: 'Drone: {state}',
422
- values: {
423
- state: droneActive
424
- ? i18n._({ id: 'viewer.common.on', message: 'On' })
425
- : i18n._({ id: 'viewer.common.off', message: 'Off' }),
426
- },
427
- })}
428
- onClick={onDroneToggle}
429
- size="icon"
430
- tooltipClassName="dark"
431
- tooltipSide="top"
432
- variant="ghost"
433
- >
434
- <Drone className="h-6 w-6" />
435
- </ActionButton>
436
- )}
437
-
438
- <div className="mx-1 h-5 w-px bg-border/40" />
439
-
440
- {/* Follow Pascal */}
441
- <FollowPascalToggle className="hidden sm:inline-flex" tooltipClassName="dark" />
442
-
443
- {/* Camera actions */}
444
- <ActionButton
445
- className="group hidden hover:bg-white/5 sm:inline-flex"
446
- label={i18n._({ id: 'viewer.camera.orbitLeft', message: 'Orbit left' })}
447
- onClick={() => emitter.emit('camera-controls:orbit-ccw')}
448
- size="icon"
449
- tooltipClassName="dark"
450
- tooltipSide="top"
451
- variant="ghost"
452
- >
453
- <img
454
- alt={i18n._({ id: 'viewer.camera.orbitLeft', message: 'Orbit left' })}
455
- className="h-[28px] w-[28px] -scale-x-100 object-contain opacity-70 transition-opacity group-hover:opacity-100"
456
- src="/icons/rotate.webp"
457
- />
458
- </ActionButton>
459
-
460
- <ActionButton
461
- className="group hidden hover:bg-white/5 sm:inline-flex"
462
- label={i18n._({ id: 'viewer.camera.orbitRight', message: 'Orbit right' })}
463
- onClick={() => emitter.emit('camera-controls:orbit-cw')}
464
- size="icon"
465
- tooltipClassName="dark"
466
- tooltipSide="top"
467
- variant="ghost"
468
- >
469
- <img
470
- alt={i18n._({ id: 'viewer.camera.orbitRight', message: 'Orbit right' })}
471
- className="h-[28px] w-[28px] object-contain opacity-70 transition-opacity group-hover:opacity-100"
472
- src="/icons/rotate.webp"
473
- />
474
- </ActionButton>
475
-
476
- <ActionButton
477
- className="group hover:bg-white/5"
478
- label={i18n._({ id: 'viewer.camera.topView', message: 'Top view' })}
479
- onClick={() => emitter.emit('camera-controls:top-view')}
480
- size="icon"
481
- tooltipClassName="dark"
482
- tooltipSide="top"
483
- variant="ghost"
484
- >
485
- <img
486
- alt={i18n._({ id: 'viewer.camera.topView', message: 'Top view' })}
487
- className="h-[28px] w-[28px] object-contain opacity-70 transition-opacity group-hover:opacity-100"
488
- src="/icons/topview.webp"
489
- />
490
- </ActionButton>
491
- </div>
492
- </TooltipProvider>
493
- </div>
494
- </>
495
- )
496
- }
@@ -1,42 +0,0 @@
1
- 'use client'
2
-
3
- import { usePascalI18n, useScene } from '@pascal-app/core'
4
- import { useViewer } from '@pascal-app/viewer'
5
- import { nodeDisplayLabel } from '../../lib/node-display-label'
6
- import type { DescriptorTranslator } from '../../lib/room-name-messages'
7
- import { type ViewerNodes, zoneLabel } from '../../lib/viewer-selection'
8
-
9
- /** The hovered node's name, for the bottom pill. A room reads as the room. */
10
- export function viewerHoverLabel(
11
- hoveredId: string | null,
12
- nodes: ViewerNodes,
13
- i18n: DescriptorTranslator,
14
- ): string | null {
15
- const node = hoveredId ? nodes[hoveredId] : undefined
16
- if (!node) return null
17
- return node.type === 'zone' ? zoneLabel(node, i18n) : nodeDisplayLabel(node)
18
- }
19
-
20
- /**
21
- * What a click would select, named in a pill above the controls bar. Every
22
- * viewer surface's hit source sets the hover (`applyViewerHover`); hidden in
23
- * the walkthrough and on devices without a hovering pointer. Names come from
24
- * the parametric scene store unless the surface passes its own graph.
25
- */
26
- export function ViewerHoverLabel({ nodes: nodesProp }: { nodes?: ViewerNodes }) {
27
- const i18n = usePascalI18n()
28
- const sceneNodes = useScene((s) => s.nodes)
29
- const nodes = nodesProp ?? sceneNodes
30
- const label = useViewer((s) =>
31
- s.walkthroughMode ? null : viewerHoverLabel(s.hoveredId, nodes, i18n),
32
- )
33
- if (!label) return null
34
- return (
35
- <div
36
- className="pointer-events-none absolute bottom-24 left-1/2 z-20 -translate-x-1/2 rounded-full border border-border/60 bg-background/90 px-3 py-1 text-foreground text-sm shadow-elevation-3 backdrop-blur [@media(hover:none)]:hidden"
37
- data-testid="viewer-hover-label"
38
- >
39
- {label}
40
- </div>
41
- )
42
- }