@pascal-app/editor 0.7.0 → 0.9.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 (244) hide show
  1. package/package.json +12 -13
  2. package/src/components/editor/alignment-3d-guide-layer.tsx +160 -0
  3. package/src/components/editor/custom-camera-controls.tsx +755 -25
  4. package/src/components/editor/editor-layout-mobile.tsx +3 -3
  5. package/src/components/editor/editor-layout-v2.tsx +62 -54
  6. package/src/components/editor/export-manager.tsx +32 -3
  7. package/src/components/editor/first-person/build-collider-world.test.ts +149 -0
  8. package/src/components/editor/first-person/build-collider-world.ts +203 -87
  9. package/src/components/editor/first-person/bvh-ecctrl.tsx +127 -62
  10. package/src/components/editor/first-person-controls.tsx +836 -31
  11. package/src/components/editor/floating-action-menu.tsx +287 -196
  12. package/src/components/editor/floating-building-action-menu.tsx +1 -1
  13. package/src/components/editor/floorplan-panel.tsx +5837 -12006
  14. package/src/components/editor/grid.tsx +51 -17
  15. package/src/components/editor/group-move-handle.tsx +316 -0
  16. package/src/components/editor/group-rotate-handle.tsx +382 -0
  17. package/src/components/editor/group-transform-shared.test.ts +205 -0
  18. package/src/components/editor/group-transform-shared.ts +256 -0
  19. package/src/components/editor/handles/handle-arrow.tsx +523 -0
  20. package/src/components/editor/handles/use-handle-drag.ts +213 -0
  21. package/src/components/editor/index.tsx +234 -190
  22. package/src/components/editor/measurement-pill.tsx +75 -0
  23. package/src/components/editor/node-arrow-handles.tsx +1453 -0
  24. package/src/components/editor/selection-manager.tsx +435 -158
  25. package/src/components/editor/site-edge-labels.tsx +44 -20
  26. package/src/components/editor/slab-hole-highlights.tsx +466 -0
  27. package/src/components/editor/snapshot-capture-overlay.tsx +539 -0
  28. package/src/components/editor/thumbnail-generator.tsx +27 -64
  29. package/src/components/editor/use-floorplan-background-placement.ts +122 -35
  30. package/src/components/editor/use-floorplan-hit-testing.ts +24 -0
  31. package/src/components/editor/use-floorplan-scene-data.ts +14 -2
  32. package/src/components/editor/wall-measurement-label.tsx +11 -6
  33. package/src/components/editor/wall-move-side-handles.tsx +855 -0
  34. package/src/components/editor/wall-opening-highlights.tsx +181 -0
  35. package/src/components/editor/wall-snap-beacon-layer.tsx +150 -0
  36. package/src/components/editor-2d/floorplan-action-menu-layer.tsx +6 -1
  37. package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +142 -0
  38. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +5 -3
  39. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +227 -0
  40. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +616 -0
  41. package/src/components/editor-2d/floorplan-render-context.tsx +62 -0
  42. package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +75 -0
  43. package/src/components/editor-2d/floorplan-wall-move-ghost-layer.tsx +47 -0
  44. package/src/components/editor-2d/renderers/floorplan-draft-layer.tsx +6 -1
  45. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +266 -0
  46. package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +108 -61
  47. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +59 -0
  48. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +1963 -0
  49. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +29 -14
  50. package/src/components/editor-2d/svg-paths.ts +10 -2
  51. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +30 -9
  52. package/src/components/systems/ceiling/ceiling-system.tsx +12 -8
  53. package/src/components/systems/roof/roof-edit-system.tsx +86 -12
  54. package/src/components/systems/stair/stair-edit-system.tsx +2 -2
  55. package/src/components/systems/zone/zone-label-editor-system.tsx +0 -0
  56. package/src/components/systems/zone/zone-system.tsx +0 -0
  57. package/src/components/tools/elevator/elevator-defaults.ts +8 -0
  58. package/src/components/tools/elevator/elevator-tool.tsx +349 -0
  59. package/src/components/tools/elevator/move-elevator-tool.tsx +252 -0
  60. package/src/components/tools/fence/fence-drafting.ts +64 -22
  61. package/src/components/tools/item/move-tool.tsx +40 -107
  62. package/src/components/tools/item/placement-math.test.ts +10 -0
  63. package/src/components/tools/item/placement-math.ts +33 -9
  64. package/src/components/tools/item/placement-strategies.ts +201 -25
  65. package/src/components/tools/item/placement-types.ts +10 -1
  66. package/src/components/tools/item/use-draft-node.ts +20 -2
  67. package/src/components/tools/item/use-placement-coordinator.tsx +700 -299
  68. package/src/components/tools/registry/move-registry-node-tool.tsx +602 -0
  69. package/src/components/tools/roof/roof-tool.tsx +108 -12
  70. package/src/components/tools/select/box-select-state.ts +77 -0
  71. package/src/components/tools/select/box-select-tool.tsx +324 -543
  72. package/src/components/tools/select/plane-box-select-tool.tsx +577 -0
  73. package/src/components/tools/select/screen-rectangle-selection.ts +84 -0
  74. package/src/components/tools/select/select-candidates.test.ts +109 -0
  75. package/src/components/tools/select/select-candidates.ts +85 -0
  76. package/src/components/tools/shared/affordance-dispatch.ts +30 -0
  77. package/src/components/tools/shared/drag-bounding-box.tsx +171 -0
  78. package/src/components/tools/shared/floor-stack-preview.ts +25 -0
  79. package/src/components/tools/shared/fresh-placement-visibility.ts +61 -0
  80. package/src/components/tools/shared/placement-box-geometry.ts +123 -0
  81. package/src/components/tools/shared/placement-box.tsx +123 -0
  82. package/src/components/tools/shared/polygon-editor.tsx +617 -167
  83. package/src/components/tools/shared/segment-angle.ts +57 -1
  84. package/src/components/tools/site/site-boundary-editor.tsx +464 -12
  85. package/src/components/tools/site/site-flag-model.ts +18 -0
  86. package/src/components/tools/stair/stair-defaults.ts +1 -1
  87. package/src/components/tools/stair/stair-tool.tsx +269 -35
  88. package/src/components/tools/tool-manager.tsx +172 -56
  89. package/src/components/tools/wall/wall-drafting.ts +98 -98
  90. package/src/components/tools/wall/wall-snap-geometry.test.ts +79 -0
  91. package/src/components/tools/wall/wall-snap-geometry.ts +270 -0
  92. package/src/components/tools/zone/zone-tool.tsx +36 -11
  93. package/src/components/ui/action-menu/action-button.tsx +21 -1
  94. package/src/components/ui/action-menu/camera-actions.tsx +0 -0
  95. package/src/components/ui/action-menu/control-modes.tsx +5 -76
  96. package/src/components/ui/action-menu/furnish-tools.tsx +6 -92
  97. package/src/components/ui/action-menu/index.tsx +33 -196
  98. package/src/components/ui/action-menu/structure-tools.tsx +13 -88
  99. package/src/components/ui/action-menu/view-toggles.tsx +262 -37
  100. package/src/components/ui/command-palette/editor-commands.tsx +24 -17
  101. package/src/components/ui/command-palette/index.tsx +6 -256
  102. package/src/components/ui/controls/material-paint-panel.tsx +83 -0
  103. package/src/components/ui/controls/material-picker.tsx +17 -35
  104. package/src/components/ui/controls/metric-control.tsx +33 -11
  105. package/src/components/ui/floating-level-selector.tsx +48 -11
  106. package/src/components/ui/helpers/helper-manager.tsx +21 -17
  107. package/src/components/ui/helpers/registered-tool-helper.tsx +25 -0
  108. package/src/components/ui/item-catalog/catalog-items.tsx +1743 -315
  109. package/src/components/ui/item-catalog/item-catalog.tsx +91 -46
  110. package/src/components/ui/level-duplicate-dialog.tsx +5 -6
  111. package/src/components/ui/panels/node-display.ts +1 -0
  112. package/src/components/ui/panels/paint-panel.tsx +66 -41
  113. package/src/components/ui/panels/panel-manager.tsx +33 -74
  114. package/src/components/ui/panels/panel-wrapper.tsx +217 -12
  115. package/src/components/ui/panels/parametric-inspector.tsx +469 -0
  116. package/src/components/ui/panels/reference-panel.tsx +28 -13
  117. package/src/components/ui/primitives/dropdown-menu.tsx +12 -8
  118. package/src/components/ui/primitives/number-input.tsx +1 -1
  119. package/src/components/ui/primitives/sidebar.tsx +0 -0
  120. package/src/components/ui/scene-loader.tsx +1 -3
  121. package/src/components/ui/sidebar/app-sidebar.tsx +0 -0
  122. package/src/components/ui/sidebar/icon-rail.tsx +0 -0
  123. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +263 -0
  124. package/src/components/ui/sidebar/panels/items-panel/index.tsx +404 -0
  125. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +59 -9
  126. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +6 -1
  127. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +259 -0
  128. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -1
  129. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +5 -2
  130. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +83 -0
  131. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +89 -0
  132. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +75 -0
  133. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +2 -2
  134. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +83 -0
  135. package/src/components/ui/sidebar/panels/site-panel/index.tsx +8 -12
  136. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +2 -1
  137. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +93 -0
  138. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +41 -5
  139. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +128 -0
  140. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +5 -2
  141. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +83 -0
  142. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -1
  143. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +68 -34
  144. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -1
  145. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +52 -3
  146. package/src/components/ui/sidebar/tab-bar.tsx +61 -1
  147. package/src/components/ui/slider.tsx +1 -1
  148. package/src/components/viewer-overlay.tsx +169 -52
  149. package/src/components/viewer-zone-system.tsx +1 -1
  150. package/src/hooks/use-auto-save.ts +24 -3
  151. package/src/hooks/use-ceiling-events.ts +175 -0
  152. package/src/hooks/use-drag-action.ts +124 -0
  153. package/src/hooks/use-keyboard.ts +125 -62
  154. package/src/hooks/use-selection.ts +64 -0
  155. package/src/index.tsx +243 -4
  156. package/src/lib/constants.ts +6 -2
  157. package/src/lib/editor-api.ts +62 -0
  158. package/src/lib/elevator-support.ts +96 -0
  159. package/src/lib/floorplan/apply-alignment.ts +111 -0
  160. package/src/lib/floorplan/index.ts +7 -0
  161. package/src/lib/floorplan/selection-tool.ts +40 -0
  162. package/src/lib/floorplan/stairs.ts +12 -1
  163. package/src/lib/fresh-planar-placement.test.ts +102 -0
  164. package/src/lib/fresh-planar-placement.ts +63 -0
  165. package/src/lib/history.ts +2 -1
  166. package/src/lib/level-duplication.test.ts +0 -2
  167. package/src/lib/level-duplication.ts +55 -43
  168. package/src/lib/level-name.ts +11 -0
  169. package/src/lib/material-paint.ts +212 -20
  170. package/src/lib/placement-metadata.ts +29 -0
  171. package/src/lib/planar-cursor-placement.test.ts +43 -0
  172. package/src/lib/planar-cursor-placement.ts +42 -0
  173. package/src/lib/roof-duplication.ts +2 -2
  174. package/src/lib/scene-bounds.ts +1 -1
  175. package/src/lib/scene-clipboard.ts +267 -0
  176. package/src/lib/scene.ts +13 -4
  177. package/src/lib/sfx-bus.ts +25 -1
  178. package/src/lib/sfx-player.ts +75 -27
  179. package/src/lib/site-boundary.ts +1 -0
  180. package/src/lib/stair-duplication.ts +2 -2
  181. package/src/lib/stair-levels.test.ts +146 -0
  182. package/src/lib/stair-levels.ts +177 -0
  183. package/src/lib/world-grid-snap.ts +199 -0
  184. package/src/lib/zone-content.ts +130 -0
  185. package/src/store/use-alignment-guides.ts +21 -0
  186. package/src/store/use-editor.tsx +342 -91
  187. package/src/store/use-placement-preview.ts +31 -0
  188. package/src/store/use-wall-move-ghosts.ts +36 -0
  189. package/src/store/use-wall-snap-indicator.ts +32 -0
  190. package/tsconfig.json +2 -1
  191. package/src/components/editor/preset-thumbnail-generator.tsx +0 -125
  192. package/src/components/editor-2d/renderers/floorplan-roof-layer.tsx +0 -113
  193. package/src/components/feedback-dialog.tsx +0 -265
  194. package/src/components/pascal-radio.tsx +0 -280
  195. package/src/components/preview-button.tsx +0 -16
  196. package/src/components/tools/building/move-building-tool.tsx +0 -157
  197. package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +0 -43
  198. package/src/components/tools/ceiling/ceiling-hole-editor.tsx +0 -49
  199. package/src/components/tools/ceiling/ceiling-tool.tsx +0 -465
  200. package/src/components/tools/ceiling/move-ceiling-tool.tsx +0 -257
  201. package/src/components/tools/column/column-tool.tsx +0 -97
  202. package/src/components/tools/column/move-column-tool.tsx +0 -105
  203. package/src/components/tools/door/door-math.ts +0 -110
  204. package/src/components/tools/door/door-tool.tsx +0 -324
  205. package/src/components/tools/door/move-door-tool.tsx +0 -412
  206. package/src/components/tools/fence/curve-fence-tool.tsx +0 -179
  207. package/src/components/tools/fence/fence-tool.tsx +0 -346
  208. package/src/components/tools/fence/move-fence-endpoint-tool.tsx +0 -438
  209. package/src/components/tools/fence/move-fence-tool.tsx +0 -298
  210. package/src/components/tools/item/item-tool.tsx +0 -26
  211. package/src/components/tools/roof/move-roof-tool.tsx +0 -359
  212. package/src/components/tools/slab/move-slab-tool.tsx +0 -182
  213. package/src/components/tools/slab/slab-boundary-editor.tsx +0 -43
  214. package/src/components/tools/slab/slab-hole-editor.tsx +0 -49
  215. package/src/components/tools/slab/slab-tool.tsx +0 -322
  216. package/src/components/tools/spawn/move-spawn-tool.tsx +0 -101
  217. package/src/components/tools/spawn/spawn-tool.tsx +0 -130
  218. package/src/components/tools/wall/curve-wall-tool.tsx +0 -176
  219. package/src/components/tools/wall/move-wall-endpoint-tool.tsx +0 -423
  220. package/src/components/tools/wall/move-wall-tool.tsx +0 -356
  221. package/src/components/tools/wall/wall-tool.tsx +0 -332
  222. package/src/components/tools/window/move-window-tool.tsx +0 -447
  223. package/src/components/tools/window/window-math.ts +0 -117
  224. package/src/components/tools/window/window-tool.tsx +0 -332
  225. package/src/components/ui/helpers/ceiling-helper.tsx +0 -20
  226. package/src/components/ui/helpers/slab-helper.tsx +0 -20
  227. package/src/components/ui/helpers/wall-helper.tsx +0 -20
  228. package/src/components/ui/panels/ceiling-panel.tsx +0 -250
  229. package/src/components/ui/panels/column-panel.tsx +0 -715
  230. package/src/components/ui/panels/door-panel.tsx +0 -1292
  231. package/src/components/ui/panels/fence-panel.tsx +0 -227
  232. package/src/components/ui/panels/item-panel.tsx +0 -306
  233. package/src/components/ui/panels/presets/presets-popover.tsx +0 -511
  234. package/src/components/ui/panels/roof-panel.tsx +0 -230
  235. package/src/components/ui/panels/roof-segment-panel.tsx +0 -311
  236. package/src/components/ui/panels/slab-panel.tsx +0 -250
  237. package/src/components/ui/panels/spawn-panel.tsx +0 -155
  238. package/src/components/ui/panels/stair-panel.tsx +0 -525
  239. package/src/components/ui/panels/stair-segment-panel.tsx +0 -325
  240. package/src/components/ui/panels/wall-panel.tsx +0 -177
  241. package/src/components/ui/panels/window-panel.tsx +0 -960
  242. package/src/components/ui/viewer-toolbar.tsx +0 -436
  243. package/src/contexts/presets-context.tsx +0 -121
  244. package/src/hooks/use-contextual-tools.ts +0 -61
