@pascal-app/editor 0.8.0 → 0.9.2

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 (350) hide show
  1. package/package.json +15 -13
  2. package/src/components/editor/alignment-3d-guide-layer.tsx +168 -0
  3. package/src/components/editor/bake-exporter.tsx +47 -0
  4. package/src/components/editor/custom-camera-controls.tsx +795 -28
  5. package/src/components/editor/editor-layout-mobile.tsx +8 -3
  6. package/src/components/editor/editor-layout-v2.tsx +76 -51
  7. package/src/components/editor/export-manager.tsx +69 -32
  8. package/src/components/editor/fence-tangent-lines-3d.tsx +87 -0
  9. package/src/components/editor/first-person/build-collider-world.test.ts +182 -0
  10. package/src/components/editor/first-person/build-collider-world.ts +228 -83
  11. package/src/components/editor/first-person-controls.tsx +943 -27
  12. package/src/components/editor/floating-action-menu.tsx +676 -208
  13. package/src/components/editor/floating-building-action-menu.tsx +2 -2
  14. package/src/components/editor/floorplan-background-selection.test.ts +58 -0
  15. package/src/components/editor/floorplan-background-selection.ts +3 -2
  16. package/src/components/editor/floorplan-panel.tsx +6916 -12137
  17. package/src/components/editor/grid.tsx +197 -36
  18. package/src/components/editor/group-actions.ts +457 -0
  19. package/src/components/editor/group-floating-action-menu.tsx +130 -0
  20. package/src/components/editor/group-move-3d.ts +397 -0
  21. package/src/components/editor/group-rotate-handle.tsx +420 -0
  22. package/src/components/editor/group-selection-box-3d.tsx +173 -0
  23. package/src/components/editor/group-transform-shared.test.ts +396 -0
  24. package/src/components/editor/group-transform-shared.ts +489 -0
  25. package/src/components/editor/handles/handle-arrow.tsx +581 -0
  26. package/src/components/editor/handles/use-handle-drag.ts +246 -0
  27. package/src/components/editor/index.tsx +412 -162
  28. package/src/components/editor/measurement-pill.tsx +115 -0
  29. package/src/components/editor/node-action-menu.tsx +14 -1
  30. package/src/components/editor/node-arrow-handles.tsx +1646 -0
  31. package/src/components/editor/opening-guides-3d-layer.tsx +144 -0
  32. package/src/components/editor/quick-measurement-card.tsx +70 -0
  33. package/src/components/editor/quick-measurement-hud.tsx +28 -0
  34. package/src/components/editor/riser-diagram-panel.tsx +137 -0
  35. package/src/components/editor/selection-manager.tsx +1106 -483
  36. package/src/components/editor/site-edge-labels.tsx +45 -21
  37. package/src/components/editor/slab-hole-highlights.tsx +474 -0
  38. package/src/components/editor/snapshot-capture-overlay.tsx +369 -155
  39. package/src/components/editor/three-context-bridge.ts +22 -0
  40. package/src/components/editor/thumbnail-generator.tsx +140 -41
  41. package/src/components/editor/use-floorplan-background-placement.ts +190 -35
  42. package/src/components/editor/use-floorplan-hit-testing.ts +24 -0
  43. package/src/components/editor/use-floorplan-scene-data.ts +19 -7
  44. package/src/components/editor/use-mesh-settle-epoch.ts +26 -0
  45. package/src/components/editor/wall-measurement-label.tsx +14 -19
  46. package/src/components/editor/wall-move-side-handles.tsx +870 -0
  47. package/src/components/editor/wall-opening-highlights.tsx +181 -0
  48. package/src/components/editor/wall-snap-beacon-layer.tsx +304 -0
  49. package/src/components/editor-2d/floorplan-action-menu-layer.tsx +15 -6
  50. package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +142 -0
  51. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +5 -3
  52. package/src/components/editor-2d/floorplan-group-action-menu.tsx +87 -0
  53. package/src/components/editor-2d/floorplan-group-move.tsx +701 -0
  54. package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +113 -0
  55. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1656 -0
  56. package/src/components/editor-2d/floorplan-quick-measure-layer.tsx +212 -0
  57. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +392 -0
  58. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +857 -0
  59. package/src/components/editor-2d/floorplan-render-context.tsx +62 -0
  60. package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +77 -0
  61. package/src/components/editor-2d/floorplan-wall-move-ghost-layer.tsx +47 -0
  62. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +266 -0
  63. package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +15 -0
  64. package/src/components/editor-2d/renderers/floorplan-label-angle.ts +14 -0
  65. package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +108 -66
  66. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +62 -0
  67. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +308 -0
  68. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +3325 -0
  69. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +24 -9
  70. package/src/components/editor-2d/renderers/floorplan-voronoi-layer.tsx +128 -0
  71. package/src/components/editor-2d/svg-paths.ts +10 -2
  72. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +439 -57
  73. package/src/components/systems/ceiling/ceiling-system.tsx +109 -12
  74. package/src/components/systems/roof/roof-edit-system.tsx +1851 -22
  75. package/src/components/systems/selection-affordance-manager.tsx +38 -0
  76. package/src/components/systems/stair/stair-edit-system.tsx +1 -1
  77. package/src/components/systems/zone/zone-label-editor-system.tsx +14 -1
  78. package/src/components/systems/zone/zone-system.tsx +42 -13
  79. package/src/components/tools/elevator/elevator-defaults.ts +8 -0
  80. package/src/components/tools/elevator/elevator-tool.tsx +375 -0
  81. package/src/components/tools/elevator/move-elevator-tool.tsx +263 -0
  82. package/src/components/tools/fence/fence-drafting.ts +141 -27
  83. package/src/components/tools/item/move-tool.tsx +45 -105
  84. package/src/components/tools/item/placement-math.test.ts +22 -0
  85. package/src/components/tools/item/placement-math.ts +59 -7
  86. package/src/components/tools/item/placement-strategies.ts +432 -16
  87. package/src/components/tools/item/placement-types.ts +22 -1
  88. package/src/components/tools/item/use-draft-node.ts +51 -2
  89. package/src/components/tools/item/use-placement-coordinator.tsx +1399 -256
  90. package/src/components/tools/registry/move-registry-node-tool.tsx +1064 -0
  91. package/src/components/tools/roof/roof-tool.tsx +350 -28
  92. package/src/components/tools/select/box-select-state.ts +88 -0
  93. package/src/components/tools/select/box-select-tool.tsx +442 -520
  94. package/src/components/tools/select/marquee-geometry.test.ts +161 -0
  95. package/src/components/tools/select/marquee-geometry.ts +155 -0
  96. package/src/components/tools/select/plane-box-select-tool.tsx +570 -0
  97. package/src/components/tools/select/screen-rectangle-selection.ts +84 -0
  98. package/src/components/tools/select/select-candidates.test.ts +109 -0
  99. package/src/components/tools/select/select-candidates.ts +85 -0
  100. package/src/components/tools/shared/affordance-dispatch.ts +30 -0
  101. package/src/components/tools/shared/cursor-sphere.tsx +62 -26
  102. package/src/components/tools/shared/drag-bounding-box.tsx +195 -0
  103. package/src/components/tools/shared/facing-indicator.tsx +87 -0
  104. package/src/components/tools/shared/facing-pose-indicator.tsx +54 -0
  105. package/src/components/tools/shared/floor-stack-preview.ts +25 -0
  106. package/src/components/tools/shared/fresh-placement-visibility.ts +61 -0
  107. package/src/components/tools/shared/placement-box-geometry.ts +123 -0
  108. package/src/components/tools/shared/placement-box.tsx +305 -0
  109. package/src/components/tools/shared/polygon-editor.tsx +866 -175
  110. package/src/components/tools/shared/segment-angle.ts +56 -0
  111. package/src/components/tools/site/site-boundary-editor.tsx +520 -16
  112. package/src/components/tools/site/site-flag-model.ts +18 -0
  113. package/src/components/tools/stair/stair-defaults.ts +2 -1
  114. package/src/components/tools/stair/stair-tool.tsx +342 -37
  115. package/src/components/tools/tool-manager.tsx +239 -62
  116. package/src/components/tools/wall/wall-drafting.test.ts +330 -0
  117. package/src/components/tools/wall/wall-drafting.ts +248 -171
  118. package/src/components/tools/wall/wall-snap-geometry.test.ts +123 -0
  119. package/src/components/tools/wall/wall-snap-geometry.ts +331 -0
  120. package/src/components/tools/zone/zone-boundary-editor.tsx +5 -1
  121. package/src/components/tools/zone/zone-tool.tsx +86 -82
  122. package/src/components/ui/action-menu/action-button.tsx +21 -1
  123. package/src/components/ui/action-menu/camera-actions.tsx +24 -17
  124. package/src/components/ui/action-menu/control-modes.tsx +40 -170
  125. package/src/components/ui/action-menu/furnish-tools.tsx +5 -5
  126. package/src/components/ui/action-menu/index.tsx +3 -117
  127. package/src/components/ui/action-menu/measurement-control.tsx +188 -0
  128. package/src/components/ui/action-menu/structure-tools.tsx +27 -93
  129. package/src/components/ui/action-menu/view-toggles.tsx +279 -90
  130. package/src/components/ui/command-palette/editor-commands.tsx +20 -15
  131. package/src/components/ui/command-palette/index.tsx +5 -3
  132. package/src/components/ui/controls/material-paint-panel.tsx +152 -0
  133. package/src/components/ui/controls/material-picker.tsx +87 -177
  134. package/src/components/ui/controls/material-properties-editor.tsx +108 -0
  135. package/src/components/ui/controls/metric-control.tsx +142 -46
  136. package/src/components/ui/controls/scene-material-list.tsx +247 -0
  137. package/src/components/ui/controls/slider-control.tsx +88 -38
  138. package/src/components/ui/floating-level-selector.tsx +47 -10
  139. package/src/components/ui/helpers/building-helper.tsx +10 -23
  140. package/src/components/ui/helpers/contextual-helper-panel.tsx +428 -0
  141. package/src/components/ui/helpers/helper-manager.tsx +235 -23
  142. package/src/components/ui/helpers/item-helper.tsx +28 -32
  143. package/src/components/ui/helpers/registered-tool-helper.tsx +59 -0
  144. package/src/components/ui/helpers/roof-helper.tsx +10 -12
  145. package/src/components/ui/icon-ref.tsx +47 -0
  146. package/src/components/ui/item-catalog/catalog-items.tsx +40 -1
  147. package/src/components/ui/item-catalog/item-catalog.tsx +16 -36
  148. package/src/components/ui/level-duplicate-dialog.tsx +2 -1
  149. package/src/components/ui/panels/node-display.ts +17 -16
  150. package/src/components/ui/panels/panel-manager.tsx +55 -58
  151. package/src/components/ui/panels/panel-wrapper.tsx +238 -12
  152. package/src/components/ui/panels/parametric-inspector.tsx +482 -0
  153. package/src/components/ui/panels/reference-panel.tsx +54 -19
  154. package/src/components/ui/primitives/dropdown-menu.tsx +12 -8
  155. package/src/components/ui/primitives/shortcut-token.tsx +39 -3
  156. package/src/components/ui/primitives/sidebar.tsx +1 -0
  157. package/src/components/ui/scene-loader.tsx +1 -3
  158. package/src/components/ui/sidebar/app-sidebar.tsx +5 -1
  159. package/src/components/ui/sidebar/icon-rail.tsx +50 -31
  160. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +263 -0
  161. package/src/components/ui/sidebar/panels/items-panel/index.tsx +82 -8
  162. package/src/components/ui/sidebar/panels/plugins-panel.tsx +218 -0
  163. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +122 -41
  164. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +66 -7
  165. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +267 -0
  166. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -1
  167. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +9 -5
  168. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +86 -0
  169. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -1
  170. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -2
  171. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +92 -0
  172. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +75 -0
  173. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +3 -3
  174. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +86 -0
  175. package/src/components/ui/sidebar/panels/site-panel/index.tsx +42 -21
  176. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +31 -17
  177. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +2 -1
  178. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +140 -0
  179. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +48 -8
  180. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +132 -0
  181. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +9 -5
  182. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +86 -0
  183. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +7 -1
  184. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +2 -2
  185. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +112 -39
  186. package/src/components/ui/sidebar/panels/site-panel/tree-structure.ts +36 -0
  187. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -1
  188. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -2
  189. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -3
  190. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +51 -2
  191. package/src/components/ui/sidebar/tab-bar.tsx +75 -1
  192. package/src/components/ui/sidebar/use-plugin-panels.tsx +132 -0
  193. package/src/components/ui/snap-target-badge.test.tsx +40 -0
  194. package/src/components/ui/snap-target-badge.tsx +88 -0
  195. package/src/components/viewer-overlay.tsx +253 -61
  196. package/src/components/viewer-zone-system.tsx +7 -2
  197. package/src/hooks/use-auto-save.test.ts +14 -0
  198. package/src/hooks/use-auto-save.ts +64 -10
  199. package/src/hooks/use-ceiling-events.ts +176 -0
  200. package/src/hooks/use-drag-action.ts +127 -0
  201. package/src/hooks/use-keyboard.ts +493 -95
  202. package/src/hooks/use-selection.ts +64 -0
  203. package/src/index.tsx +451 -6
  204. package/src/lib/active-placement-surface.ts +35 -0
  205. package/src/lib/ceiling-plan-snap.ts +24 -0
  206. package/src/lib/constants.ts +6 -2
  207. package/src/lib/contextual-help.test.ts +112 -0
  208. package/src/lib/contextual-help.ts +144 -0
  209. package/src/lib/continuation.ts +64 -0
  210. package/src/lib/direct-manipulation.test.ts +192 -0
  211. package/src/lib/direct-manipulation.ts +109 -0
  212. package/src/lib/editor-api.ts +50 -0
  213. package/src/lib/elevator-support.ts +96 -0
  214. package/src/lib/floorplan/apply-alignment.test.ts +25 -0
  215. package/src/lib/floorplan/apply-alignment.ts +117 -0
  216. package/src/lib/floorplan/floorplan-export.tsx +364 -0
  217. package/src/lib/floorplan/geometry.ts +53 -0
  218. package/src/lib/floorplan/index.ts +10 -0
  219. package/src/lib/floorplan/items.ts +5 -2
  220. package/src/lib/floorplan/plan-coords.ts +21 -0
  221. package/src/lib/floorplan/selection-tool.ts +40 -0
  222. package/src/lib/floorplan/stairs.ts +12 -1
  223. package/src/lib/fresh-planar-placement.test.ts +339 -0
  224. package/src/lib/fresh-planar-placement.ts +130 -0
  225. package/src/lib/glb-export.test.ts +441 -0
  226. package/src/lib/glb-export.ts +874 -0
  227. package/src/lib/history.ts +11 -1
  228. package/src/lib/interaction/hot-set.test.ts +133 -0
  229. package/src/lib/interaction/hot-set.ts +67 -0
  230. package/src/lib/interaction/overlay-policy.test.ts +51 -0
  231. package/src/lib/interaction/overlay-policy.ts +59 -0
  232. package/src/lib/interaction/scope.ts +211 -0
  233. package/src/lib/level-duplication.ts +54 -42
  234. package/src/lib/level-selection.ts +2 -2
  235. package/src/lib/material-paint.ts +227 -48
  236. package/src/lib/measurement-kind.test.ts +30 -0
  237. package/src/lib/measurement-kind.ts +19 -0
  238. package/src/lib/measurement-label.test.ts +47 -0
  239. package/src/lib/measurement-label.ts +68 -0
  240. package/src/lib/measurement-parser.ts +116 -0
  241. package/src/lib/measurements.test.ts +183 -0
  242. package/src/lib/measurements.ts +199 -0
  243. package/src/lib/paint-scope.test.ts +454 -0
  244. package/src/lib/paint-scope.ts +461 -0
  245. package/src/lib/placement-drag-release.ts +23 -0
  246. package/src/lib/placement-metadata.ts +29 -0
  247. package/src/lib/planar-cursor-placement.test.ts +100 -0
  248. package/src/lib/planar-cursor-placement.ts +42 -0
  249. package/src/lib/plugin-panels.test.ts +19 -0
  250. package/src/lib/plugin-panels.ts +91 -0
  251. package/src/lib/quick-measurement.test.ts +77 -0
  252. package/src/lib/quick-measurement.ts +109 -0
  253. package/src/lib/roof-duplication.ts +7 -7
  254. package/src/lib/roof-hover-outline-proxy.ts +22 -0
  255. package/src/lib/roof-wall-hit.ts +164 -0
  256. package/src/lib/scene-clipboard.test.ts +196 -0
  257. package/src/lib/scene-clipboard.ts +332 -0
  258. package/src/lib/scene.ts +40 -8
  259. package/src/lib/selection-routing.test.ts +298 -0
  260. package/src/lib/selection-routing.ts +175 -0
  261. package/src/lib/sfx/index.ts +1 -0
  262. package/src/lib/sfx/movement-tick.test.ts +65 -0
  263. package/src/lib/sfx/movement-tick.ts +18 -0
  264. package/src/lib/sfx-bus.ts +69 -10
  265. package/src/lib/sfx-player.test.ts +124 -0
  266. package/src/lib/sfx-player.ts +148 -55
  267. package/src/lib/site-boundary.ts +1 -0
  268. package/src/lib/slab-plan-snap.test.ts +93 -0
  269. package/src/lib/slab-plan-snap.ts +108 -0
  270. package/src/lib/snapping-mode.test.ts +138 -0
  271. package/src/lib/snapping-mode.ts +179 -0
  272. package/src/lib/stair-duplication.ts +4 -4
  273. package/src/lib/stair-levels.test.ts +146 -0
  274. package/src/lib/stair-levels.ts +177 -0
  275. package/src/lib/surface-plan-snap.test.ts +71 -0
  276. package/src/lib/surface-plan-snap.ts +256 -0
  277. package/src/lib/use-linear-display.ts +40 -0
  278. package/src/lib/world-grid-snap.ts +231 -0
  279. package/src/lib/zone-content.ts +130 -0
  280. package/src/store/use-alignment-guides.ts +21 -0
  281. package/src/store/use-direct-manipulation-feedback.ts +20 -0
  282. package/src/store/use-editor.tsx +767 -114
  283. package/src/store/use-facing-pose.ts +44 -0
  284. package/src/store/use-fence-curve-draft.ts +21 -0
  285. package/src/store/use-floorplan-draft-preview.ts +101 -0
  286. package/src/store/use-floorplan-marquee.ts +50 -0
  287. package/src/store/use-interaction-scope.test.ts +186 -0
  288. package/src/store/use-interaction-scope.ts +132 -0
  289. package/src/store/use-measurement-draft.test.ts +530 -0
  290. package/src/store/use-measurement-draft.ts +543 -0
  291. package/src/store/use-opening-guides.ts +41 -0
  292. package/src/store/use-placement-preview.ts +39 -0
  293. package/src/store/use-quick-measurement-hud.test.ts +53 -0
  294. package/src/store/use-quick-measurement-hud.ts +77 -0
  295. package/src/store/use-segment-draft-chain.ts +28 -0
  296. package/src/store/use-stair-build-preview.ts +43 -0
  297. package/src/store/use-wall-move-ghosts.ts +36 -0
  298. package/src/store/use-wall-snap-indicator.ts +34 -0
  299. package/src/components/editor/first-person/bvh-ecctrl.tsx +0 -860
  300. package/src/components/editor/preset-thumbnail-generator.tsx +0 -125
  301. package/src/components/editor-2d/renderers/floorplan-roof-layer.tsx +0 -113
  302. package/src/components/tools/building/move-building-tool.tsx +0 -157
  303. package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +0 -43
  304. package/src/components/tools/ceiling/ceiling-hole-editor.tsx +0 -49
  305. package/src/components/tools/ceiling/ceiling-tool.tsx +0 -465
  306. package/src/components/tools/ceiling/move-ceiling-tool.tsx +0 -264
  307. package/src/components/tools/column/column-tool.tsx +0 -97
  308. package/src/components/tools/column/move-column-tool.tsx +0 -105
  309. package/src/components/tools/door/door-math.ts +0 -110
  310. package/src/components/tools/door/door-tool.tsx +0 -324
  311. package/src/components/tools/door/move-door-tool.tsx +0 -412
  312. package/src/components/tools/fence/curve-fence-tool.tsx +0 -178
  313. package/src/components/tools/fence/fence-tool.tsx +0 -346
  314. package/src/components/tools/fence/move-fence-endpoint-tool.tsx +0 -441
  315. package/src/components/tools/fence/move-fence-tool.tsx +0 -302
  316. package/src/components/tools/item/item-tool.tsx +0 -26
  317. package/src/components/tools/roof/move-roof-tool.tsx +0 -364
  318. package/src/components/tools/slab/move-slab-tool.tsx +0 -182
  319. package/src/components/tools/slab/slab-boundary-editor.tsx +0 -43
  320. package/src/components/tools/slab/slab-hole-editor.tsx +0 -49
  321. package/src/components/tools/slab/slab-tool.tsx +0 -322
  322. package/src/components/tools/spawn/move-spawn-tool.tsx +0 -101
  323. package/src/components/tools/spawn/spawn-tool.tsx +0 -130
  324. package/src/components/tools/wall/curve-wall-tool.tsx +0 -178
  325. package/src/components/tools/wall/move-wall-endpoint-tool.tsx +0 -426
  326. package/src/components/tools/wall/move-wall-tool.tsx +0 -358
  327. package/src/components/tools/wall/wall-tool.tsx +0 -332
  328. package/src/components/tools/window/move-window-tool.tsx +0 -447
  329. package/src/components/tools/window/window-math.ts +0 -117
  330. package/src/components/tools/window/window-tool.tsx +0 -332
  331. package/src/components/ui/helpers/ceiling-helper.tsx +0 -20
  332. package/src/components/ui/helpers/slab-helper.tsx +0 -20
  333. package/src/components/ui/helpers/wall-helper.tsx +0 -20
  334. package/src/components/ui/panels/ceiling-panel.tsx +0 -249
  335. package/src/components/ui/panels/column-panel.tsx +0 -759
  336. package/src/components/ui/panels/door-panel.tsx +0 -1299
  337. package/src/components/ui/panels/fence-panel.tsx +0 -222
  338. package/src/components/ui/panels/item-panel.tsx +0 -306
  339. package/src/components/ui/panels/paint-panel.tsx +0 -163
  340. package/src/components/ui/panels/presets/presets-popover.tsx +0 -511
  341. package/src/components/ui/panels/roof-panel.tsx +0 -280
  342. package/src/components/ui/panels/roof-segment-panel.tsx +0 -311
  343. package/src/components/ui/panels/slab-panel.tsx +0 -250
  344. package/src/components/ui/panels/spawn-panel.tsx +0 -161
  345. package/src/components/ui/panels/stair-panel.tsx +0 -577
  346. package/src/components/ui/panels/stair-segment-panel.tsx +0 -325
  347. package/src/components/ui/panels/wall-panel.tsx +0 -273
  348. package/src/components/ui/panels/window-panel.tsx +0 -968
  349. package/src/contexts/presets-context.tsx +0 -121
  350. package/src/hooks/use-contextual-tools.ts +0 -61
