@pascal-app/editor 0.9.1 → 1.0.0-beta.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 (366) hide show
  1. package/package.json +14 -9
  2. package/src/components/editor/alignment-3d-guide-layer.tsx +10 -2
  3. package/src/components/editor/bake-exporter.tsx +49 -0
  4. package/src/components/editor/bake-thumbnail.tsx +95 -0
  5. package/src/components/editor/camera-dragging-lifecycle.test.ts +49 -0
  6. package/src/components/editor/camera-dragging-lifecycle.ts +41 -0
  7. package/src/components/editor/custom-camera-controls.tsx +332 -199
  8. package/src/components/editor/delete-confirmation-dialog.tsx +54 -0
  9. package/src/components/editor/editor-layout-mobile.tsx +5 -0
  10. package/src/components/editor/editor-layout-v2.tsx +18 -0
  11. package/src/components/editor/elevation-3d-guide-layer.tsx +120 -0
  12. package/src/components/editor/export-manager.tsx +71 -63
  13. package/src/components/editor/fence-tangent-lines-3d.tsx +87 -0
  14. package/src/components/editor/first-person/build-collider-world.test.ts +102 -5
  15. package/src/components/editor/first-person/build-collider-world.ts +52 -5
  16. package/src/components/editor/first-person/terrain-collider.test.ts +132 -0
  17. package/src/components/editor/first-person/terrain-collider.ts +212 -0
  18. package/src/components/editor/first-person-controls.tsx +410 -136
  19. package/src/components/editor/floating-action-menu.tsx +435 -64
  20. package/src/components/editor/floating-building-action-menu.tsx +1 -1
  21. package/src/components/editor/floorplan-background-selection.test.ts +72 -0
  22. package/src/components/editor/floorplan-background-selection.ts +7 -20
  23. package/src/components/editor/floorplan-camera-sync.test.ts +249 -0
  24. package/src/components/editor/floorplan-camera-sync.ts +213 -0
  25. package/src/components/editor/floorplan-mode-coordinator.tsx +82 -0
  26. package/src/components/editor/floorplan-navigation-presentation.test.ts +142 -0
  27. package/src/components/editor/floorplan-navigation-presentation.ts +156 -0
  28. package/src/components/editor/floorplan-panel.tsx +2517 -1492
  29. package/src/components/editor/grid.tsx +166 -39
  30. package/src/components/editor/group-actions.ts +597 -0
  31. package/src/components/editor/group-floating-action-menu.tsx +130 -0
  32. package/src/components/editor/group-move-3d.ts +397 -0
  33. package/src/components/editor/group-rotate-handle.tsx +102 -64
  34. package/src/components/editor/group-selection-box-3d.tsx +173 -0
  35. package/src/components/editor/group-transform-shared.test.ts +192 -1
  36. package/src/components/editor/group-transform-shared.ts +240 -7
  37. package/src/components/editor/handles/handle-arrow.tsx +81 -23
  38. package/src/components/editor/handles/handle-drag-history.test.ts +96 -0
  39. package/src/components/editor/handles/handle-drag-history.ts +14 -0
  40. package/src/components/editor/handles/preview-overrides.test.ts +26 -0
  41. package/src/components/editor/handles/preview-overrides.ts +13 -0
  42. package/src/components/editor/handles/resize-snap.test.ts +52 -0
  43. package/src/components/editor/handles/resize-snap.ts +23 -0
  44. package/src/components/editor/handles/use-handle-drag.ts +49 -6
  45. package/src/components/editor/index.tsx +289 -35
  46. package/src/components/editor/measurement-pill.tsx +68 -23
  47. package/src/components/editor/node-action-menu.tsx +14 -1
  48. package/src/components/editor/node-arrow-handles.tsx +409 -180
  49. package/src/components/editor/opening-guides-3d-layer.tsx +144 -0
  50. package/src/components/editor/quick-measurement-card.tsx +76 -0
  51. package/src/components/editor/quick-measurement-hud.tsx +34 -0
  52. package/src/components/editor/riser-diagram-panel.tsx +137 -0
  53. package/src/components/editor/selection-manager.tsx +814 -466
  54. package/src/components/editor/site-edge-labels.tsx +28 -6
  55. package/src/components/editor/slab-hole-highlights.tsx +13 -5
  56. package/src/components/editor/snapshot-capture-overlay.tsx +258 -115
  57. package/src/components/editor/three-context-bridge.ts +22 -0
  58. package/src/components/editor/thumbnail-generator.tsx +118 -253
  59. package/src/components/editor/use-floorplan-background-placement.ts +113 -45
  60. package/src/components/editor/use-floorplan-hit-testing.test.ts +82 -0
  61. package/src/components/editor/use-floorplan-hit-testing.ts +144 -161
  62. package/src/components/editor/use-floorplan-scene-data.ts +5 -5
  63. package/src/components/editor/use-mesh-settle-epoch.ts +27 -0
  64. package/src/components/editor/wall-measurement-label.tsx +10 -28
  65. package/src/components/editor/wall-move-side-handles.tsx +229 -39
  66. package/src/components/editor/wall-opening-highlights.tsx +63 -32
  67. package/src/components/editor/wall-snap-beacon-layer.tsx +162 -6
  68. package/src/components/editor-2d/floorplan-action-menu-layer.tsx +9 -5
  69. package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +2 -1
  70. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +40 -9
  71. package/src/components/editor-2d/floorplan-cursor-indicator-position.test.ts +31 -0
  72. package/src/components/editor-2d/floorplan-cursor-indicator-position.ts +39 -0
  73. package/src/components/editor-2d/floorplan-group-action-menu.tsx +87 -0
  74. package/src/components/editor-2d/floorplan-group-move.tsx +744 -0
  75. package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +113 -0
  76. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1672 -0
  77. package/src/components/editor-2d/floorplan-quick-measure-layer.tsx +212 -0
  78. package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +72 -0
  79. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +219 -20
  80. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +327 -59
  81. package/src/components/editor-2d/floorplan-render-context.test.ts +27 -0
  82. package/src/components/editor-2d/floorplan-render-context.tsx +113 -8
  83. package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +5 -3
  84. package/src/components/editor-2d/renderers/floorplan-annotation-layout.test.ts +285 -0
  85. package/src/components/editor-2d/renderers/floorplan-annotation-layout.ts +516 -0
  86. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.test.tsx +236 -0
  87. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +618 -0
  88. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +369 -0
  89. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +431 -32
  90. package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +89 -0
  91. package/src/components/editor-2d/renderers/floorplan-label-angle.ts +106 -0
  92. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +94 -26
  93. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +587 -0
  94. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +2280 -578
  95. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +20 -10
  96. package/src/components/editor-2d/renderers/floorplan-voronoi-layer.tsx +128 -0
  97. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +425 -61
  98. package/src/components/systems/ceiling/ceiling-system.tsx +97 -4
  99. package/src/components/systems/roof/roof-edit-system.tsx +1768 -13
  100. package/src/components/systems/selection-affordance-manager.tsx +38 -0
  101. package/src/components/systems/zone/zone-label-editor-system.tsx +14 -1
  102. package/src/components/systems/zone/zone-system.tsx +42 -13
  103. package/src/components/tools/elevator/elevator-tool.tsx +34 -8
  104. package/src/components/tools/elevator/move-elevator-tool.tsx +13 -2
  105. package/src/components/tools/fence/fence-drafting.ts +107 -8
  106. package/src/components/tools/item/move-tool.tsx +17 -13
  107. package/src/components/tools/item/placement-math.test.ts +13 -1
  108. package/src/components/tools/item/placement-math.ts +28 -2
  109. package/src/components/tools/item/placement-strategies.ts +246 -5
  110. package/src/components/tools/item/placement-types.ts +13 -1
  111. package/src/components/tools/item/use-draft-node.ts +146 -57
  112. package/src/components/tools/item/use-placement-coordinator.tsx +813 -125
  113. package/src/components/tools/registry/move-registry-node-tool.tsx +636 -103
  114. package/src/components/tools/roof/roof-tool.tsx +336 -94
  115. package/src/components/tools/select/box-select-state.ts +18 -7
  116. package/src/components/tools/select/box-select-tool.tsx +176 -40
  117. package/src/components/tools/select/marquee-geometry.test.ts +161 -0
  118. package/src/components/tools/select/marquee-geometry.ts +155 -0
  119. package/src/components/tools/select/plane-box-select-tool.tsx +1 -8
  120. package/src/components/tools/select/select-candidates.ts +1 -1
  121. package/src/components/tools/shared/cursor-sphere.tsx +62 -26
  122. package/src/components/tools/shared/drag-bounding-box.tsx +28 -4
  123. package/src/components/tools/shared/facing-indicator.tsx +87 -0
  124. package/src/components/tools/shared/facing-pose-indicator.tsx +54 -0
  125. package/src/components/tools/shared/floor-stack-preview.ts +4 -0
  126. package/src/components/tools/shared/horizontal-construction-plane.ts +123 -0
  127. package/src/components/tools/shared/placement-box.tsx +183 -1
  128. package/src/components/tools/shared/pointer-support-cap.test.ts +98 -0
  129. package/src/components/tools/shared/pointer-support-cap.ts +273 -0
  130. package/src/components/tools/shared/polygon-editor.tsx +276 -35
  131. package/src/components/tools/site/site-boundary-editor.tsx +101 -42
  132. package/src/components/tools/site/stroke-pointer-ownership.test.ts +91 -0
  133. package/src/components/tools/site/stroke-pointer-ownership.ts +57 -0
  134. package/src/components/tools/site/terrain-brush-cursor.tsx +247 -0
  135. package/src/components/tools/site/terrain-sculpt-grid.tsx +192 -0
  136. package/src/components/tools/site/terrain-sculpt-tool.tsx +356 -0
  137. package/src/components/tools/stair/stair-click-guard.test.ts +58 -0
  138. package/src/components/tools/stair/stair-click-guard.ts +72 -0
  139. package/src/components/tools/stair/stair-defaults.ts +1 -0
  140. package/src/components/tools/stair/stair-tool.tsx +164 -42
  141. package/src/components/tools/tool-manager.tsx +149 -30
  142. package/src/components/tools/wall/wall-drafting.test.ts +447 -0
  143. package/src/components/tools/wall/wall-drafting.ts +304 -108
  144. package/src/components/tools/wall/wall-snap-geometry.test.ts +52 -0
  145. package/src/components/tools/wall/wall-snap-geometry.ts +137 -9
  146. package/src/components/tools/zone/zone-boundary-editor.tsx +5 -1
  147. package/src/components/tools/zone/zone-tool.tsx +98 -88
  148. package/src/components/ui/action-menu/camera-actions.tsx +24 -17
  149. package/src/components/ui/action-menu/control-modes.tsx +44 -94
  150. package/src/components/ui/action-menu/furnish-tools.tsx +5 -5
  151. package/src/components/ui/action-menu/index.tsx +2 -4
  152. package/src/components/ui/action-menu/measurement-control.tsx +267 -0
  153. package/src/components/ui/action-menu/structure-tools.tsx +22 -13
  154. package/src/components/ui/action-menu/view-toggles.tsx +42 -90
  155. package/src/components/ui/command-palette/editor-commands.tsx +16 -3
  156. package/src/components/ui/command-palette/index.tsx +4 -3
  157. package/src/components/ui/controls/material-paint-panel.tsx +93 -18
  158. package/src/components/ui/controls/material-picker.tsx +162 -152
  159. package/src/components/ui/controls/material-properties-editor.tsx +108 -0
  160. package/src/components/ui/controls/metric-control.tsx +118 -44
  161. package/src/components/ui/controls/scene-material-list.tsx +247 -0
  162. package/src/components/ui/controls/slider-control.tsx +88 -38
  163. package/src/components/ui/controls/terrain-sculpt-panel.tsx +193 -0
  164. package/src/components/ui/floating-level-selector.tsx +74 -20
  165. package/src/components/ui/helpers/building-helper.tsx +10 -23
  166. package/src/components/ui/helpers/contextual-helper-panel.tsx +428 -0
  167. package/src/components/ui/helpers/helper-manager.tsx +273 -14
  168. package/src/components/ui/helpers/item-helper.tsx +28 -32
  169. package/src/components/ui/helpers/registered-tool-helper.tsx +45 -11
  170. package/src/components/ui/helpers/roof-helper.tsx +10 -12
  171. package/src/components/ui/icon-ref.tsx +47 -0
  172. package/src/components/ui/item-catalog/catalog-items.tsx +39 -1
  173. package/src/components/ui/item-catalog/item-catalog.tsx +13 -36
  174. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  175. package/src/components/ui/panels/multi-selection-panel.tsx +57 -0
  176. package/src/components/ui/panels/node-display.ts +17 -17
  177. package/src/components/ui/panels/panel-manager.tsx +36 -14
  178. package/src/components/ui/panels/panel-wrapper.tsx +24 -3
  179. package/src/components/ui/panels/parametric-inspector.tsx +19 -4
  180. package/src/components/ui/panels/reference-panel.tsx +54 -19
  181. package/src/components/ui/panels/selection-breakdown.test.ts +24 -0
  182. package/src/components/ui/panels/selection-breakdown.ts +20 -0
  183. package/src/components/ui/primitives/shortcut-token.tsx +39 -3
  184. package/src/components/ui/primitives/sidebar.tsx +1 -0
  185. package/src/components/ui/sidebar/app-sidebar.tsx +5 -1
  186. package/src/components/ui/sidebar/icon-rail.tsx +50 -31
  187. package/src/components/ui/sidebar/panels/plugins-panel.tsx +218 -0
  188. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +82 -41
  189. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +76 -6
  190. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +12 -4
  191. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +2 -1
  192. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -3
  193. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +10 -7
  194. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -1
  195. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -2
  196. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +10 -7
  197. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -1
  198. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +1 -1
  199. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +10 -7
  200. package/src/components/ui/sidebar/panels/site-panel/index.tsx +42 -17
  201. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +31 -17
  202. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +1 -1
  203. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +72 -25
  204. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +7 -3
  205. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +12 -8
  206. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +4 -3
  207. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +10 -7
  208. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -1
  209. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +2 -2
  210. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +44 -3
  211. package/src/components/ui/sidebar/panels/site-panel/tree-structure.ts +36 -0
  212. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -1
  213. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -2
  214. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -3
  215. package/src/components/ui/sidebar/tab-bar.tsx +42 -28
  216. package/src/components/ui/sidebar/use-plugin-panels.tsx +132 -0
  217. package/src/components/ui/snap-target-badge.test.tsx +40 -0
  218. package/src/components/ui/snap-target-badge.tsx +88 -0
  219. package/src/components/viewer/viewer-controls-bar.tsx +466 -0
  220. package/src/components/viewer/viewer-scene-header.tsx +235 -0
  221. package/src/components/viewer-overlay.tsx +36 -596
  222. package/src/components/viewer-zone-system.tsx +6 -1
  223. package/src/components/walkthrough-hud.tsx +111 -0
  224. package/src/hooks/use-auto-save.test.ts +14 -0
  225. package/src/hooks/use-auto-save.ts +54 -7
  226. package/src/hooks/use-ceiling-events.ts +3 -2
  227. package/src/hooks/use-drag-action.ts +4 -1
  228. package/src/hooks/use-grid-events.ts +24 -0
  229. package/src/hooks/use-keyboard.ts +438 -70
  230. package/src/index.tsx +355 -9
  231. package/src/lib/active-placement-surface.test.ts +24 -0
  232. package/src/lib/active-placement-surface.ts +42 -0
  233. package/src/lib/camera-pose.test.ts +237 -0
  234. package/src/lib/camera-pose.ts +172 -0
  235. package/src/lib/ceiling-plan-snap.ts +24 -0
  236. package/src/lib/contextual-help.test.ts +112 -0
  237. package/src/lib/contextual-help.ts +144 -0
  238. package/src/lib/continuation.ts +64 -0
  239. package/src/lib/direct-manipulation.test.ts +275 -0
  240. package/src/lib/direct-manipulation.ts +118 -0
  241. package/src/lib/door-interaction.ts +1 -1
  242. package/src/lib/editor-api.ts +23 -35
  243. package/src/lib/elevation-guides.test.ts +107 -0
  244. package/src/lib/elevation-guides.ts +236 -0
  245. package/src/lib/floorplan/annotation-visibility.test.ts +327 -0
  246. package/src/lib/floorplan/annotation-visibility.ts +133 -0
  247. package/src/lib/floorplan/apply-alignment.test.ts +25 -0
  248. package/src/lib/floorplan/apply-alignment.ts +18 -12
  249. package/src/lib/floorplan/drawing-coordination.test.ts +46 -0
  250. package/src/lib/floorplan/drawing-coordination.ts +13 -0
  251. package/src/lib/floorplan/floorplan-export.test.ts +319 -0
  252. package/src/lib/floorplan/floorplan-export.tsx +978 -0
  253. package/src/lib/floorplan/floorplan-extension.test.ts +43 -0
  254. package/src/lib/floorplan/floorplan-extension.ts +181 -0
  255. package/src/lib/floorplan/floorplan-mode.test.ts +120 -0
  256. package/src/lib/floorplan/floorplan-mode.ts +73 -0
  257. package/src/lib/floorplan/floorplan-pdfkit-document.ts +194 -0
  258. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +107 -0
  259. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +680 -0
  260. package/src/lib/floorplan/geometry.ts +52 -0
  261. package/src/lib/floorplan/index.ts +3 -0
  262. package/src/lib/floorplan/items.ts +5 -2
  263. package/src/lib/floorplan/plan-coords.ts +21 -0
  264. package/src/lib/fresh-planar-placement.test.ts +283 -3
  265. package/src/lib/fresh-planar-placement.ts +85 -1
  266. package/src/lib/glb-export.test.ts +556 -0
  267. package/src/lib/glb-export.ts +1071 -0
  268. package/src/lib/ground-surface.ts +95 -0
  269. package/src/lib/history.test.ts +96 -0
  270. package/src/lib/history.ts +31 -0
  271. package/src/lib/interaction/hot-set.test.ts +133 -0
  272. package/src/lib/interaction/hot-set.ts +67 -0
  273. package/src/lib/interaction/overlay-policy.test.ts +52 -0
  274. package/src/lib/interaction/overlay-policy.ts +59 -0
  275. package/src/lib/interaction/scope.ts +243 -0
  276. package/src/lib/level-duplication.test.ts +2 -1
  277. package/src/lib/level-selection.ts +2 -2
  278. package/src/lib/material-paint.ts +36 -49
  279. package/src/lib/measurement-kind.test.ts +30 -0
  280. package/src/lib/measurement-kind.ts +19 -0
  281. package/src/lib/measurement-label.test.ts +47 -0
  282. package/src/lib/measurement-label.ts +68 -0
  283. package/src/lib/measurement-parser.ts +116 -0
  284. package/src/lib/measurements.test.ts +188 -0
  285. package/src/lib/measurements.ts +210 -0
  286. package/src/lib/paint-scope.test.ts +454 -0
  287. package/src/lib/paint-scope.ts +461 -0
  288. package/src/lib/placement-drag-release.ts +23 -0
  289. package/src/lib/planar-cursor-placement.test.ts +57 -0
  290. package/src/lib/plugin-panels.test.ts +19 -0
  291. package/src/lib/plugin-panels.ts +91 -0
  292. package/src/lib/quick-action-feedback.ts +33 -0
  293. package/src/lib/quick-action-nodes.test.ts +94 -0
  294. package/src/lib/quick-action-nodes.ts +47 -0
  295. package/src/lib/quick-measurement.test.ts +77 -0
  296. package/src/lib/quick-measurement.ts +109 -0
  297. package/src/lib/roof-duplication.ts +6 -6
  298. package/src/lib/roof-hover-outline-proxy.ts +22 -0
  299. package/src/lib/roof-wall-hit.ts +164 -0
  300. package/src/lib/scene-clipboard.test.ts +325 -0
  301. package/src/lib/scene-clipboard.ts +310 -21
  302. package/src/lib/scene.ts +28 -5
  303. package/src/lib/selection-routing.test.ts +313 -0
  304. package/src/lib/selection-routing.ts +180 -0
  305. package/src/lib/sfx/index.ts +1 -0
  306. package/src/lib/sfx/movement-tick.test.ts +65 -0
  307. package/src/lib/sfx/movement-tick.ts +18 -0
  308. package/src/lib/sfx-bus.ts +82 -14
  309. package/src/lib/sfx-player.test.ts +266 -0
  310. package/src/lib/sfx-player.ts +262 -69
  311. package/src/lib/site-boundary.test.ts +53 -0
  312. package/src/lib/site-boundary.ts +27 -0
  313. package/src/lib/slab-plan-snap.test.ts +93 -0
  314. package/src/lib/slab-plan-snap.ts +108 -0
  315. package/src/lib/snapping-mode.test.ts +165 -0
  316. package/src/lib/snapping-mode.ts +191 -0
  317. package/src/lib/stair-duplication.test.ts +103 -0
  318. package/src/lib/stair-duplication.ts +58 -86
  319. package/src/lib/stair-levels.ts +2 -0
  320. package/src/lib/surface-plan-snap.test.ts +71 -0
  321. package/src/lib/surface-plan-snap.ts +256 -0
  322. package/src/lib/terrain-sculpt.test.ts +429 -0
  323. package/src/lib/terrain-sculpt.ts +301 -0
  324. package/src/lib/terrain-verb-color.test.ts +51 -0
  325. package/src/lib/terrain-verb-color.ts +58 -0
  326. package/src/lib/touch-gesture-priority.test.ts +45 -0
  327. package/src/lib/touch-gesture-priority.ts +30 -0
  328. package/src/lib/use-linear-display.ts +40 -0
  329. package/src/lib/window-interaction.ts +1 -1
  330. package/src/lib/world-grid-snap.ts +37 -5
  331. package/src/store/camera-pose-store.test.ts +29 -0
  332. package/src/store/camera-pose-store.ts +27 -0
  333. package/src/store/live-draft-preview-stores.test.ts +136 -0
  334. package/src/store/navigation-sync-pose-store.test.ts +34 -0
  335. package/src/store/navigation-sync-pose-store.ts +27 -0
  336. package/src/store/terrain-sculpt-mode.test.ts +223 -0
  337. package/src/store/use-delete-confirmation.ts +27 -0
  338. package/src/store/use-direct-manipulation-feedback.ts +20 -0
  339. package/src/store/use-drawing-view.test.ts +23 -0
  340. package/src/store/use-drawing-view.ts +84 -0
  341. package/src/store/use-editor.tsx +665 -126
  342. package/src/store/use-elevation-guides.ts +40 -0
  343. package/src/store/use-facing-pose.ts +44 -0
  344. package/src/store/use-fence-curve-draft.ts +51 -0
  345. package/src/store/use-first-person-hud.ts +40 -0
  346. package/src/store/use-floorplan-annotation-visibility.ts +60 -0
  347. package/src/store/use-floorplan-draft-preview.ts +151 -0
  348. package/src/store/use-floorplan-marquee.ts +50 -0
  349. package/src/store/use-floorplan-mode.ts +110 -0
  350. package/src/store/use-interaction-scope.test.ts +206 -0
  351. package/src/store/use-interaction-scope.ts +140 -0
  352. package/src/store/use-measurement-draft.test.ts +559 -0
  353. package/src/store/use-measurement-draft.ts +570 -0
  354. package/src/store/use-opening-guides.ts +41 -0
  355. package/src/store/use-path-draft-preview.ts +90 -0
  356. package/src/store/use-placement-preview.ts +11 -3
  357. package/src/store/use-quick-measurement-hud.test.ts +53 -0
  358. package/src/store/use-quick-measurement-hud.ts +77 -0
  359. package/src/store/use-segment-draft-chain.ts +28 -0
  360. package/src/store/use-stair-build-preview.ts +53 -0
  361. package/src/store/use-wall-snap-indicator.ts +2 -0
  362. package/src/components/editor/first-person/bvh-ecctrl.tsx +0 -860
  363. package/src/components/editor/group-move-handle.tsx +0 -316
  364. package/src/components/ui/panels/paint-panel.tsx +0 -163
  365. package/src/lib/floorplan/selection-tool.ts +0 -271
  366. package/src/lib/level-name.ts +0 -11
