@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
@@ -1,332 +0,0 @@
1
- import {
2
- type AnyNodeId,
3
- emitter,
4
- isCurvedWall,
5
- sceneRegistry,
6
- spatialGridManager,
7
- useScene,
8
- type WallEvent,
9
- WindowNode,
10
- } from '@pascal-app/core'
11
- import { useViewer } from '@pascal-app/viewer'
12
- import { useEffect, useRef } from 'react'
13
- import { BoxGeometry, EdgesGeometry, type Group, type LineSegments } from 'three'
14
- import { LineBasicNodeMaterial } from 'three/webgpu'
15
- import { EDITOR_LAYER } from '../../../lib/constants'
16
- import { sfxEmitter } from '../../../lib/sfx-bus'
17
- import {
18
- calculateCursorRotation,
19
- calculateItemRotation,
20
- getSideFromNormal,
21
- isValidWallSideFace,
22
- snapToHalf,
23
- } from '../item/placement-math'
24
- import { clampToWall, hasWallChildOverlap, wallLocalToWorld } from './window-math'
25
-
26
- // Shared edge material — reuse across renders, just toggle color
27
- const edgeMaterial = new LineBasicNodeMaterial({
28
- color: 0xef_44_44, // red-500 default (invalid)
29
- linewidth: 3,
30
- depthTest: false,
31
- depthWrite: false,
32
- })
33
-
34
- /**
35
- * Window tool — places WindowNodes on walls only.
36
- * Shows a rectangle cursor (green = valid, red = invalid) matching window dimensions.
37
- */
38
- export const WindowTool: React.FC = () => {
39
- const draftRef = useRef<WindowNode | null>(null)
40
- const cursorGroupRef = useRef<Group>(null!)
41
- const edgesRef = useRef<LineSegments>(null!)
42
-
43
- useEffect(() => {
44
- useScene.temporal.getState().pause()
45
-
46
- const getLevelId = () => useViewer.getState().selection.levelId
47
- const getLevelYOffset = () => {
48
- const id = getLevelId()
49
- return id ? (sceneRegistry.nodes.get(id as AnyNodeId)?.position.y ?? 0) : 0
50
- }
51
- const getSlabElevation = (wallEvent: WallEvent) =>
52
- spatialGridManager.getSlabElevationForWall(
53
- wallEvent.node.parentId ?? '',
54
- wallEvent.node.start,
55
- wallEvent.node.end,
56
- )
57
-
58
- const markWallDirty = (wallId: string) => {
59
- useScene.getState().dirtyNodes.add(wallId as AnyNodeId)
60
- }
61
-
62
- const destroyDraft = () => {
63
- if (!draftRef.current) return
64
- const wallId = draftRef.current.parentId
65
- useScene.getState().deleteNode(draftRef.current.id)
66
- draftRef.current = null
67
- // Rebuild wall so it removes the cutout from the deleted draft
68
- if (wallId) markWallDirty(wallId)
69
- }
70
-
71
- const hideCursor = () => {
72
- if (cursorGroupRef.current) cursorGroupRef.current.visible = false
73
- }
74
-
75
- const updateCursor = (
76
- worldPosition: [number, number, number],
77
- cursorRotationY: number,
78
- valid: boolean,
79
- ) => {
80
- const group = cursorGroupRef.current
81
- if (!group) return
82
- group.visible = true
83
- group.position.set(...worldPosition)
84
- group.rotation.y = cursorRotationY
85
- edgeMaterial.color.setHex(valid ? 0x22_c5_5e : 0xef_44_44)
86
- }
87
-
88
- const onWallEnter = (event: WallEvent) => {
89
- if (!isValidWallSideFace(event.normal)) return
90
- if (isCurvedWall(event.node)) {
91
- destroyDraft()
92
- hideCursor()
93
- return
94
- }
95
- const levelId = getLevelId()
96
- if (!levelId) return
97
- // Only interact with walls on the current level
98
- if (event.node.parentId !== levelId) return
99
-
100
- destroyDraft()
101
-
102
- const side = getSideFromNormal(event.normal)
103
- const itemRotation = calculateItemRotation(event.normal)
104
- const cursorRotation = calculateCursorRotation(event.normal, event.node.start, event.node.end)
105
-
106
- const localX = snapToHalf(event.localPosition[0])
107
- const localY = snapToHalf(event.localPosition[1])
108
-
109
- const width = 1.5
110
- const height = 1.5
111
-
112
- const { clampedX, clampedY } = clampToWall(event.node, localX, localY, width, height)
113
-
114
- const node = WindowNode.parse({
115
- position: [clampedX, clampedY, 0],
116
- rotation: [0, itemRotation, 0],
117
- side,
118
- wallId: event.node.id,
119
- parentId: event.node.id,
120
- metadata: { isTransient: true },
121
- })
122
-
123
- useScene.getState().createNode(node, event.node.id as AnyNodeId)
124
- draftRef.current = node
125
-
126
- const valid = !hasWallChildOverlap(event.node.id, clampedX, clampedY, width, height, node.id)
127
-
128
- updateCursor(
129
- wallLocalToWorld(
130
- event.node,
131
- clampedX,
132
- clampedY,
133
- getLevelYOffset(),
134
- getSlabElevation(event),
135
- ),
136
- cursorRotation,
137
- valid,
138
- )
139
- event.stopPropagation()
140
- }
141
-
142
- const onWallMove = (event: WallEvent) => {
143
- if (!isValidWallSideFace(event.normal)) return
144
- if (isCurvedWall(event.node)) {
145
- destroyDraft()
146
- hideCursor()
147
- return
148
- }
149
- // Only interact with walls on the current level
150
- if (event.node.parentId !== getLevelId()) return
151
-
152
- const side = getSideFromNormal(event.normal)
153
- const itemRotation = calculateItemRotation(event.normal)
154
- const cursorRotation = calculateCursorRotation(event.normal, event.node.start, event.node.end)
155
-
156
- const localX = snapToHalf(event.localPosition[0])
157
- const localY = snapToHalf(event.localPosition[1])
158
-
159
- const width = draftRef.current?.width ?? 1.5
160
- const height = draftRef.current?.height ?? 1.5
161
-
162
- const { clampedX, clampedY } = clampToWall(event.node, localX, localY, width, height)
163
-
164
- if (draftRef.current) {
165
- if (event.node.id !== draftRef.current.parentId) {
166
- // Wall changed without enter/leave: must updateNode to reparent
167
- useScene.getState().updateNode(draftRef.current.id, {
168
- position: [clampedX, clampedY, 0],
169
- rotation: [0, itemRotation, 0],
170
- side,
171
- parentId: event.node.id,
172
- wallId: event.node.id,
173
- })
174
- } else {
175
- // Same wall: update Three.js mesh directly to avoid store churn
176
- const draftMesh = sceneRegistry.nodes.get(draftRef.current.id as AnyNodeId)
177
- if (draftMesh) {
178
- draftMesh.position.set(clampedX, clampedY, 0)
179
- draftMesh.rotation.set(0, itemRotation, 0)
180
- draftMesh.updateMatrixWorld(true)
181
- }
182
- markWallDirty(event.node.id)
183
- }
184
- }
185
-
186
- const valid = !hasWallChildOverlap(
187
- event.node.id,
188
- clampedX,
189
- clampedY,
190
- width,
191
- height,
192
- draftRef.current?.id,
193
- )
194
-
195
- updateCursor(
196
- wallLocalToWorld(
197
- event.node,
198
- clampedX,
199
- clampedY,
200
- getLevelYOffset(),
201
- getSlabElevation(event),
202
- ),
203
- cursorRotation,
204
- valid,
205
- )
206
- event.stopPropagation()
207
- }
208
-
209
- const onWallClick = (event: WallEvent) => {
210
- if (!draftRef.current) return
211
- if (!isValidWallSideFace(event.normal)) return
212
- if (isCurvedWall(event.node)) return
213
- // Only interact with walls on the current level
214
- if (event.node.parentId !== getLevelId()) return
215
-
216
- const side = getSideFromNormal(event.normal)
217
- const itemRotation = calculateItemRotation(event.normal)
218
-
219
- const localX = snapToHalf(event.localPosition[0])
220
- const localY = snapToHalf(event.localPosition[1])
221
- const { clampedX, clampedY } = clampToWall(
222
- event.node,
223
- localX,
224
- localY,
225
- draftRef.current.width,
226
- draftRef.current.height,
227
- )
228
- const valid = !hasWallChildOverlap(
229
- event.node.id,
230
- clampedX,
231
- clampedY,
232
- draftRef.current.width,
233
- draftRef.current.height,
234
- draftRef.current.id,
235
- )
236
- if (!valid) return
237
-
238
- const draft = draftRef.current
239
- draftRef.current = null
240
-
241
- // Delete transient draft (paused, invisible to undo)
242
- useScene.getState().deleteNode(draft.id)
243
-
244
- // Resume → create permanent node (single undoable action)
245
- useScene.temporal.getState().resume()
246
-
247
- const levelId = getLevelId()
248
- const state = useScene.getState()
249
- const windowCount = Object.values(state.nodes).filter((n) => {
250
- if (n.type !== 'window') return false
251
- const wall = n.parentId ? state.nodes[n.parentId as AnyNodeId] : undefined
252
- return wall?.parentId === levelId
253
- }).length
254
- const name = `Window ${windowCount + 1}`
255
-
256
- const node = WindowNode.parse({
257
- name,
258
- position: [clampedX, clampedY, 0],
259
- rotation: [0, itemRotation, 0],
260
- side,
261
- wallId: event.node.id,
262
- parentId: event.node.id,
263
- width: draft.width,
264
- height: draft.height,
265
- windowType: draft.windowType,
266
- operationState: draft.operationState,
267
- awningDirection: draft.awningDirection,
268
- casementStyle: draft.casementStyle,
269
- hingesSide: draft.hingesSide,
270
- frameThickness: draft.frameThickness,
271
- frameDepth: draft.frameDepth,
272
- columnRatios: draft.columnRatios,
273
- rowRatios: draft.rowRatios,
274
- columnDividerThickness: draft.columnDividerThickness,
275
- rowDividerThickness: draft.rowDividerThickness,
276
- sill: draft.sill,
277
- sillDepth: draft.sillDepth,
278
- sillThickness: draft.sillThickness,
279
- })
280
-
281
- useScene.getState().createNode(node, event.node.id as AnyNodeId)
282
- useViewer.getState().setSelection({ selectedIds: [node.id] })
283
- useScene.temporal.getState().pause()
284
- sfxEmitter.emit('sfx:item-place')
285
-
286
- event.stopPropagation()
287
- }
288
-
289
- const onWallLeave = () => {
290
- destroyDraft()
291
- hideCursor()
292
- }
293
-
294
- const onCancel = () => {
295
- destroyDraft()
296
- hideCursor()
297
- }
298
-
299
- emitter.on('wall:enter', onWallEnter)
300
- emitter.on('wall:move', onWallMove)
301
- emitter.on('wall:click', onWallClick)
302
- emitter.on('wall:leave', onWallLeave)
303
- emitter.on('tool:cancel', onCancel)
304
-
305
- return () => {
306
- destroyDraft()
307
- hideCursor()
308
- useScene.temporal.getState().resume()
309
- emitter.off('wall:enter', onWallEnter)
310
- emitter.off('wall:move', onWallMove)
311
- emitter.off('wall:click', onWallClick)
312
- emitter.off('wall:leave', onWallLeave)
313
- emitter.off('tool:cancel', onCancel)
314
- }
315
- }, [])
316
-
317
- // Cursor geometry: window outline rectangle (width × height × frameDepth)
318
- const boxGeo = new BoxGeometry(1.5, 1.5, 0.07)
319
- const edgesGeo = new EdgesGeometry(boxGeo)
320
- boxGeo.dispose()
321
-
322
- return (
323
- <group ref={cursorGroupRef} visible={false}>
324
- <lineSegments
325
- geometry={edgesGeo}
326
- layers={EDITOR_LAYER}
327
- material={edgeMaterial}
328
- ref={edgesRef}
329
- />
330
- </group>
331
- )
332
- }
@@ -1,20 +0,0 @@
1
- import { ShortcutToken } from '../primitives/shortcut-token'
2
-
3
- export function CeilingHelper() {
4
- return (
5
- <div className="pointer-events-none fixed top-1/2 right-4 z-40 flex -translate-y-1/2 flex-col gap-2 rounded-lg border border-border bg-background/95 px-4 py-3 shadow-lg backdrop-blur-md">
6
- <div className="flex items-center gap-2 text-sm">
7
- <ShortcutToken value="Left click" />
8
- <span className="text-muted-foreground">Add point</span>
9
- </div>
10
- <div className="flex items-center gap-2 text-sm">
11
- <ShortcutToken value="Shift" />
12
- <span className="text-muted-foreground">Allow non-45° angles</span>
13
- </div>
14
- <div className="flex items-center gap-2 text-sm">
15
- <ShortcutToken value="Esc" />
16
- <span className="text-muted-foreground">Cancel</span>
17
- </div>
18
- </div>
19
- )
20
- }
@@ -1,20 +0,0 @@
1
- import { ShortcutToken } from '../primitives/shortcut-token'
2
-
3
- export function SlabHelper() {
4
- return (
5
- <div className="pointer-events-none fixed top-1/2 right-4 z-40 flex -translate-y-1/2 flex-col gap-2 rounded-lg border border-border bg-background/95 px-4 py-3 shadow-lg backdrop-blur-md">
6
- <div className="flex items-center gap-2 text-sm">
7
- <ShortcutToken value="Left click" />
8
- <span className="text-muted-foreground">Add point</span>
9
- </div>
10
- <div className="flex items-center gap-2 text-sm">
11
- <ShortcutToken value="Shift" />
12
- <span className="text-muted-foreground">Allow non-45° angles</span>
13
- </div>
14
- <div className="flex items-center gap-2 text-sm">
15
- <ShortcutToken value="Esc" />
16
- <span className="text-muted-foreground">Cancel</span>
17
- </div>
18
- </div>
19
- )
20
- }
@@ -1,20 +0,0 @@
1
- import { ShortcutToken } from '../primitives/shortcut-token'
2
-
3
- export function WallHelper() {
4
- return (
5
- <div className="pointer-events-none fixed top-1/2 right-4 z-40 flex -translate-y-1/2 flex-col gap-2 rounded-lg border border-border bg-background/95 px-4 py-3 shadow-lg backdrop-blur-md">
6
- <div className="flex items-center gap-2 text-sm">
7
- <ShortcutToken value="Left click" />
8
- <span className="text-muted-foreground">Set wall start / end</span>
9
- </div>
10
- <div className="flex items-center gap-2 text-sm">
11
- <ShortcutToken value="Shift" />
12
- <span className="text-muted-foreground">Allow non-45° angles</span>
13
- </div>
14
- <div className="flex items-center gap-2 text-sm">
15
- <ShortcutToken value="Esc" />
16
- <span className="text-muted-foreground">Cancel</span>
17
- </div>
18
- </div>
19
- )
20
- }
@@ -1,249 +0,0 @@
1
- 'use client'
2
-
3
- import { type AnyNode, type CeilingNode, useScene } from '@pascal-app/core'
4
- import { useViewer } from '@pascal-app/viewer'
5
- import { Edit, Move, Plus, Trash2 } from 'lucide-react'
6
- import { useCallback, useEffect } from 'react'
7
- import { sfxEmitter } from '../../../lib/sfx-bus'
8
- import useEditor from '../../../store/use-editor'
9
- import { ActionButton, ActionGroup } from '../controls/action-button'
10
- import { PanelSection } from '../controls/panel-section'
11
- import { SliderControl } from '../controls/slider-control'
12
- import { PanelWrapper } from './panel-wrapper'
13
-
14
- export function CeilingPanel() {
15
- const selectedId = useViewer((s) => s.selection.selectedIds[0])
16
- const setSelection = useViewer((s) => s.setSelection)
17
- const updateNode = useScene((s) => s.updateNode)
18
- const editingHole = useEditor((s) => s.editingHole)
19
- const setEditingHole = useEditor((s) => s.setEditingHole)
20
- const setMovingNode = useEditor((s) => s.setMovingNode)
21
-
22
- const node = useScene((s) =>
23
- selectedId ? (s.nodes[selectedId as AnyNode['id']] as CeilingNode | undefined) : undefined,
24
- )
25
-
26
- const handleUpdate = useCallback(
27
- (updates: Partial<CeilingNode>) => {
28
- if (!selectedId) return
29
- updateNode(selectedId as AnyNode['id'], updates)
30
- },
31
- [selectedId, updateNode],
32
- )
33
-
34
- const handleClose = useCallback(() => {
35
- setSelection({ selectedIds: [] })
36
- setEditingHole(null)
37
- }, [setSelection, setEditingHole])
38
-
39
- useEffect(() => {
40
- if (!node) {
41
- setEditingHole(null)
42
- }
43
- }, [node, setEditingHole])
44
-
45
- useEffect(() => {
46
- return () => {
47
- setEditingHole(null)
48
- }
49
- }, [setEditingHole])
50
-
51
- const handleAddHole = useCallback(() => {
52
- if (!(node && selectedId)) return
53
-
54
- const polygon = node.polygon
55
- let cx = 0
56
- let cz = 0
57
- for (const [x, z] of polygon) {
58
- cx += x
59
- cz += z
60
- }
61
- cx /= polygon.length
62
- cz /= polygon.length
63
-
64
- const holeSize = 0.5
65
- const newHole: Array<[number, number]> = [
66
- [cx - holeSize, cz - holeSize],
67
- [cx + holeSize, cz - holeSize],
68
- [cx + holeSize, cz + holeSize],
69
- [cx - holeSize, cz + holeSize],
70
- ]
71
- const currentHoles = node?.holes || []
72
- const currentMetadata = currentHoles.map(
73
- (_, index) => node?.holeMetadata?.[index] ?? { source: 'manual' as const },
74
- )
75
- handleUpdate({
76
- holes: [...currentHoles, newHole],
77
- holeMetadata: [...currentMetadata, { source: 'manual' }],
78
- })
79
- setEditingHole({ nodeId: selectedId, holeIndex: currentHoles.length })
80
- }, [node, selectedId, handleUpdate, setEditingHole])
81
-
82
- const handleEditHole = useCallback(
83
- (index: number) => {
84
- if (!selectedId) return
85
- setEditingHole({ nodeId: selectedId, holeIndex: index })
86
- },
87
- [selectedId, setEditingHole],
88
- )
89
-
90
- const handleDeleteHole = useCallback(
91
- (index: number) => {
92
- if (!selectedId) return
93
- const currentHoles = node?.holes || []
94
- if (node?.holeMetadata?.[index]?.source === 'stair') return
95
- const newHoles = currentHoles.filter((_, i) => i !== index)
96
- const currentMetadata = currentHoles.map(
97
- (_, metadataIndex) => node?.holeMetadata?.[metadataIndex] ?? { source: 'manual' as const },
98
- )
99
- const newMetadata = currentMetadata.filter((_, i) => i !== index)
100
- handleUpdate({ holes: newHoles, holeMetadata: newMetadata })
101
- if (editingHole?.nodeId === selectedId && editingHole?.holeIndex === index) {
102
- setEditingHole(null)
103
- }
104
- },
105
- [selectedId, node?.holes, node?.holeMetadata, handleUpdate, editingHole, setEditingHole],
106
- )
107
-
108
- const handleMove = useCallback(() => {
109
- if (!node) return
110
- sfxEmitter.emit('sfx:item-pick')
111
- setMovingNode(node)
112
- setSelection({ selectedIds: [] })
113
- }, [node, setMovingNode, setSelection])
114
-
115
- if (!(node && node.type === 'ceiling' && selectedId)) return null
116
-
117
- const calculateArea = (polygon: Array<[number, number]>): number => {
118
- if (polygon.length < 3) return 0
119
- let area = 0
120
- const n = polygon.length
121
- for (let i = 0; i < n; i++) {
122
- const j = (i + 1) % n
123
- const current = polygon[i]!
124
- const next = polygon[j]!
125
- area += current[0] * next[1]
126
- area -= next[0] * current[1]
127
- }
128
- return Math.abs(area) / 2
129
- }
130
-
131
- const area = calculateArea(node.polygon)
132
-
133
- return (
134
- <PanelWrapper
135
- icon="/icons/ceiling.png"
136
- onClose={handleClose}
137
- title={node.name || 'Ceiling'}
138
- width={320}
139
- >
140
- <PanelSection title="Height">
141
- <SliderControl
142
- label="Height"
143
- max={6}
144
- min={0}
145
- onChange={(v) => handleUpdate({ height: v })}
146
- precision={3}
147
- step={0.01}
148
- unit="m"
149
- value={Math.round(node.height * 1000) / 1000}
150
- />
151
-
152
- <div className="mt-2 grid grid-cols-3 gap-1.5 px-1 pb-1">
153
- <ActionButton label="Low (2.4m)" onClick={() => handleUpdate({ height: 2.4 })} />
154
- <ActionButton label="Standard (2.5m)" onClick={() => handleUpdate({ height: 2.5 })} />
155
- <ActionButton label="High (3.0m)" onClick={() => handleUpdate({ height: 3.0 })} />
156
- </div>
157
- </PanelSection>
158
-
159
- <PanelSection title="Info">
160
- <div className="flex items-center justify-between px-2 py-1 text-muted-foreground text-sm">
161
- <span>Area</span>
162
- <span className="font-mono text-white">{area.toFixed(2)} m²</span>
163
- </div>
164
- </PanelSection>
165
-
166
- <PanelSection title="Holes">
167
- {node.holes && node.holes.length > 0 ? (
168
- <div className="flex flex-col gap-1 pb-2">
169
- {node.holes.map((hole, index) => {
170
- const holeArea = calculateArea(hole)
171
- const isEditing =
172
- editingHole?.nodeId === selectedId && editingHole?.holeIndex === index
173
- const source = node.holeMetadata?.[index]?.source ?? 'manual'
174
- const isAutoHole = source === 'stair'
175
- return (
176
- <div
177
- className={`flex items-center justify-between rounded-lg border p-2 transition-colors ${
178
- isEditing
179
- ? 'border-primary/50 bg-primary/10'
180
- : 'border-transparent hover:bg-accent/30'
181
- }`}
182
- key={index}
183
- >
184
- <div className="min-w-0 flex-1">
185
- <p
186
- className={`font-medium text-xs ${isEditing ? 'text-primary' : 'text-white'}`}
187
- >
188
- Hole {index + 1} {isEditing && '(Editing)'}
189
- </p>
190
- <p className="text-[10px] text-muted-foreground">
191
- {holeArea.toFixed(2)} m² · {hole.length} pts ·{' '}
192
- {isAutoHole ? 'Auto stair cutout' : 'Manual'}
193
- </p>
194
- </div>
195
- <div className="flex items-center gap-1">
196
- {isEditing ? (
197
- <ActionButton
198
- className="h-7 bg-primary text-primary-foreground hover:bg-primary/90"
199
- label="Done"
200
- onClick={() => setEditingHole(null)}
201
- />
202
- ) : isAutoHole ? (
203
- <div className="rounded-md bg-[#2C2C2E] px-2 py-1 text-[10px] text-muted-foreground">
204
- Auto
205
- </div>
206
- ) : (
207
- <>
208
- <button
209
- className="flex h-7 w-7 items-center justify-center rounded-md bg-[#2C2C2E] text-muted-foreground hover:bg-[#3e3e3e] hover:text-foreground"
210
- onClick={() => handleEditHole(index)}
211
- type="button"
212
- >
213
- <Edit className="h-3.5 w-3.5" />
214
- </button>
215
- <button
216
- className="flex h-7 w-7 items-center justify-center rounded-md bg-red-500/10 text-red-400 hover:bg-red-500/20 hover:text-red-300"
217
- onClick={() => handleDeleteHole(index)}
218
- type="button"
219
- >
220
- <Trash2 className="h-3.5 w-3.5" />
221
- </button>
222
- </>
223
- )}
224
- </div>
225
- </div>
226
- )
227
- })}
228
- </div>
229
- ) : (
230
- <div className="px-2 py-3 text-center text-muted-foreground text-xs">No holes</div>
231
- )}
232
-
233
- <div className="px-1 pt-1 pb-1">
234
- <ActionButton
235
- className="w-full"
236
- disabled={editingHole?.nodeId === selectedId}
237
- icon={<Plus className="h-3.5 w-3.5" />}
238
- label="Add Hole"
239
- onClick={handleAddHole}
240
- />
241
- </div>
242
- </PanelSection>
243
-
244
- <ActionGroup>
245
- <ActionButton icon={<Move className="h-3.5 w-3.5" />} label="Move" onClick={handleMove} />
246
- </ActionGroup>
247
- </PanelWrapper>
248
- )
249
- }