@@ -0,0 +1,152 @@
1
+ 'use client'
2
+
3
+ import {
4
+ type AnyNodeId,
5
+ generateSceneMaterialId,
6
+ type SceneMaterialId,
7
+ toSceneMaterialRef,
8
+ useScene,
9
+ } from '@pascal-app/core'
10
+ import { useViewer } from '@pascal-app/viewer'
11
+ import { Eraser, Plus, RotateCcw } from 'lucide-react'
12
+ import { useEffect, useState } from 'react'
13
+ import {
14
+ buildResetSurfaceMaterialUpdates,
15
+ resolvePaintTargetFromSelection,
16
+ } from './../../../lib/material-paint'
17
+ import useEditor from './../../../store/use-editor'
18
+ import { Button } from '../primitives/button'
19
+ import { Tooltip, TooltipContent, TooltipTrigger } from '../primitives/tooltip'
20
+ import { MaterialPicker } from './material-picker'
21
+ import { SceneMaterialList } from './scene-material-list'
22
+
23
+ /**
24
+ * Material picker for paint mode. Embedders render this wherever paint controls
25
+ * belong (the community editor places it in the Build sidebar while paint mode
26
+ * is active). It fills its container's height and lays out as three bands: a
27
+ * fixed control/category header, a single scrolling catalog grid, and a fixed
28
+ * scene-material footer (always visible, with a `+` to add a custom material).
29
+ */
30
+ export function MaterialPaintPanel() {
31
+ const activePaintMaterial = useEditor((state) => state.activePaintMaterial)
32
+ const activePaintTarget = useEditor((state) => state.activePaintTarget)
33
+ const setActivePaintMaterial = useEditor((state) => state.setActivePaintMaterial)
34
+ const setActivePaintTarget = useEditor((state) => state.setActivePaintTarget)
35
+ const paintEraser = useEditor((state) => state.paintEraser)
36
+ const setPaintEraser = useEditor((state) => state.setPaintEraser)
37
+ // Id of a just-created scene material whose inline editor should open on mount.
38
+ const [autoEditMaterialId, setAutoEditMaterialId] = useState<SceneMaterialId | null>(null)
39
+ const selectedIds = useViewer((state) => state.selection.selectedIds)
40
+ const nodes = useScene((state) => state.nodes)
41
+ const materialCount = useScene((state) => Object.keys(state.materials).length)
42
+ const selectedId = selectedIds.length === 1 ? (selectedIds[0] ?? null) : null
43
+ const selectedNode = selectedId ? nodes[selectedId as AnyNodeId] : null
44
+ const canResetSelection =
45
+ selectedNode != null && resolvePaintTargetFromSelection({ nodes, selectedId }) != null
46
+
47
+ useEffect(() => {
48
+ const selectedPaintTarget = resolvePaintTargetFromSelection({ nodes, selectedId })
49
+ if (selectedPaintTarget) {
50
+ setActivePaintTarget(selectedPaintTarget)
51
+ }
52
+ }, [nodes, selectedId, setActivePaintTarget])
53
+
54
+ const resetSelection = () => {
55
+ if (!selectedNode) return
56
+ useScene.getState().updateNodes(buildResetSurfaceMaterialUpdates(nodes, selectedNode))
57
+ }
58
+
59
+ // Create a blank custom scene material, select it as the brush (`scene:` ref so
60
+ // edits propagate), and open its inline editor. Available from any category.
61
+ const createCustomMaterial = () => {
62
+ const id = generateSceneMaterialId()
63
+ const count = Object.keys(useScene.getState().materials).length
64
+ useScene.getState().addSceneMaterial({
65
+ id,
66
+ name: `Material ${count + 1}`,
67
+ material: {
68
+ preset: 'custom',
69
+ properties: {
70
+ color: '#ffffff',
71
+ roughness: 0.5,
72
+ metalness: 0,
73
+ opacity: 1,
74
+ transparent: false,
75
+ side: 'front',
76
+ },
77
+ },
78
+ })
79
+ setActivePaintMaterial({ materialPreset: toSceneMaterialRef(id), sourceTarget: activePaintTarget })
80
+ setAutoEditMaterialId(id)
81
+ }
82
+
83
+ return (
84
+ <div className="flex h-full min-h-0 w-full flex-col">
85
+ {/* Fixed: eraser / reset. */}
86
+ <div className="flex shrink-0 items-center gap-2 pb-2">
87
+ <Button
88
+ aria-pressed={paintEraser}
89
+ className="flex-1"
90
+ onClick={() => setPaintEraser(!paintEraser)}
91
+ size="sm"
92
+ variant={paintEraser ? 'default' : 'outline'}
93
+ >
94
+ <Eraser />
95
+ Erase
96
+ </Button>
97
+ <Button
98
+ className="flex-1"
99
+ disabled={!canResetSelection}
100
+ onClick={resetSelection}
101
+ size="sm"
102
+ variant="outline"
103
+ >
104
+ <RotateCcw />
105
+ Reset all
106
+ </Button>
107
+ </div>
108
+
109
+ {/* Scrolls: category tabs (fixed inside) + catalog grid (the scroll). */}
110
+ <div className="min-h-0 flex-1">
111
+ <MaterialPicker
112
+ onSelectMaterialPreset={(materialPreset) => {
113
+ setActivePaintMaterial({ materialPreset, sourceTarget: activePaintTarget })
114
+ }}
115
+ selectedMaterialPreset={activePaintMaterial?.materialPreset}
116
+ />
117
+ </div>
118
+
119
+ {/* Fixed footer: scene materials, always visible, with a `+` to add one. */}
120
+ <div className="mt-2 shrink-0 space-y-1.5 border-border/60 border-t pt-2">
121
+ <div className="flex items-center justify-between">
122
+ <span className="font-medium text-muted-foreground text-xs uppercase tracking-[0.12em]">
123
+ Scene materials
124
+ </span>
125
+ <Tooltip>
126
+ <TooltipTrigger asChild>
127
+ <Button
128
+ aria-label="Add material"
129
+ onClick={createCustomMaterial}
130
+ size="icon-sm"
131
+ type="button"
132
+ variant="outline"
133
+ >
134
+ <Plus />
135
+ </Button>
136
+ </TooltipTrigger>
137
+ <TooltipContent>Add material</TooltipContent>
138
+ </Tooltip>
139
+ </div>
140
+ <div className="subtle-scrollbar max-h-56 overflow-y-auto">
141
+ {materialCount > 0 ? (
142
+ <SceneMaterialList autoEditId={autoEditMaterialId} />
143
+ ) : (
144
+ <p className="px-0.5 py-1 text-muted-foreground text-xs">
145
+ No custom materials yet — add one with +.
146
+ </p>
147
+ )}
148
+ </div>
149
+ </div>
150
+ </div>
151
+ )
152
+ }
@@ -4,214 +4,124 @@ import {
4
4
  getCatalogMaterialById,
5
5
  getLibraryMaterialIdFromRef,
6
6
  getMaterialsForCategory,
7
- getMaterialsForTarget,
8
7
  MATERIAL_CATEGORIES,
9
- type MaterialSchema,
10
8
  type MaterialTarget,
11
9
  toLibraryMaterialRef,
12
10
  } from '@pascal-app/core'
