@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,227 +0,0 @@
1
- 'use client'
2
-
3
-
4
- import {
5
- type AnyNode,
6
- type AnyNodeId,
7
- type FenceNode,
8
- getClampedWallCurveOffset,
9
- getMaxWallCurveOffset,
10
- getWallCurveLength,
11
- type MaterialSchema,
12
- normalizeWallCurveOffset,
13
- useScene,
14
- } from '@pascal-app/core'
15
-
16
- import { useViewer } from '@pascal-app/viewer'
17
- import { Move, Spline } from 'lucide-react'
18
- import { useCallback } from 'react'
19
-
20
- import { sfxEmitter } from '../../../lib/sfx-bus'
21
- import useEditor from '../../../store/use-editor'
22
- import { ActionButton, ActionGroup } from '../controls/action-button'
23
- import { MaterialPicker } from '../controls/material-picker'
24
- import { PanelSection } from '../controls/panel-section'
25
- import { SegmentedControl } from '../controls/segmented-control'
26
- import { SliderControl } from '../controls/slider-control'
27
- import { PanelWrapper } from './panel-wrapper'
28
-
29
- type FenceStyleValue = 'slat' | 'rail' | 'privacy'
30
- type FenceBaseStyleValue = 'grounded' | 'floating'
31
-
32
- const FENCE_STYLE_OPTIONS: { label: string; value: FenceStyleValue }[] = [
33
- { label: 'Slat', value: 'slat' },
34
- { label: 'Rail', value: 'rail' },
35
- { label: 'Privacy', value: 'privacy' },
36
- ]
37
-
38
- const FENCE_BASE_STYLE_OPTIONS: { label: string; value: FenceBaseStyleValue }[] = [
39
- { label: 'Grounded', value: 'grounded' },
40
- { label: 'Floating', value: 'floating' },
41
- ]
42
-
43
- export function FencePanel() {
44
- const selectedId = useViewer((s) => s.selection.selectedIds[0])
45
- const selectedCount = useViewer((s) => s.selection.selectedIds.length)
46
- const setSelection = useViewer((s) => s.setSelection)
47
- const updateNode = useScene((s) => s.updateNode)
48
- const setMovingNode = useEditor((s) => s.setMovingNode)
49
- const setCurvingFence = useEditor((s) => s.setCurvingFence)
50
-
51
- const node = useScene((s) =>
52
- selectedId ? (s.nodes[selectedId as AnyNode['id']] as FenceNode | undefined) : undefined,
53
- )
54
-
55
- const handleUpdate = useCallback(
56
- (updates: Partial<FenceNode>) => {
57
- if (!selectedId) return
58
- updateNode(selectedId as AnyNode['id'], updates)
59
- useScene.getState().dirtyNodes.add(selectedId as AnyNodeId)
60
- },
61
- [selectedId, updateNode],
62
- )
63
-
64
- const handleUpdateLength = useCallback(
65
- (newLength: number) => {
66
- if (!node || newLength <= 0) return
67
-
68
- const dx = node.end[0] - node.start[0]
69
- const dz = node.end[1] - node.start[1]
70
- const currentLength = Math.sqrt(dx * dx + dz * dz)
71
- if (currentLength === 0) return
72
-
73
- const dirX = dx / currentLength
74
- const dirZ = dz / currentLength
75
- const newEnd: [number, number] = [
76
- node.start[0] + dirX * newLength,
77
- node.start[1] + dirZ * newLength,
78
- ]
79
-
80
- handleUpdate({ end: newEnd })
81
- },
82
- [node, handleUpdate],
83
- )
84
-
85
- const handleClose = useCallback(() => {
86
- setSelection({ selectedIds: [] })
87
- }, [setSelection])
88
-
89
-
90
-
91
-
92
-
93
- if (!(node && node.type === 'fence' && selectedId && selectedCount === 1)) return null
94
-
95
- const length = getWallCurveLength(node)
96
- const curveOffset = getClampedWallCurveOffset(node)
97
- const maxCurveOffset = getMaxWallCurveOffset(node)
98
-
99
- return (
100
- <PanelWrapper
101
- icon="/icons/build.png"
102
- onClose={handleClose}
103
- title={node.name || 'Fence'}
104
- width={300}
105
- >
106
- <PanelSection title="Style">
107
- <SegmentedControl
108
- onChange={(value) => handleUpdate({ style: value })}
109
- options={FENCE_STYLE_OPTIONS}
110
- value={node.style}
111
- />
112
- <SegmentedControl
113
- className="mt-2"
114
- onChange={(value) => handleUpdate({ baseStyle: value })}
115
- options={FENCE_BASE_STYLE_OPTIONS}
116
- value={node.baseStyle}
117
- />
118
- </PanelSection>
119
-
120
- <PanelSection title="Dimensions">
121
- <SliderControl
122
- label="Length"
123
- max={50}
124
- min={0.1}
125
- onChange={handleUpdateLength}
126
- precision={2}
127
- step={0.01}
128
- unit="m"
129
- value={length}
130
- />
131
- <SliderControl
132
- label="Curve"
133
- max={Math.max(0.01, maxCurveOffset)}
134
- min={-Math.max(0.01, maxCurveOffset)}
135
- onChange={(value) => handleUpdate({ curveOffset: normalizeWallCurveOffset(node, value) })}
136
- precision={2}
137
- step={0.1}
138
- unit="m"
139
- value={Math.round(curveOffset * 100) / 100}
140
- />
141
- <SliderControl
142
- label="Height"
143
- max={4}
144
- min={0.4}
145
- onChange={(value) => handleUpdate({ height: Math.max(0.4, value) })}
146
- precision={2}
147
- step={0.05}
148
- unit="m"
149
- value={node.height}
150
- />
151
- <SliderControl
152
- label="Thickness"
153
- max={0.5}
154
- min={0.03}
155
- onChange={(value) => handleUpdate({ thickness: Math.max(0.03, value) })}
156
- precision={3}
157
- step={0.005}
158
- unit="m"
159
- value={node.thickness}
160
- />
161
- </PanelSection>
162
-
163
- <PanelSection title="Structure">
164
- <SliderControl
165
- label="Base Height"
166
- max={1}
167
- min={0.04}
168
- onChange={(value) => handleUpdate({ baseHeight: Math.max(0.04, value) })}
169
- precision={3}
170
- step={0.01}
171
- unit="m"
172
- value={node.baseHeight}
173
- />
174
- <SliderControl
175
- label="Top Rail"
176
- max={0.25}
177
- min={0.01}
178
- onChange={(value) => handleUpdate({ topRailHeight: Math.max(0.01, value) })}
179
- precision={3}
180
- step={0.005}
181
- unit="m"
182
- value={node.topRailHeight}
183
- />
184
- <SliderControl
185
- label="Post Spacing"
186
- max={5}
187
- min={0.2}
188
- onChange={(value) => handleUpdate({ postSpacing: Math.max(0.2, value) })}
189
- precision={2}
190
- step={0.05}
191
- unit="m"
192
- value={node.postSpacing}
193
- />
194
- <SliderControl
195
- label="Post Size"
196
- max={0.4}
197
- min={0.01}
198
- onChange={(value) => handleUpdate({ postSize: Math.max(0.01, value) })}
199
- precision={3}
200
- step={0.005}
201
- unit="m"
202
- value={node.postSize}
203
- />
204
- <SliderControl
205
- label="Ground Clear"
206
- max={0.6}
207
- min={0}
208
- onChange={(value) => handleUpdate({ groundClearance: Math.max(0, value) })}
209
- precision={3}
210
- step={0.005}
211
- unit="m"
212
- value={node.groundClearance}
213
- />
214
- <SliderControl
215
- label="Edge Inset"
216
- max={0.25}
217
- min={0.005}
218
- onChange={(value) => handleUpdate({ edgeInset: Math.max(0.005, value) })}
219
- precision={3}
220
- step={0.005}
221
- unit="m"
222
- value={node.edgeInset}
223
- />
224
- </PanelSection>
225
- </PanelWrapper>
226
- )
227
- }
@@ -1,306 +0,0 @@
1
- 'use client'
2
-
3
- import { type AnyNode, getScaledDimensions, ItemNode, useScene } from '@pascal-app/core'
4
- import { useViewer } from '@pascal-app/viewer'
5
- import { Copy, Link, Link2Off, Move, Trash2 } from 'lucide-react'
6
- import { useCallback, useState } from 'react'
7
- import { sfxEmitter } from '../../../lib/sfx-bus'
8
- import { cn } from '../../../lib/utils'
9
- import useEditor from '../../../store/use-editor'
10
- import { ActionButton, ActionGroup } from '../controls/action-button'
11
- import { PanelSection } from '../controls/panel-section'
12
- import { SliderControl } from '../controls/slider-control'
13
- import { CollectionsPopover } from './collections/collections-popover'
14
- import { PanelWrapper } from './panel-wrapper'
15
-
16
- export function ItemPanel() {
17
- const selectedId = useViewer((s) => s.selection.selectedIds[0])
18
- const setSelection = useViewer((s) => s.setSelection)
19
- const updateNode = useScene((s) => s.updateNode)
20
- const deleteNode = useScene((s) => s.deleteNode)
21
- const setMovingNode = useEditor((s) => s.setMovingNode)
22
-
23
- const node = useScene((s) =>
24
- selectedId ? (s.nodes[selectedId as AnyNode['id']] as ItemNode | undefined) : undefined,
25
- )
26
-
27
- const [uniformScale, setUniformScale] = useState(true)
28
-
29
- const handleUpdate = useCallback(
30
- (updates: Partial<ItemNode>) => {
31
- if (!(selectedId && node)) return
32
- updateNode(selectedId as AnyNode['id'], updates)
33
-
34
- if (node.asset.attachTo === 'wall' && node.parentId) {
35
- requestAnimationFrame(() => {
36
- useScene.getState().dirtyNodes.add(node.parentId as AnyNode['id'])
37
- })
38
- }
39
- },
40
- [selectedId, node, updateNode],
41
- )
42
-
43
- const handleClose = useCallback(() => {
44
- setSelection({ selectedIds: [] })
45
- }, [setSelection])
46
-
47
- const handleMove = useCallback(() => {
48
- if (node) {
49
- sfxEmitter.emit('sfx:item-pick')
50
- setMovingNode(node)
51
- setSelection({ selectedIds: [] })
52
- }
53
- }, [node, setMovingNode, setSelection])
54
-
55
- const handleDuplicate = useCallback(() => {
56
- if (!node) return
57
- sfxEmitter.emit('sfx:item-pick')
58
- const proto = ItemNode.parse({
59
- position: [...node.position] as [number, number, number],
60
- rotation: [...node.rotation] as [number, number, number],
61
- name: node.name,
62
- asset: node.asset,
63
- parentId: node.parentId,
64
- side: node.side,
65
- metadata: { isNew: true },
66
- })
67
- setMovingNode(proto)
68
- setSelection({ selectedIds: [] })
69
- }, [node, setMovingNode, setSelection])
70
-
71
- const handleDelete = useCallback(() => {
72
- if (!selectedId) return
73
- sfxEmitter.emit('sfx:item-delete')
74
- deleteNode(selectedId as AnyNode['id'])
75
- setSelection({ selectedIds: [] })
76
- }, [selectedId, deleteNode, setSelection])
77
-
78
- if (!(node && node.type === 'item' && selectedId)) return null
79
-
80
- return (
81
- <PanelWrapper
82
- icon={node.asset.thumbnail || '/icons/furniture.png'}
83
- onClose={handleClose}
84
- title={node.name || node.asset.name}
85
- width={300}
86
- >
87
- <PanelSection title="Position">
88
- <SliderControl
89
- label={
90
- <>
91
- X<sub className="ml-[1px] text-[11px] opacity-70">pos</sub>
92
- </>
93
- }
94
- max={node.position[0] + 2}
95
- min={node.position[0] - 2}
96
- onChange={(value) =>
97
- handleUpdate({ position: [value, node.position[1], node.position[2]] })
98
- }
99
- precision={2}
100
- step={0.01}
101
- unit="m"
102
- value={Math.round(node.position[0] * 100) / 100}
103
- />
104
- <SliderControl
105
- label={
106
- <>
107
- Y<sub className="ml-[1px] text-[11px] opacity-70">pos</sub>
108
- </>
109
- }
110
- max={node.position[1] + 2}
111
- min={node.position[1] - 2}
112
- onChange={(value) =>
113
- handleUpdate({ position: [node.position[0], value, node.position[2]] })
114
- }
115
- precision={2}
116
- step={0.01}
117
- unit="m"
118
- value={Math.round(node.position[1] * 100) / 100}
119
- />
120
- <SliderControl
121
- label={
122
- <>
123
- Z<sub className="ml-[1px] text-[11px] opacity-70">pos</sub>
124
- </>
125
- }
126
- max={node.position[2] + 2}
127
- min={node.position[2] - 2}
128
- onChange={(value) =>
129
- handleUpdate({ position: [node.position[0], node.position[1], value] })
130
- }
131
- precision={2}
132
- step={0.01}
133
- unit="m"
134
- value={Math.round(node.position[2] * 100) / 100}
135
- />
136
- </PanelSection>
137
-
138
- <PanelSection title="Rotation">
139
- <SliderControl
140
- label={
141
- <>
142
- Y<sub className="ml-[1px] text-[11px] opacity-70">rot</sub>
143
- </>
144
- }
145
- max={Math.round((node.rotation[1] * 180) / Math.PI) + 45}
146
- min={Math.round((node.rotation[1] * 180) / Math.PI) - 45}
147
- onChange={(degrees) => {
148
- const radians = (degrees * Math.PI) / 180
149
- handleUpdate({ rotation: [node.rotation[0], radians, node.rotation[2]] })
150
- }}
151
- precision={0}
152
- step={1}
153
- unit="°"
154
- value={Math.round((node.rotation[1] * 180) / Math.PI)}
155
- />
156
- <div className="flex gap-1.5 px-1 pt-2 pb-1">
157
- <ActionButton
158
- label="-45°"
159
- onClick={() => {
160
- sfxEmitter.emit('sfx:item-rotate')
161
- const currentDegrees = (node.rotation[1] * 180) / Math.PI
162
- const radians = ((currentDegrees - 45) * Math.PI) / 180
163
- handleUpdate({ rotation: [node.rotation[0], radians, node.rotation[2]] })
164
- }}
165
- />
166
- <ActionButton
167
- label="+45°"
168
- onClick={() => {
169
- sfxEmitter.emit('sfx:item-rotate')
170
- const currentDegrees = (node.rotation[1] * 180) / Math.PI
171
- const radians = ((currentDegrees + 45) * Math.PI) / 180
172
- handleUpdate({ rotation: [node.rotation[0], radians, node.rotation[2]] })
173
- }}
174
- />
175
- </div>
176
- </PanelSection>
177
-
178
- <PanelSection title="Scale">
179
- <div className="flex items-center justify-between px-2 pb-2">
180
- <span className="font-medium text-[10px] text-muted-foreground/80 uppercase tracking-wider">
181
- Uniform Scale
182
- </span>
183
- <button
184
- className={cn(
185
- 'flex h-6 w-6 items-center justify-center rounded-md text-muted-foreground transition-colors hover:text-foreground',
186
- uniformScale ? 'bg-[#3e3e3e]' : 'bg-[#2C2C2E] hover:bg-[#3e3e3e]',
187
- )}
188
- onClick={() => setUniformScale((v) => !v)}
189
- type="button"
190
- >
191
- {uniformScale ? <Link className="h-3.5 w-3.5" /> : <Link2Off className="h-3.5 w-3.5" />}
192
- </button>
193
- </div>
194
-
195
- {uniformScale ? (
196
- <SliderControl
197
- label={
198
- <>
199
- XYZ<sub className="ml-[1px] text-[11px] opacity-70">scale</sub>
200
- </>
201
- }
202
- max={10}
203
- min={0.01}
204
- onChange={(value) => {
205
- const v = Math.max(0.01, value)
206
- handleUpdate({ scale: [v, v, v] })
207
- }}
208
- precision={2}
209
- step={0.1}
210
- value={Math.round(node.scale[0] * 100) / 100}
211
- />
212
- ) : (
213
- <>
214
- <SliderControl
215
- label={
216
- <>
217
- X<sub className="ml-[1px] text-[11px] opacity-70">scale</sub>
218
- </>
219
- }
220
- max={10}
221
- min={0.01}
222
- onChange={(value) =>
223
- handleUpdate({ scale: [Math.max(0.01, value), node.scale[1], node.scale[2]] })
224
- }
225
- precision={2}
226
- step={0.1}
227
- value={Math.round(node.scale[0] * 100) / 100}
228
- />
229
- <SliderControl
230
- label={
231
- <>
232
- Y<sub className="ml-[1px] text-[11px] opacity-70">scale</sub>
233
- </>
234
- }
235
- max={10}
236
- min={0.01}
237
- onChange={(value) =>
238
- handleUpdate({ scale: [node.scale[0], Math.max(0.01, value), node.scale[2]] })
239
- }
240
- precision={2}
241
- step={0.1}
242
- value={Math.round(node.scale[1] * 100) / 100}
243
- />
244
- <SliderControl
245
- label={
246
- <>
247
- Z<sub className="ml-[1px] text-[11px] opacity-70">scale</sub>
248
- </>
249
- }
250
- max={10}
251
- min={0.01}
252
- onChange={(value) =>
253
- handleUpdate({ scale: [node.scale[0], node.scale[1], Math.max(0.01, value)] })
254
- }
255
- precision={2}
256
- step={0.1}
257
- value={Math.round(node.scale[2] * 100) / 100}
258
- />
259
- </>
260
- )}
261
- </PanelSection>
262
-
263
- <PanelSection title="Info">
264
- <div className="flex items-center justify-between px-2 py-1 text-muted-foreground text-sm">
265
- <span>Dimensions</span>
266
- {(() => {
267
- const [w, h, d] = getScaledDimensions(node)
268
- return (
269
- <span className="font-mono text-white">
270
- {Math.round(w * 100) / 100}×{Math.round(h * 100) / 100}×{Math.round(d * 100) / 100}
271
- </span>
272
- )
273
- })()}
274
- </div>
275
- </PanelSection>
276
-
277
- <PanelSection title="Collections">
278
- <ActionGroup>
279
- <CollectionsPopover
280
- collectionIds={node.collectionIds}
281
- nodeId={selectedId as AnyNode['id']}
282
- >
283
- <ActionButton label="Manage collections…" />
284
- </CollectionsPopover>
285
- </ActionGroup>
286
- </PanelSection>
287
-
288
- <PanelSection title="Actions">
289
- <ActionGroup>
290
- <ActionButton icon={<Move className="h-3.5 w-3.5" />} label="Move" onClick={handleMove} />
291
- <ActionButton
292
- icon={<Copy className="h-3.5 w-3.5" />}
293
- label="Duplicate"
294
- onClick={handleDuplicate}
295
- />
296
- <ActionButton
297
- className="hover:bg-red-500/20"
298
- icon={<Trash2 className="h-3.5 w-3.5 text-red-400" />}
299
- label="Delete"
300
- onClick={handleDelete}
301
- />
302
- </ActionGroup>
303
- </PanelSection>
304
- </PanelWrapper>
305
- )
306
- }