@@ -1,250 +0,0 @@
1
- 'use client'
2
-
3
- import { type AnyNode, type SlabNode, 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 SlabPanel() {
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 SlabNode | undefined) : undefined,
24
- )
25
-
26
- const handleUpdate = useCallback(
27
- (updates: Partial<SlabNode>) => {
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 === 'slab' && 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
- if (!(current && next)) continue
126
- area += current[0] * next[1]
127
- area -= next[0] * current[1]
128
- }
129
- return Math.abs(area) / 2
130
- }
131
-
132
- const area = calculateArea(node.polygon)
133
-
134
- return (
135
- <PanelWrapper
136
- icon="/icons/floor.png"
137
- onClose={handleClose}
138
- title={node.name || 'Slab'}
139
- width={320}
140
- >
141
- <PanelSection title="Elevation">
142
- <SliderControl
143
- label="Height"
144
- max={1}
145
- min={-1}
146
- onChange={(v) => handleUpdate({ elevation: v })}
147
- precision={3}
148
- step={0.01}
149
- unit="m"
150
- value={Math.round(node.elevation * 1000) / 1000}
151
- />
152
-
153
- <div className="mt-2 grid grid-cols-2 gap-1.5 px-1 pb-1">
154
- <ActionButton label="Sunken (-15cm)" onClick={() => handleUpdate({ elevation: -0.15 })} />
155
- <ActionButton label="Ground (0m)" onClick={() => handleUpdate({ elevation: 0 })} />
156
- <ActionButton label="Raised (+5cm)" onClick={() => handleUpdate({ elevation: 0.05 })} />
157
- <ActionButton label="Step (+15cm)" onClick={() => handleUpdate({ elevation: 0.15 })} />
158
- </div>
159
- </PanelSection>
160
-
161
- <PanelSection title="Info">
162
- <div className="flex items-center justify-between px-2 py-1 text-muted-foreground text-sm">
163
- <span>Area</span>
164
- <span className="font-mono text-white">{area.toFixed(2)} m²</span>
165
- </div>
166
- </PanelSection>
167
-
168
- <PanelSection title="Holes">
169
- {node.holes && node.holes.length > 0 ? (
170
- <div className="flex flex-col gap-1 pb-2">
171
- {node.holes.map((hole, index) => {
172
- const holeArea = calculateArea(hole)
173
- const isEditing =
174
- editingHole?.nodeId === selectedId && editingHole?.holeIndex === index
175
- const source = node.holeMetadata?.[index]?.source ?? 'manual'
176
- const isAutoHole = source === 'stair'
177
- return (
178
- <div
179
- className={`flex items-center justify-between rounded-lg border p-2 transition-colors ${
180
- isEditing
181
- ? 'border-primary/50 bg-primary/10'
182
- : 'border-transparent hover:bg-accent/30'
183
- }`}
184
- key={index}
185
- >
186
- <div className="min-w-0 flex-1">
187
- <p
188
- className={`font-medium text-xs ${isEditing ? 'text-primary' : 'text-white'}`}
189
- >
190
- Hole {index + 1} {isEditing && '(Editing)'}
191
- </p>
192
- <p className="text-[10px] text-muted-foreground">
193
- {holeArea.toFixed(2)} m² · {hole.length} pts ·{' '}
194
- {isAutoHole ? 'Auto stair cutout' : 'Manual'}
195
- </p>
196
- </div>
197
- <div className="flex items-center gap-1">
198
- {isEditing ? (
199
- <ActionButton
200
- className="h-7 bg-primary text-primary-foreground hover:bg-primary/90"
201
- label="Done"
202
- onClick={() => setEditingHole(null)}
203
- />
204
- ) : isAutoHole ? (
205
- <div className="rounded-md bg-[#2C2C2E] px-2 py-1 text-[10px] text-muted-foreground">
206
- Auto
207
- </div>
208
- ) : (
209
- <>
210
- <button
211
- className="flex h-7 w-7 items-center justify-center rounded-md bg-[#2C2C2E] text-muted-foreground hover:bg-[#3e3e3e] hover:text-foreground"
212
- onClick={() => handleEditHole(index)}
213
- type="button"
214
- >
215
- <Edit className="h-3.5 w-3.5" />
216
- </button>
217
- <button
218
- 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"
219
- onClick={() => handleDeleteHole(index)}
220
- type="button"
221
- >
222
- <Trash2 className="h-3.5 w-3.5" />
223
- </button>
224
- </>
225
- )}
226
- </div>
227
- </div>
228
- )
229
- })}
230
- </div>
231
- ) : (
232
- <div className="px-2 py-3 text-center text-muted-foreground text-xs">No holes</div>
233
- )}
234
-
235
- <div className="px-1 pt-1 pb-1">
236
- <ActionButton
237
- className="w-full"
238
- disabled={editingHole?.nodeId === selectedId}
239
- icon={<Plus className="h-3.5 w-3.5" />}
240
- label="Add Hole"
241
- onClick={handleAddHole}
242
- />
243
- </div>
244
- </PanelSection>
245
- <ActionGroup>
246
- <ActionButton icon={<Move className="h-3.5 w-3.5" />} label="Move" onClick={handleMove} />
247
- </ActionGroup>
248
- </PanelWrapper>
249
- )
250
- }
@@ -1,155 +0,0 @@
1
- 'use client'
2
-
3
- import { type AnyNode, type SpawnNode, useLiveTransforms, useScene } from '@pascal-app/core'
4
- import { useViewer } from '@pascal-app/viewer'
5
- import { Move, Trash2 } from 'lucide-react'
6
- import { useCallback, useEffect, useState } 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 SpawnPanel() {
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 deleteNode = useScene((s) => s.deleteNode)
19
- const setMovingNode = useEditor((s) => s.setMovingNode)
20
-
21
- const node = useScene((s) =>
22
- selectedId ? (s.nodes[selectedId as AnyNode['id']] as SpawnNode | undefined) : undefined,
23
- )
24
- const [draftRotation, setDraftRotation] = useState<number | null>(null)
25
-
26
- useEffect(() => {
27
- if (!(node && node.type === 'spawn')) {
28
- setDraftRotation(null)
29
- return
30
- }
31
-
32
- setDraftRotation(node.rotation)
33
- useLiveTransforms.getState().clear(node.id)
34
- }, [node?.id, node?.rotation, node?.type])
35
-
36
- const handleUpdate = useCallback(
37
- (updates: Partial<SpawnNode>) => {
38
- if (!(selectedId && node)) return
39
- updateNode(selectedId as AnyNode['id'], updates)
40
- },
41
- [node, selectedId, updateNode],
42
- )
43
-
44
- const handleRotationChange = useCallback(
45
- (degrees: number) => {
46
- if (!(node && selectedId)) return
47
- const nextRotation = (degrees * Math.PI) / 180
48
- setDraftRotation(nextRotation)
49
- useLiveTransforms.getState().set(selectedId as AnyNode['id'], {
50
- position: [...node.position],
51
- rotation: nextRotation,
52
- })
53
- },
54
- [node, selectedId],
55
- )
56
-
57
- const commitRotation = useCallback(
58
- (degrees: number) => {
59
- if (!(node && selectedId)) return
60
- const nextRotation = (degrees * Math.PI) / 180
61
- useLiveTransforms.getState().clear(selectedId as AnyNode['id'])
62
- setDraftRotation(nextRotation)
63
- if (Math.abs(nextRotation - node.rotation) > 1e-6) {
64
- updateNode(selectedId as AnyNode['id'], { rotation: nextRotation })
65
- }
66
- },
67
- [node, selectedId, updateNode],
68
- )
69
-
70
- const handleClose = useCallback(() => {
71
- setSelection({ selectedIds: [] })
72
- }, [setSelection])
73
-
74
- const handleMove = useCallback(() => {
75
- if (!node) return
76
- sfxEmitter.emit('sfx:item-pick')
77
- setMovingNode(node)
78
- setSelection({ selectedIds: [] })
79
- }, [node, setMovingNode, setSelection])
80
-
81
- const handleDelete = useCallback(() => {
82
- if (!selectedId) return
83
- sfxEmitter.emit('sfx:structure-delete')
84
- deleteNode(selectedId as AnyNode['id'])
85
- setSelection({ selectedIds: [] })
86
- }, [deleteNode, selectedId, setSelection])
87
-
88
- if (!(node && node.type === 'spawn' && selectedId)) return null
89
-
90
- const rotationDegrees = Math.round((((draftRotation ?? node.rotation) * 180) / Math.PI))
91
- const storedRotationDegrees = Math.round((node.rotation * 180) / Math.PI)
92
-
93
- return (
94
- <PanelWrapper icon="/icons/site.png" onClose={handleClose} title="Spawn Point" width={300}>
95
- <PanelSection title="Position">
96
- <SliderControl
97
- label="X"
98
- max={node.position[0] + 2}
99
- min={node.position[0] - 2}
100
- onChange={(value) => handleUpdate({ position: [value, node.position[1], node.position[2]] })}
101
- precision={2}
102
- step={0.01}
103
- unit="m"
104
- value={Math.round(node.position[0] * 100) / 100}
105
- />
106
- <SliderControl
107
- label="Y"
108
- max={node.position[1] + 2}
109
- min={node.position[1] - 2}
110
- onChange={(value) => handleUpdate({ position: [node.position[0], value, node.position[2]] })}
111
- precision={2}
112
- step={0.01}
113
- unit="m"
114
- value={Math.round(node.position[1] * 100) / 100}
115
- />
116
- <SliderControl
117
- label="Z"
118
- max={node.position[2] + 2}
119
- min={node.position[2] - 2}
120
- onChange={(value) => handleUpdate({ position: [node.position[0], node.position[1], value] })}
121
- precision={2}
122
- step={0.01}
123
- unit="m"
124
- value={Math.round(node.position[2] * 100) / 100}
125
- />
126
- </PanelSection>
127
-
128
- <PanelSection title="Facing">
129
- <SliderControl
130
- label="Yaw"
131
- max={storedRotationDegrees + 90}
132
- min={storedRotationDegrees - 90}
133
- onChange={handleRotationChange}
134
- onCommit={commitRotation}
135
- precision={0}
136
- step={1}
137
- unit="°"
138
- value={rotationDegrees}
139
- />
140
- </PanelSection>
141
-
142
- <PanelSection title="Actions">
143
- <ActionGroup>
144
- <ActionButton icon={<Move className="h-4 w-4" />} label="Move" onClick={handleMove} />
145
- <ActionButton
146
- className="border-red-500/40 text-red-200 hover:bg-red-500/15"
147
- icon={<Trash2 className="h-4 w-4" />}
148
- label="Delete"
149
- onClick={handleDelete}
150
- />
151
- </ActionGroup>
152
- </PanelSection>
153
- </PanelWrapper>
154
- )
155
- }