13
- import { useEffect, useRef, useState } from 'react'
14
- import useEditor from '../../../store/use-editor'
11
+ import { useEffect, useState } from 'react'
12
+ import { triggerSFX } from '../../../lib/sfx-bus'
15
13
 
16
14
  type MaterialPickerProps = {
17
- value?: MaterialSchema
18
15
  selectedMaterialPreset?: string
19
- onChange?: (material: MaterialSchema) => void
20
16
  onSelectMaterialPreset?: (materialPreset: string) => void
21
17
  disabled?: boolean
22
18
  nodeType?: MaterialTarget
23
19
  hideSideControl?: boolean
24
20
  }
25
21
 
22
+ function getCategoryLabel(category: (typeof MATERIAL_CATEGORIES)[number]) {
23
+ return category.charAt(0).toUpperCase() + category.slice(1)
24
+ }
25
+
26
+ /**
27
+ * Catalog material picker: a fixed row of category tabs over a scrollable grid
28
+ * of swatches. Custom-material creation lives in the scene-material section
29
+ * (the host's `+` action), not here, so it's available from any category.
30
+ */
26
31
  export function MaterialPicker({
27
- value,
28
32
  selectedMaterialPreset,
29
- onChange,
30
33
  onSelectMaterialPreset,
31
34
  disabled = false,
32
35
  }: MaterialPickerProps) {
33
- const setPaintPanelOpen = useEditor((state) => state.setPaintPanelOpen)
34
- const [showCustom, setShowCustom] = useState<boolean>(!!value?.properties)
35
36
  const [selectedCategory, setSelectedCategory] = useState<(typeof MATERIAL_CATEGORIES)[number]>(
36
37
  MATERIAL_CATEGORIES[0],
37
38
  )
38
- const catalogScrollRef = useRef<HTMLDivElement>(null)
39
- const categoryScrollRef = useRef<HTMLDivElement>(null)
40
- const catalogItems =
41
- selectedCategory === 'other'
42
- ? getMaterialsForCategory('other')
43
- : getMaterialsForCategory(selectedCategory)
44
-
45
- useEffect(() => {
46
- setShowCustom(!!value?.properties && !selectedMaterialPreset)
47
- }, [selectedMaterialPreset, value?.properties])
39
+ const availableCategories = MATERIAL_CATEGORIES.filter(
40
+ (category) => getMaterialsForCategory(category).length > 0,
41
+ )
42
+ const catalogItems = getMaterialsForCategory(selectedCategory)
48
43
 
44
+ // Keep the visible category in sync with the externally-selected catalog
45
+ // material (a `scene:` ref matches no catalog entry, so the tab stays put).
49
46
  useEffect(() => {
50
- if (!selectedMaterialPreset && value?.properties) {
51
- setSelectedCategory('other')
52
- return
53
- }
54
-
55
- const catalogId = getLibraryMaterialIdFromRef(selectedMaterialPreset) ?? value?.id ?? undefined
56
- const selectedCatalogEntry = getCatalogMaterialById(catalogId)
57
- if (selectedCatalogEntry?.category) {
58
- setSelectedCategory(selectedCatalogEntry.category)
59
- }
60
- }, [selectedMaterialPreset, value?.id])
61
-
62
- const selectedCatalogId =
63
- selectedMaterialPreset ?? (value?.id ? toLibraryMaterialRef(value.id) : undefined)
47
+ const catalogId = getLibraryMaterialIdFromRef(selectedMaterialPreset) ?? undefined
48
+ const entry = getCatalogMaterialById(catalogId)
49
+ if (entry?.category) setSelectedCategory(entry.category)
50
+ }, [selectedMaterialPreset])
64
51
 