@@ -0,0 +1,466 @@
1
+ 'use client'
2
+
3
+ import { emitter } from '@pascal-app/core'
4
+ import {
5
+ CLAY_PALETTE,
6
+ type EdgeMode,
7
+ getSceneTheme,
8
+ SCENE_THEMES,
9
+ useViewer,
10
+ } from '@pascal-app/viewer'
11
+ import {
12
+ Box,
13
+ Camera,
14
+ Check,
15
+ Contrast,
16
+ Diamond,
17
+ Eye,
18
+ EyeOff,
19
+ Footprints,
20
+ Layers,
21
+ Layers2,
22
+ Palette,
23
+ PenLine,
24
+ SlidersHorizontal,
25
+ Sparkles,
26
+ Square,
27
+ SwatchBook,
28
+ } from 'lucide-react'
29
+ import { cn } from '../../lib/utils'
30
+ import { ActionButton } from '../ui/action-menu/action-button'
31
+ import {
32
+ DropdownMenu,
33
+ DropdownMenuContent,
34
+ DropdownMenuItem,
35
+ DropdownMenuSeparator,
36
+ DropdownMenuSub,
37
+ DropdownMenuSubContent,
38
+ DropdownMenuSubTrigger,
39
+ DropdownMenuTrigger,
40
+ } from '../ui/primitives/dropdown-menu'
41
+ import { TooltipProvider } from '../ui/primitives/tooltip'
42
+
43
+ const levelModeLabels: Record<'stacked' | 'exploded' | 'solo', string> = {
44
+ stacked: 'Stacked',
45
+ exploded: 'Exploded',
46
+ solo: 'Solo',
47
+ }
48
+
49
+ const wallModeConfig = {
50
+ up: {
51
+ icon: (props: any) => (
52
+ <img alt="Full height" height={28} src="/icons/room.webp" width={28} {...props} />
53
+ ),
54
+ label: 'Full height',
55
+ },
56
+ cutaway: {
57
+ icon: (props: any) => (
58
+ <img alt="Cutaway" height={28} src="/icons/wallcut.webp" width={28} {...props} />
59
+ ),
60
+ label: 'Cutaway',
61
+ },
62
+ down: {
63
+ icon: (props: any) => (
64
+ <img alt="Low" height={28} src="/icons/walllow.webp" width={28} {...props} />
65
+ ),
66
+ label: 'Low',
67
+ },
68
+ }
69
+
70
+ const SHADING_OPTIONS = [
71
+ { id: 'solid', name: 'Solid', detail: 'Flat and fast — no ambient occlusion', icon: Box },
72
+ { id: 'rendered', name: 'Rendered', detail: 'Full ambient occlusion', icon: Sparkles },
73
+ ] as const
74
+
75
+ const EDGE_OPTIONS = [
76
+ { id: 'off', name: 'Off', detail: 'No edge lines' },
77
+ { id: 'soft', name: 'Soft', detail: 'Faint outline of major creases' },
78
+ { id: 'strong', name: 'Strong', detail: 'Crisp, opaque edge lines' },
79
+ ] as const satisfies readonly { id: EdgeMode; name: string; detail: string }[]
80
+
81
+ // Keep the dropdown open when flipping an in-place toggle row.
82
+ const keepOpen = (event: Event, fn: () => void) => {
83
+ event.preventDefault()
84
+ fn()
85
+ }
86
+
87
+ // Scans + guides folded into one control. A baked GLB carries none of its own,
88
+ // but the GLB viewer re-adds them from scene data when the privacy flags allow,
89
+ // so the toggle shows for whichever exist.
90
+ function VisibilityMenu({
91
+ canShowScans,
92
+ canShowGuides,
93
+ }: {
94
+ canShowScans: boolean
95
+ canShowGuides: boolean
96
+ }) {
97
+ const showScans = useViewer((s) => s.showScans)
98
+ const showGuides = useViewer((s) => s.showGuides)
99
+ return (
100
+ <DropdownMenu>
101
+ <DropdownMenuTrigger asChild>
102
+ <ActionButton
103
+ className="hover:bg-white/5 hover:text-foreground"
104
+ label="Visibility"
105
+ size="icon"
106
+ tooltipSide="top"
107
+ variant="ghost"
108
+ >
109
+ <Eye className="h-5 w-5" />
110
+ </ActionButton>
111
+ </DropdownMenuTrigger>
112
+ <DropdownMenuContent align="center" className="min-w-44" side="top">
113
+ {canShowScans && (
114
+ <DropdownMenuItem
115
+ onSelect={(e) => keepOpen(e, () => useViewer.getState().setShowScans(!showScans))}
116
+ >
117
+ <img alt="" className="h-4 w-4 object-contain" src="/icons/mesh.webp" />
118
+ <span>Scans</span>
119
+ {showScans ? (
120
+ <Eye className="ml-auto h-4 w-4 text-foreground" />
121
+ ) : (
122
+ <EyeOff className="ml-auto h-4 w-4 text-muted-foreground" />
123
+ )}
124
+ </DropdownMenuItem>
125
+ )}
126
+ {canShowGuides && (
127
+ <DropdownMenuItem
128
+ onSelect={(e) => keepOpen(e, () => useViewer.getState().setShowGuides(!showGuides))}
129
+ >
130
+ <img alt="" className="h-4 w-4 object-contain" src="/icons/floorplan.webp" />
131
+ <span>Guides</span>
132
+ {showGuides ? (
133
+ <Eye className="ml-auto h-4 w-4 text-foreground" />
134
+ ) : (
135
+ <EyeOff className="ml-auto h-4 w-4 text-muted-foreground" />
136
+ )}
137
+ </DropdownMenuItem>
138
+ )}
139
+ </DropdownMenuContent>
140
+ </DropdownMenu>
141
+ )
142
+ }
143
+
144
+ // One "Display" button gathering shadows, camera projection, colors, render
145
+ // mode, scene theme and edges.
146
+ function DisplayMenu() {
147
+ const cameraMode = useViewer((s) => s.cameraMode)
148
+ const shading = useViewer((s) => s.shading)
149
+ const textures = useViewer((s) => s.textures)
150
+ const shadows = useViewer((s) => s.shadows)
151
+ const sceneTheme = useViewer((s) => s.sceneTheme)
152
+ const edges = useViewer((s) => s.edges)
153
+ const activeShading = SHADING_OPTIONS.find((o) => o.id === shading) ?? SHADING_OPTIONS[0]
154
+ const activeTheme = getSceneTheme(sceneTheme)
155
+ const activeEdges = EDGE_OPTIONS.find((o) => o.id === edges) ?? EDGE_OPTIONS[0]
156
+ return (
157
+ <DropdownMenu>
158
+ <DropdownMenuTrigger asChild>
159
+ <ActionButton
160
+ className="hover:bg-white/5 hover:text-foreground"
161
+ label="Display settings"
162
+ size="icon"
163
+ tooltipSide="top"
164
+ variant="ghost"
165
+ >
166
+ <SlidersHorizontal className="h-5 w-5" />
167
+ </ActionButton>
168
+ </DropdownMenuTrigger>
169
+ <DropdownMenuContent align="center" className="min-w-56" side="top">
170
+ <DropdownMenuItem
171
+ onSelect={(e) => keepOpen(e, () => useViewer.getState().setShadows(!shadows))}
172
+ >
173
+ <Contrast className="h-4 w-4" />
174
+ <span>Shadows</span>
175
+ <span className="ml-auto text-muted-foreground text-xs">{shadows ? 'On' : 'Off'}</span>
176
+ </DropdownMenuItem>
177
+ <DropdownMenuItem
178
+ onSelect={(e) =>
179
+ keepOpen(e, () =>
180
+ useViewer
181
+ .getState()
182
+ .setCameraMode(cameraMode === 'perspective' ? 'orthographic' : 'perspective'),
183
+ )
184
+ }
185
+ >
186
+ <Camera className="h-4 w-4" />
187
+ <span>Camera</span>
188
+ <span className="ml-auto text-muted-foreground text-xs">
189
+ {cameraMode === 'perspective' ? 'Perspective' : 'Orthographic'}
190
+ </span>
191
+ </DropdownMenuItem>
192
+ <DropdownMenuItem
193
+ onSelect={(e) => keepOpen(e, () => useViewer.getState().setTextures(!textures))}
194
+ >
195
+ {textures ? <Palette className="h-4 w-4" /> : <Square className="h-4 w-4" />}
196
+ <span>Colors</span>
197
+ <span className="ml-auto text-muted-foreground text-xs">
198
+ {textures ? 'Colored' : 'Monochrome'}
199
+ </span>
200
+ </DropdownMenuItem>
201
+
202
+ <DropdownMenuSeparator />
203
+
204
+ <DropdownMenuSub>
205
+ <DropdownMenuSubTrigger>
206
+ <activeShading.icon className="h-4 w-4" />
207
+ <span>Render</span>
208
+ <span className="ml-auto text-muted-foreground text-xs">{activeShading.name}</span>
209
+ </DropdownMenuSubTrigger>
210
+ <DropdownMenuSubContent className="min-w-56">
211
+ {SHADING_OPTIONS.map((option) => {
212
+ const OptionIcon = option.icon
213
+ return (
214
+ <DropdownMenuItem
215
+ key={option.id}
216
+ onSelect={() => useViewer.getState().setShading(option.id)}
217
+ >
218
+ <OptionIcon className="h-4 w-4" />
219
+ <div className="flex flex-col">
220
+ <span className="text-foreground">{option.name}</span>
221
+ <span className="text-muted-foreground text-xs">{option.detail}</span>
222
+ </div>
223
+ {shading === option.id ? (
224
+ <Check className="ml-auto h-4 w-4 text-foreground" />
225
+ ) : null}
226
+ </DropdownMenuItem>
227
+ )
228
+ })}
229
+ </DropdownMenuSubContent>
230
+ </DropdownMenuSub>
231
+
232
+ <DropdownMenuSub>
233
+ <DropdownMenuSubTrigger>
234
+ <SwatchBook className="h-4 w-4" />
235
+ <span>Theme</span>
236
+ <span className="ml-auto truncate text-muted-foreground text-xs">
237
+ {activeTheme.name}
238
+ </span>
239
+ </DropdownMenuSubTrigger>
240
+ <DropdownMenuSubContent className="min-w-48">
241
+ {SCENE_THEMES.map((t) => {
242
+ const swatches = (['wall', 'roof', 'floor', 'glazing'] as const).map(
243
+ (role) => t.clayTints?.[role] ?? CLAY_PALETTE[role],
244
+ )
245
+ return (
246
+ <DropdownMenuItem
247
+ className="gap-2"
248
+ key={t.id}
249
+ onSelect={() => useViewer.getState().setSceneTheme(t.id)}
250
+ >
251
+ <span
252
+ className="grid h-5 w-5 shrink-0 grid-cols-2 overflow-hidden rounded-sm border border-black/10"
253
+ style={{ backgroundColor: t.background }}
254
+ >
255
+ {swatches.map((color, index) => (
256
+ <span key={`${t.id}-${index}`} style={{ backgroundColor: color }} />
257
+ ))}
258
+ </span>
259
+ <span>{t.name}</span>
260
+ {sceneTheme === t.id ? <Check className="ml-auto h-4 w-4" /> : null}
261
+ </DropdownMenuItem>
262
+ )
263
+ })}
264
+ </DropdownMenuSubContent>
265
+ </DropdownMenuSub>
266
+
267
+ <DropdownMenuSub>
268
+ <DropdownMenuSubTrigger>
269
+ <PenLine className="h-4 w-4" />
270
+ <span>Edges</span>
271
+ <span className="ml-auto text-muted-foreground text-xs">{activeEdges.name}</span>
272
+ </DropdownMenuSubTrigger>
273
+ <DropdownMenuSubContent className="min-w-56">
274
+ {EDGE_OPTIONS.map((option) => (
275
+ <DropdownMenuItem
276
+ key={option.id}
277
+ onSelect={() => useViewer.getState().setEdges(option.id)}
278
+ >
279
+ <div className="flex flex-col">
280
+ <span className="text-foreground">{option.name}</span>
281
+ <span className="text-muted-foreground text-xs">{option.detail}</span>
282
+ </div>
283
+ {edges === option.id ? <Check className="ml-auto h-4 w-4 text-foreground" /> : null}
284
+ </DropdownMenuItem>
285
+ ))}
286
+ </DropdownMenuSubContent>
287
+ </DropdownMenuSub>
288
+ </DropdownMenuContent>
289
+ </DropdownMenu>
290
+ )
291
+ }
292
+
293
+ export type ViewerControlsBarProps = {
294
+ canShowScans?: boolean
295
+ canShowGuides?: boolean
296
+ /** A baked GLB is the active artifact: hide controls it can't honor (wall
297
+ * modes aren't baked into the GLB). */
298
+ glbActive?: boolean
299
+ /** In GLB mode, whether scans/guides were re-added from scene data — so the
300
+ * visibility control surfaces the matching toggle even though the artifact
301
+ * itself carries none. */
302
+ glbHasScans?: boolean
303
+ glbHasGuides?: boolean
304
+ walkthroughActive?: boolean
305
+ onWalkthroughToggle: () => void
306
+ className?: string
307
+ }
308
+
309
+ export const ViewerControlsBar = ({
310
+ canShowScans = true,
311
+ canShowGuides = true,
312
+ glbActive = false,
313
+ glbHasScans = false,
314
+ glbHasGuides = false,
315
+ walkthroughActive = false,
316
+ onWalkthroughToggle,
317
+ className,
318
+ }: ViewerControlsBarProps) => {
319
+ const levelMode = useViewer((s) => s.levelMode)
320
+ const wallMode = useViewer((s) => s.wallMode)
321
+ // Sessions may carry a stale mode outside the cycle (e.g. the retired
322
+ // 'translucent'); render and cycle it as cutaway instead of crashing.
323
+ const safeWallMode = (
324
+ wallMode in wallModeConfig ? wallMode : 'cutaway'
325
+ ) as keyof typeof wallModeConfig
326
+ const WallModeIcon = wallModeConfig[safeWallMode].icon
327
+
328
+ return (
329
+ <div
330
+ className={cn(
331
+ 'dark absolute bottom-4 left-1/2 z-20 -translate-x-1/2 text-foreground sm:bottom-6',
332
+ className,
333
+ )}
334
+ >
335
+ <TooltipProvider delayDuration={0}>
336
+ <div 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">
337
+ {((canShowScans && (!glbActive || glbHasScans)) ||
338
+ (canShowGuides && (!glbActive || glbHasGuides))) && (
339
+ <>
340
+ <VisibilityMenu
341
+ canShowGuides={canShowGuides && (!glbActive || glbHasGuides)}
342
+ canShowScans={canShowScans && (!glbActive || glbHasScans)}
343
+ />
344
+ <div className="mx-1 h-5 w-px bg-border/40" />
345
+ </>
346
+ )}
347
+
348
+ {/* Level mode */}
349
+ <ActionButton
350
+ className={
351
+ levelMode === 'stacked'
352
+ ? 'hover:bg-white/5 hover:text-amber-400'
353
+ : 'bg-amber-500/20 text-amber-400'
354
+ }
355
+ label={`Levels: ${levelMode === 'manual' ? 'Manual' : levelModeLabels[levelMode as keyof typeof levelModeLabels]}`}
356
+ onClick={() => {
357
+ if (levelMode === 'manual') return useViewer.getState().setLevelMode('stacked')
358
+ const modes: ('stacked' | 'exploded' | 'solo')[] = ['stacked', 'exploded', 'solo']
359
+ const nextIndex = (modes.indexOf(levelMode as any) + 1) % modes.length
360
+ useViewer.getState().setLevelMode(modes[nextIndex] ?? 'stacked')
361
+ }}
362
+ size="icon"
363
+ tooltipSide="top"
364
+ variant="ghost"
365
+ >
366
+ {levelMode === 'solo' && <Diamond className="h-6 w-6" />}
367
+ {levelMode === 'exploded' && <Layers2 className="h-6 w-6" />}
368
+ {(levelMode === 'stacked' || levelMode === 'manual') && <Layers className="h-6 w-6" />}
369
+ </ActionButton>
370
+
371
+ {/* Wall mode — parametric only; baked GLB walls are fixed-height. */}
372
+ {!glbActive && (
373
+ <ActionButton
374
+ className={
375
+ safeWallMode === 'cutaway'
376
+ ? 'opacity-60 grayscale hover:bg-white/5 hover:opacity-100 hover:grayscale-0'
377
+ : 'bg-white/10'
378
+ }
379
+ label={`Walls: ${wallModeConfig[safeWallMode].label}`}
380
+ onClick={() => {
381
+ const modes: ('cutaway' | 'up' | 'down')[] = ['cutaway', 'up', 'down']
382
+ const nextIndex = (modes.indexOf(safeWallMode) + 1) % modes.length
383
+ useViewer.getState().setWallMode(modes[nextIndex] ?? 'cutaway')
384
+ }}
385
+ size="icon"
386
+ tooltipSide="top"
387
+ variant="ghost"
388
+ >
389
+ <WallModeIcon className="h-[28px] w-[28px]" />
390
+ </ActionButton>
391
+ )}
392
+
393
+ <div className="mx-1 h-5 w-px bg-border/40" />
394
+
395
+ <DisplayMenu />
396
+
397
+ <div className="mx-1 h-5 w-px bg-border/40" />
398
+
399
+ {/* Walkthrough */}
400
+ <ActionButton
401
+ className={
402
+ walkthroughActive
403
+ ? 'bg-emerald-500/20 text-emerald-400'
404
+ : 'hover:bg-white/5 hover:text-emerald-400'
405
+ }
406
+ label={`Walkthrough: ${walkthroughActive ? 'On' : 'Off'}`}
407
+ onClick={onWalkthroughToggle}
408
+ size="icon"
409
+ tooltipSide="top"
410
+ variant="ghost"
411
+ >
412
+ <Footprints className="h-6 w-6" />
413
+ </ActionButton>
414
+
415
+ <div className="mx-1 h-5 w-px bg-border/40" />
416
+
417
+ {/* Camera actions */}
418
+ <ActionButton
419
+ className="group hidden hover:bg-white/5 sm:inline-flex"
420
+ label="Orbit left"
421
+ onClick={() => emitter.emit('camera-controls:orbit-ccw')}
422
+ size="icon"
423
+ tooltipSide="top"
424
+ variant="ghost"
425
+ >
426
+ <img
427
+ alt="Orbit left"
428
+ className="h-[28px] w-[28px] -scale-x-100 object-contain opacity-70 transition-opacity group-hover:opacity-100"
429
+ src="/icons/rotate.webp"
430
+ />
431
+ </ActionButton>
432
+
433
+ <ActionButton
434
+ className="group hidden hover:bg-white/5 sm:inline-flex"
435
+ label="Orbit right"
436
+ onClick={() => emitter.emit('camera-controls:orbit-cw')}
437
+ size="icon"
438
+ tooltipSide="top"
439
+ variant="ghost"
440
+ >
441
+ <img
442
+ alt="Orbit right"
443
+ className="h-[28px] w-[28px] object-contain opacity-70 transition-opacity group-hover:opacity-100"
444
+ src="/icons/rotate.webp"
445
+ />
446
+ </ActionButton>
447
+
448
+ <ActionButton
449
+ className="group hover:bg-white/5"
450
+ label="Top view"
451
+ onClick={() => emitter.emit('camera-controls:top-view')}
452
+ size="icon"
453
+ tooltipSide="top"
454
+ variant="ghost"
455
+ >
456
+ <img
457
+ alt="Top view"
458
+ className="h-[28px] w-[28px] object-contain opacity-70 transition-opacity group-hover:opacity-100"
459
+ src="/icons/topview.webp"
460
+ />
461
+ </ActionButton>
462
+ </div>
463
+ </TooltipProvider>
464
+ </div>
465
+ )
466
+ }