@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,230 +0,0 @@
1
- 'use client'
2
-
3
- import {
4
- type AnyNode,
5
- type AnyNodeId,
6
- type RoofNode,
7
- type RoofSurfaceMaterialRole,
8
- RoofNode as RoofNodeSchema,
9
- type RoofSegmentNode,
10
- RoofSegmentNode as RoofSegmentNodeSchema,
11
- useScene,
12
- } from '@pascal-app/core'
13
- import { useViewer } from '@pascal-app/viewer'
14
- import { Copy, Move, Plus, Trash2 } from 'lucide-react'
15
- import { useCallback } from 'react'
16
- import { useShallow } from 'zustand/react/shallow'
17
- import { sfxEmitter } from '../../../lib/sfx-bus'
18
- import { duplicateRoofSubtree } from '../../../lib/roof-duplication'
19
- import useEditor from '../../../store/use-editor'
20
- import { ActionButton, ActionGroup } from '../controls/action-button'
21
- import { PanelSection } from '../controls/panel-section'
22
- import { SliderControl } from '../controls/slider-control'
23
- import { PanelWrapper } from './panel-wrapper'
24
-
25
- export function RoofPanel() {
26
- const selectedId = useViewer((s) => s.selection.selectedIds[0])
27
- const setSelection = useViewer((s) => s.setSelection)
28
- const updateNode = useScene((s) => s.updateNode)
29
- const createNode = useScene((s) => s.createNode)
30
- const setMovingNode = useEditor((s) => s.setMovingNode)
31
-
32
- const node = useScene((s) =>
33
- selectedId ? (s.nodes[selectedId as AnyNode['id']] as RoofNode | undefined) : undefined,
34
- )
35
- // Shallow selector — only re-renders when the segment list content changes.
36
- const segments = useScene(
37
- useShallow((s) => {
38
- if (!node) return []
39
- return (node.children ?? [])
40
- .map((childId) => s.nodes[childId as AnyNodeId] as RoofSegmentNode | undefined)
41
- .filter((n): n is RoofSegmentNode => n?.type === 'roof-segment')
42
- }),
43
- )
44
-
45
- const handleUpdate = useCallback(
46
- (updates: Partial<RoofNode>) => {
47
- if (!selectedId) return
48
- updateNode(selectedId as AnyNode['id'], updates)
49
- },
50
- [selectedId, updateNode],
51
- )
52
-
53
- const handleClose = useCallback(() => {
54
- setSelection({ selectedIds: [] })
55
- }, [setSelection])
56
-
57
- const handleAddSegment = useCallback(() => {
58
- if (!node) return
59
- const segment = RoofSegmentNodeSchema.parse({
60
- width: 6,
61
- depth: 6,
62
- wallHeight: 0.5,
63
- roofHeight: 2.5,
64
- roofType: 'gable',
65
- position: [2, 0, 2],
66
- })
67
- createNode(segment, node.id as AnyNodeId)
68
- }, [node, createNode])
69
-
70
- const handleSelectSegment = useCallback(
71
- (segmentId: string) => {
72
- setSelection({ selectedIds: [segmentId as AnyNode['id']] })
73
- },
74
- [setSelection],
75
- )
76
-
77
- const handleDuplicate = useCallback(() => {
78
- if (!node) return
79
- sfxEmitter.emit('sfx:item-pick')
80
-
81
- try {
82
- duplicateRoofSubtree(node.id as AnyNodeId, { mode: 'move' })
83
- } catch (e) {
84
- console.error('Failed to duplicate roof', e)
85
- }
86
- }, [node])
87
-
88
- const handleMove = useCallback(() => {
89
- if (node) {
90
- sfxEmitter.emit('sfx:item-pick')
91
- setMovingNode(node)
92
- setSelection({ selectedIds: [] })
93
- }
94
- }, [node, setMovingNode, setSelection])
95
-
96
- const handleDelete = useCallback(() => {
97
- if (!(selectedId && node)) return
98
- sfxEmitter.emit('sfx:item-delete')
99
- const parentId = node.parentId
100
- useScene.getState().deleteNode(selectedId as AnyNodeId)
101
- if (parentId) {
102
- useScene.getState().dirtyNodes.add(parentId as AnyNodeId)
103
- }
104
- setSelection({ selectedIds: [] })
105
- }, [selectedId, node, setSelection])
106
-
107
- if (!(node && node.type === 'roof' && selectedId)) return null
108
-
109
- return (
110
- <PanelWrapper
111
- icon="/icons/roof.png"
112
- onClose={handleClose}
113
- title={node.name || 'Roof'}
114
- width={300}
115
- >
116
- <PanelSection title="Segments">
117
- <div className="flex flex-col gap-1">
118
- {segments.map((seg, i) => (
119
- <button
120
- className="flex items-center justify-between rounded-lg border border-border/50 bg-[#2C2C2E] px-3 py-2 text-foreground text-sm transition-colors hover:bg-[#3e3e3e]"
121
- key={seg.id}
122
- onClick={() => handleSelectSegment(seg.id)}
123
- type="button"
124
- >
125
- <span className="truncate">{seg.name || `Segment ${i + 1}`}</span>
126
- <span className="text-muted-foreground text-xs capitalize">{seg.roofType}</span>
127
- </button>
128
- ))}
129
- </div>
130
- <ActionGroup>
131
- <ActionButton
132
- icon={<Plus className="h-3.5 w-3.5" />}
133
- label="Add Segment"
134
- onClick={handleAddSegment}
135
- />
136
- </ActionGroup>
137
- </PanelSection>
138
-
139
- <PanelSection title="Position">
140
- <SliderControl
141
- label="X"
142
- max={50}
143
- min={-50}
144
- onChange={(v) => {
145
- const pos = [...node.position] as [number, number, number]
146
- pos[0] = v
147
- handleUpdate({ position: pos })
148
- }}
149
- precision={2}
150
- step={0.05}
151
- unit="m"
152
- value={Math.round(node.position[0] * 100) / 100}
153
- />
154
- <SliderControl
155
- label="Y"
156
- max={50}
157
- min={-50}
158
- onChange={(v) => {
159
- const pos = [...node.position] as [number, number, number]
160
- pos[1] = v
161
- handleUpdate({ position: pos })
162
- }}
163
- precision={2}
164
- step={0.05}
165
- unit="m"
166
- value={Math.round(node.position[1] * 100) / 100}
167
- />
168
- <SliderControl
169
- label="Z"
170
- max={50}
171
- min={-50}
172
- onChange={(v) => {
173
- const pos = [...node.position] as [number, number, number]
174
- pos[2] = v
175
- handleUpdate({ position: pos })
176
- }}
177
- precision={2}
178
- step={0.05}
179
- unit="m"
180
- value={Math.round(node.position[2] * 100) / 100}
181
- />
182
- <SliderControl
183
- label="Rotation"
184
- max={180}
185
- min={-180}
186
- onChange={(degrees) => {
187
- handleUpdate({ rotation: (degrees * Math.PI) / 180 })
188
- }}
189
- precision={0}
190
- step={1}
191
- unit="°"
192
- value={Math.round((node.rotation * 180) / Math.PI)}
193
- />
194
- <div className="flex gap-1.5 px-1 pt-2 pb-1">
195
- <ActionButton
196
- label="-45°"
197
- onClick={() => {
198
- sfxEmitter.emit('sfx:item-rotate')
199
- handleUpdate({ rotation: node.rotation - Math.PI / 4 })
200
- }}
201
- />
202
- <ActionButton
203
- label="+45°"
204
- onClick={() => {
205
- sfxEmitter.emit('sfx:item-rotate')
206
- handleUpdate({ rotation: node.rotation + Math.PI / 4 })
207
- }}
208
- />
209
- </div>
210
- </PanelSection>
211
-
212
- <PanelSection title="Actions">
213
- <ActionGroup>
214
- <ActionButton icon={<Move className="h-3.5 w-3.5" />} label="Move" onClick={handleMove} />
215
- <ActionButton
216
- icon={<Copy className="h-3.5 w-3.5" />}
217
- label="Duplicate"
218
- onClick={handleDuplicate}
219
- />
220
- <ActionButton
221
- className="hover:bg-red-500/20"
222
- icon={<Trash2 className="h-3.5 w-3.5 text-red-400" />}
223
- label="Delete"
224
- onClick={handleDelete}
225
- />
226
- </ActionGroup>
227
- </PanelSection>
228
- </PanelWrapper>
229
- )
230
- }
@@ -1,311 +0,0 @@
1
- 'use client'
2
-
3
- import {
4
- type AnyNode,
5
- type AnyNodeId,
6
- type RoofSegmentNode,
7
- RoofSegmentNode as RoofSegmentNodeSchema,
8
- type RoofType,
9
- useScene,
10
- } from '@pascal-app/core'
11
- import { useViewer } from '@pascal-app/viewer'
12
- import { Copy, Move, Trash2 } from 'lucide-react'
13
- import { useCallback } from 'react'
14
- import { sfxEmitter } from '../../../lib/sfx-bus'
15
- import useEditor from '../../../store/use-editor'
16
- import { ActionButton, ActionGroup } from '../controls/action-button'
17
- import { PanelSection } from '../controls/panel-section'
18
- import { SegmentedControl } from '../controls/segmented-control'
19
- import { SliderControl } from '../controls/slider-control'
20
- import { PanelWrapper } from './panel-wrapper'
21
-
22
- const ROOF_TYPE_OPTIONS: { label: string; value: RoofType }[] = [
23
- { label: 'Hip', value: 'hip' },
24
- { label: 'Gable', value: 'gable' },
25
- { label: 'Shed', value: 'shed' },
26
- { label: 'Flat', value: 'flat' },
27
- ]
28
-
29
- const ROOF_TYPE_OPTIONS_2: { label: string; value: RoofType }[] = [
30
- { label: 'Gambrel', value: 'gambrel' },
31
- { label: 'Dutch', value: 'dutch' },
32
- { label: 'Mansard', value: 'mansard' },
33
- ]
34
-
35
- export function RoofSegmentPanel() {
36
- const selectedId = useViewer((s) => s.selection.selectedIds[0])
37
- const setSelection = useViewer((s) => s.setSelection)
38
- const updateNode = useScene((s) => s.updateNode)
39
- const setMovingNode = useEditor((s) => s.setMovingNode)
40
-
41
- const node = useScene((s) =>
42
- selectedId ? (s.nodes[selectedId as AnyNode['id']] as RoofSegmentNode | undefined) : undefined,
43
- )
44
-
45
- const handleUpdate = useCallback(
46
- (updates: Partial<RoofSegmentNode>) => {
47
- if (!selectedId) return
48
- updateNode(selectedId as AnyNode['id'], updates)
49
- },
50
- [selectedId, updateNode],
51
- )
52
-
53
- const handleClose = useCallback(() => {
54
- setSelection({ selectedIds: [] })
55
- }, [setSelection])
56
-
57
- const handleBack = useCallback(() => {
58
- if (node?.parentId) {
59
- setSelection({ selectedIds: [node.parentId] })
60
- }
61
- }, [node?.parentId, setSelection])
62
-
63
- const handleDuplicate = useCallback(() => {
64
- if (!node?.parentId) return
65
- sfxEmitter.emit('sfx:item-pick')
66
-
67
- let duplicateInfo = structuredClone(node) as any
68
- delete duplicateInfo.id
69
- duplicateInfo.metadata = { ...duplicateInfo.metadata, isNew: true }
70
- // Offset slightly so it's visible
71
- duplicateInfo.position = [
72
- duplicateInfo.position[0] + 1,
73
- duplicateInfo.position[1],
74
- duplicateInfo.position[2] + 1,
75
- ]
76
-
77
- try {
78
- const duplicate = RoofSegmentNodeSchema.parse(duplicateInfo)
79
- useScene.getState().createNode(duplicate, duplicate.parentId as AnyNodeId)
80
- setSelection({ selectedIds: [] })
81
- setMovingNode(duplicate)
82
- } catch (e) {
83
- console.error('Failed to duplicate roof segment', e)
84
- }
85
- }, [node, setSelection, setMovingNode])
86
-
87
- const handleMove = useCallback(() => {
88
- if (node) {
89
- sfxEmitter.emit('sfx:item-pick')
90
- setMovingNode(node)
91
- setSelection({ selectedIds: [] })
92
- }
93
- }, [node, setMovingNode, setSelection])
94
-
95
- const handleDelete = useCallback(() => {
96
- if (!(selectedId && node)) return
97
- sfxEmitter.emit('sfx:item-delete')
98
- const parentId = node.parentId
99
- useScene.getState().deleteNode(selectedId as AnyNodeId)
100
- if (parentId) {
101
- useScene.getState().dirtyNodes.add(parentId as AnyNodeId)
102
- setSelection({ selectedIds: [parentId] })
103
- } else {
104
- setSelection({ selectedIds: [] })
105
- }
106
- }, [selectedId, node, setSelection])
107
-
108
- if (!(node && node.type === 'roof-segment' && selectedId)) return null
109
-
110
- return (
111
- <PanelWrapper
112
- icon="/icons/roof.png"
113
- onBack={handleBack}
114
- onClose={handleClose}
115
- title={node.name || 'Roof Segment'}
116
- width={300}
117
- >
118
- <PanelSection title="Roof Type">
119
- <SegmentedControl
120
- onChange={(v) => handleUpdate({ roofType: v })}
121
- options={ROOF_TYPE_OPTIONS}
122
- value={node.roofType}
123
- />
124
- <SegmentedControl
125
- onChange={(v) => handleUpdate({ roofType: v })}
126
- options={ROOF_TYPE_OPTIONS_2}
127
- value={node.roofType}
128
- />
129
- </PanelSection>
130
-
131
- <PanelSection title="Footprint">
132
- <SliderControl
133
- label="Width"
134
- max={25}
135
- min={0.5}
136
- onChange={(v) => handleUpdate({ width: v })}
137
- precision={2}
138
- step={0.5}
139
- unit="m"
140
- value={Math.round(node.width * 100) / 100}
141
- />
142
- <SliderControl
143
- label="Depth"
144
- max={25}
145
- min={0.5}
146
- onChange={(v) => handleUpdate({ depth: v })}
147
- precision={2}
148
- step={0.5}
149
- unit="m"
150
- value={Math.round(node.depth * 100) / 100}
151
- />
152
- </PanelSection>
153
-
154
- <PanelSection title="Heights">
155
- <SliderControl
156
- label="Wall"
157
- max={5}
158
- min={0}
159
- onChange={(v) => handleUpdate({ wallHeight: v })}
160
- precision={2}
161
- step={0.1}
162
- unit="m"
163
- value={Math.round(node.wallHeight * 100) / 100}
164
- />
165
- <SliderControl
166
- label="Roof"
167
- max={15}
168
- min={0}
169
- onChange={(v) => handleUpdate({ roofHeight: v })}
170
- precision={2}
171
- step={0.1}
172
- unit="m"
173
- value={Math.round(node.roofHeight * 100) / 100}
174
- />
175
- </PanelSection>
176
-
177
- <PanelSection title="Structure">
178
- <SliderControl
179
- label="Wall Thick."
180
- max={1}
181
- min={0.05}
182
- onChange={(v) => handleUpdate({ wallThickness: v })}
183
- precision={2}
184
- step={0.05}
185
- unit="m"
186
- value={Math.round(node.wallThickness * 100) / 100}
187
- />
188
- <SliderControl
189
- label="Deck Thick."
190
- max={0.3}
191
- min={0.04}
192
- onChange={(v) => handleUpdate({ deckThickness: v })}
193
- precision={2}
194
- step={0.01}
195
- unit="m"
196
- value={Math.round(node.deckThickness * 100) / 100}
197
- />
198
- <SliderControl
199
- label="Overhang"
200
- max={1}
201
- min={0}
202
- onChange={(v) => handleUpdate({ overhang: v })}
203
- precision={2}
204
- step={0.05}
205
- unit="m"
206
- value={Math.round(node.overhang * 100) / 100}
207
- />
208
- <SliderControl
209
- label="Shingle Thick."
210
- max={0.3}
211
- min={0.02}
212
- onChange={(v) => handleUpdate({ shingleThickness: v })}
213
- precision={2}
214
- step={0.01}
215
- unit="m"
216
- value={Math.round(node.shingleThickness * 100) / 100}
217
- />
218
- </PanelSection>
219
-
220
- <PanelSection title="Position">
221
- <SliderControl
222
- label="X"
223
- max={50}
224
- min={-50}
225
- onChange={(v) => {
226
- const pos = [...node.position] as [number, number, number]
227
- pos[0] = v
228
- handleUpdate({ position: pos })
229
- }}
230
- precision={2}
231
- step={0.05}
232
- unit="m"
233
- value={Math.round(node.position[0] * 100) / 100}
234
- />
235
- <SliderControl
236
- label="Y"
237
- max={50}
238
- min={-50}
239
- onChange={(v) => {
240
- const pos = [...node.position] as [number, number, number]
241
- pos[1] = v
242
- handleUpdate({ position: pos })
243
- }}
244
- precision={2}
245
- step={0.05}
246
- unit="m"
247
- value={Math.round(node.position[1] * 100) / 100}
248
- />
249
- <SliderControl
250
- label="Z"
251
- max={50}
252
- min={-50}
253
- onChange={(v) => {
254
- const pos = [...node.position] as [number, number, number]
255
- pos[2] = v
256
- handleUpdate({ position: pos })
257
- }}
258
- precision={2}
259
- step={0.05}
260
- unit="m"
261
- value={Math.round(node.position[2] * 100) / 100}
262
- />
263
- <SliderControl
264
- label="Rotation"
265
- max={180}
266
- min={-180}
267
- onChange={(degrees) => {
268
- handleUpdate({ rotation: (degrees * Math.PI) / 180 })
269
- }}
270
- precision={0}
271
- step={1}
272
- unit="°"
273
- value={Math.round((node.rotation * 180) / Math.PI)}
274
- />
275
- <div className="flex gap-1.5 px-1 pt-2 pb-1">
276
- <ActionButton
277
- label="-45°"
278
- onClick={() => {
279
- sfxEmitter.emit('sfx:item-rotate')
280
- handleUpdate({ rotation: node.rotation - Math.PI / 4 })
281
- }}
282
- />
283
- <ActionButton
284
- label="+45°"
285
- onClick={() => {
286
- sfxEmitter.emit('sfx:item-rotate')
287
- handleUpdate({ rotation: node.rotation + Math.PI / 4 })
288
- }}
289
- />
290
- </div>
291
- </PanelSection>
292
-
293
- <PanelSection title="Actions">
294
- <ActionGroup>
295
- <ActionButton icon={<Move className="h-3.5 w-3.5" />} label="Move" onClick={handleMove} />
296
- <ActionButton
297
- icon={<Copy className="h-3.5 w-3.5" />}
298
- label="Duplicate"
299
- onClick={handleDuplicate}
300
- />
301
- <ActionButton
302
- className="hover:bg-red-500/20"
303
- icon={<Trash2 className="h-3.5 w-3.5 text-red-400" />}
304
- label="Delete"
305
- onClick={handleDelete}
306
- />
307
- </ActionGroup>
308
- </PanelSection>
309
- </PanelWrapper>
310
- )
311
- }