65
52
  const handleCatalogSelect = (materialId: string) => {
66
53
  if (disabled) return
67
- setShowCustom(false)
68
- setPaintPanelOpen(false)
69
54
  onSelectMaterialPreset?.(toLibraryMaterialRef(materialId))
70
55
  }
71
56
 
72
- useEffect(() => {
73
- const container = catalogScrollRef.current
74
- if (!container) return
75
-
76
- const handleWheel = (event: WheelEvent) => {
77
- const deltaX = event.deltaX
78
- const deltaY = event.deltaY
79
- const nextScrollLeft = container.scrollLeft + deltaX + deltaY
80
-
81
- if (nextScrollLeft === container.scrollLeft) return
82
-
83
- event.preventDefault()
84
- container.scrollLeft = nextScrollLeft
85
- }
86
-
87
- container.addEventListener('wheel', handleWheel, { passive: false })
88
- return () => {
89
- container.removeEventListener('wheel', handleWheel)
90
- }
91
- }, [catalogItems.length, onChange, showCustom])
92
-
93
- useEffect(() => {
94
- const container = categoryScrollRef.current
95
- if (!container) return
96
-
97
- const handleWheel = (event: WheelEvent) => {
98
- const deltaX = event.deltaX
99
- const deltaY = event.deltaY
100
- const nextScrollLeft = container.scrollLeft + deltaX + deltaY
101
-
102
- if (nextScrollLeft === container.scrollLeft) return
103
-
104
- event.preventDefault()
105
- container.scrollLeft = nextScrollLeft
106
- }
107
-
108
- container.addEventListener('wheel', handleWheel, { passive: false })
109
- return () => {
110
- container.removeEventListener('wheel', handleWheel)
111
- }
112
- }, [])
113
-
114
- const handleCustomOpen = () => {
115
- if (disabled) return
116
- setShowCustom(true)
117
- setPaintPanelOpen(true)
118
- onChange?.({
119
- preset: 'custom',
120
- properties: {
121
- color: value?.properties?.color || '#ffffff',
122
- roughness: value?.properties?.roughness ?? 0.5,
123
- metalness: value?.properties?.metalness ?? 0,
124
- opacity: value?.properties?.opacity ?? 1,
125
- transparent: value?.properties?.transparent ?? false,
126
- side: value?.properties?.side ?? 'front',
127
- },
128
- })
129
- }
130
-
131
57
  return (
132
- <div className={`min-w-0 space-y-3 ${disabled ? 'pointer-events-none opacity-50' : ''}`}>
133
- {(catalogItems.length > 0 || onChange) && (
134
- <div className="min-w-0 space-y-1">
135
- <div
136
- className="w-full max-w-full overflow-x-auto overflow-y-hidden"
137
- ref={categoryScrollRef}
138
- style={{ msOverflowStyle: 'none', scrollbarWidth: 'none' }}
139
- >
140
- <div className="flex min-w-max gap-1 pb-1">
141
- {MATERIAL_CATEGORIES.map((category) => (
142
- <button
143
- className={`shrink-0 px-2 font-medium text-[11px] uppercase tracking-[0.12em] transition-all ${
144
- selectedCategory === category
145
- ? 'bg-transparent text-foreground'
146
- : 'bg-transparent text-muted-foreground opacity-70 hover:text-foreground hover:opacity-100'
147
- }`}
148
- key={category}
149
- onClick={() => {
150
- setSelectedCategory(category)
151
- if (showCustom) {
152
- setShowCustom(false)
153
- }
154
- if (category !== 'other') {
155
- setPaintPanelOpen(false)
156
- }
157
- }}
158
- type="button"
159
- >
160
- {category.charAt(0).toUpperCase() + category.slice(1)}
161
- </button>
162
- ))}
163
- </div>
164
- </div>
165
- <div
166
- className="w-full max-w-full overflow-x-auto overflow-y-hidden"
167
- ref={catalogScrollRef}
168
- style={{ msOverflowStyle: 'none', scrollbarWidth: 'none' }}
58
+ <div
59
+ className={`flex h-full min-h-0 flex-col gap-2 ${disabled ? 'pointer-events-none opacity-50' : ''}`}
60
+ >
61
+ {/* Fixed category tabs — outside the scroll region. */}
62
+ <div className="flex shrink-0 flex-wrap gap-1">
63
+ {availableCategories.map((category) => (
64
+ <button
65
+ className={`rounded-full px-3 py-1 font-medium text-xs transition-colors ${
66
+ selectedCategory === category
67
+ ? 'bg-foreground text-background'
68
+ : 'text-muted-foreground hover:text-foreground'
69
+ }`}
70
+ key={category}
71
+ onClick={() => {
72
+ setSelectedCategory(category)
73
+ // Auto-select the first material in the category so the brush is
74
+ // immediately ready (and the swatch shows as selected).
75
+ const first = getMaterialsForCategory(category)[0]
76
+ if (first) handleCatalogSelect(first.id)
77
+ }}
78
+ type="button"
169
79
  >
170
- <div className="flex min-w-max gap-1.5 pb-1">
171
- {catalogItems.map((item) => (
172
- <button
173
- className={`relative h-14 w-14 shrink-0 overflow-hidden rounded-lg border transition-all ${
174
- selectedCatalogId === toLibraryMaterialRef(item.id)
175
- ? 'border-blue-500 ring-2 ring-blue-500/30'
176
- : 'border-gray-300 hover:border-gray-400'
177
- }`}
178
- key={item.id}
179
- onClick={() => handleCatalogSelect(item.id)}
180
- title={item.label}
181
- type="button"
182
- >
183
- <div className="pointer-events-none absolute inset-0 rounded-[inherit] ring-1 ring-white/12 ring-inset" />
184
- {item.previewThumbnailUrl ? (
185
- <img
186
- alt={item.label}
187
- className="h-full w-full object-cover"
188
- src={item.previewThumbnailUrl}
189
- />
190
- ) : item.previewColor ? (
191
- <div className="h-full w-full" style={{ backgroundColor: item.previewColor }} />
192
- ) : (
193
- <div className="h-full w-full bg-gray-100" />
194
- )}
195
- </button>
196
- ))}
197
- {selectedCategory === 'other' && onChange ? (
198
- <button
199
- className={`flex h-14 w-14 shrink-0 items-center justify-center rounded-lg border font-medium text-[10px] transition-all ${
200
- showCustom
201
- ? 'border-blue-500 ring-2 ring-blue-500/30'
202
- : 'border-gray-300 hover:border-gray-400'
203
- }`}
204
- onClick={handleCustomOpen}
205
- title="Custom"
206
- type="button"
207
- >
208
- Custom
209
- </button>
210
- ) : null}
211
- </div>
212
- </div>
213
- </div>
214
- )}
80
+ {getCategoryLabel(category)}
81
+ </button>
82
+ ))}
83
+ </div>
84
+ {/* The only scrolling region. */}
85
+ <div
86
+ className="subtle-scrollbar grid min-h-0 flex-1 auto-rows-min gap-2 overflow-y-auto pb-1"
87
+ style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(72px, 1fr))' }}
88
+ >
89
+ {catalogItems.map((item) => {
90
+ const isSelected = selectedMaterialPreset === toLibraryMaterialRef(item.id)
91
+ return (
92
+ <button
93
+ className={`group relative flex flex-col gap-1.5 rounded-xl p-1.5 transition-colors hover:cursor-pointer hover:bg-sidebar-accent ${
94
+ isSelected ? 'bg-sidebar-accent ring-1 ring-primary ring-inset' : ''
95
+ }`}
96
+ key={item.id}
97
+ onClick={() => {
98
+ triggerSFX('sfx:menu-click')
99
+ handleCatalogSelect(item.id)
100
+ }}
101
+ onMouseEnter={() => triggerSFX('sfx:menu-hover')}
102
+ type="button"
103
+ >
104
+ <div className="relative aspect-square w-full overflow-hidden rounded-lg">
105
+ {item.previewThumbnailUrl ? (
106
+ <img
107
+ alt={item.label}
108
+ className="h-full w-full object-cover"
109
+ src={item.previewThumbnailUrl}
110
+ />
111
+ ) : (
112
+ <div
113
+ className="h-full w-full"
114
+ style={{ backgroundColor: item.previewColor ?? '#f3f4f6' }}
115
+ />
116
+ )}
117
+ </div>
118
+ <span className="truncate px-0.5 text-left font-medium text-[11px] text-muted-foreground group-hover:text-foreground">
119
+ {item.label}
120
+ </span>
121
+ </button>
122
+ )
123
+ })}
124
+ </div>
215
125
  </div>
216
126
  )
217
127
  }
@@ -0,0 +1,108 @@
1
+ 'use client'
2
+
3
+ import type { MaterialProperties, MaterialSchema } from '@pascal-app/core'
4
+ import { Input } from '../primitives/input'
5
+ import { SliderControl } from './slider-control'
6
+
7
+ const DEFAULT_MATERIAL_PROPERTIES: MaterialProperties = {
8
+ color: '#ffffff',
9
+ roughness: 0.5,
10
+ metalness: 0,
11
+ opacity: 1,
12
+ transparent: false,
13
+ side: 'front',
14
+ }
15
+
16
+ export function MaterialPropertiesEditor({
17
+ value,
18
+ onChange,
19
+ }: {
20
+ value: MaterialSchema
21
+ onChange: (next: MaterialSchema) => void
22
+ }) {
23
+ const currentProps = value.properties ?? DEFAULT_MATERIAL_PROPERTIES
24
+
25
+ const updateMaterial = (
26
+ updates: Partial<MaterialProperties>,
27
+ nextTransparent = currentProps.transparent,
28
+ ) => {
29
+ onChange({
30
+ ...value,
31
+ preset: value.preset ?? 'custom',
32
+ properties: {
33
+ ...currentProps,
34
+ ...updates,
35
+ transparent: nextTransparent,
36
+ },
37
+ })
38
+ }
39
+
40
+ return (
41
+ <div className="space-y-3">
42
+ <div className="space-y-2">
43
+ <label className="block font-medium text-muted-foreground text-xs uppercase tracking-[0.12em]">
44
+ Color
45
+ </label>
46
+ <div className="flex items-center gap-2">
47
+ <input
48
+ className="h-9 w-9 shrink-0 cursor-pointer rounded-md border border-input bg-transparent p-0 [&::-moz-color-swatch]:rounded-[5px] [&::-moz-color-swatch]:border-none [&::-webkit-color-swatch-wrapper]:p-0 [&::-webkit-color-swatch]:rounded-[5px] [&::-webkit-color-swatch]:border-none"
49
+ onChange={(e) => updateMaterial({ color: e.target.value })}
50
+ type="color"
51
+ value={currentProps.color}
52
+ />
53
+ <Input
54
+ onChange={(e) => updateMaterial({ color: e.target.value })}
55
+ value={currentProps.color}
56
+ />
57
+ </div>
58
+ </div>
59
+
60
+ <SliderControl
61
+ label="Roughness"
62
+ max={1}
63
+ min={0}
64
+ onChange={(value) => updateMaterial({ roughness: value })}
65
+ precision={2}
66
+ step={0.01}
67
+ value={currentProps.roughness}
68
+ />
69
+
70
+ <SliderControl
71
+ label="Metalness"
72
+ max={1}
73
+ min={0}
74
+ onChange={(value) => updateMaterial({ metalness: value })}
75
+ precision={2}
76
+ step={0.01}
77
+ value={currentProps.metalness}
78
+ />
79
+
80
+ <SliderControl
81
+ label="Opacity"
82
+ max={1}
83
+ min={0}
84
+ onChange={(value) => updateMaterial({ opacity: value }, value < 1 || currentProps.transparent)}
85
+ precision={2}
86
+ step={0.01}
87
+ value={currentProps.opacity}
88
+ />
89
+
90
+ <div className="space-y-2">
91
+ <label className="block font-medium text-muted-foreground text-xs uppercase tracking-[0.12em]">
92
+ Side
93
+ </label>
94
+ <select
95
+ className="h-9 w-full rounded-md border border-input bg-transparent px-3 text-sm shadow-xs outline-none transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 dark:bg-input/30"
96
+ onChange={(e) =>
97
+ updateMaterial({ side: e.target.value as 'front' | 'back' | 'double' })
98
+ }
99
+ value={currentProps.side}
100
+ >
101
+ <option value="front">Front</option>
102
+ <option value="back">Back</option>
103
+ <option value="double">Double</option>
104
+ </select>
105
+ </div>
106
+ </div>
107
+ )
108
+ }