@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.
- package/package.json +12 -13
- package/src/components/editor/alignment-3d-guide-layer.tsx +160 -0
- package/src/components/editor/custom-camera-controls.tsx +755 -25
- package/src/components/editor/editor-layout-mobile.tsx +3 -3
- package/src/components/editor/editor-layout-v2.tsx +62 -54
- package/src/components/editor/export-manager.tsx +32 -3
- package/src/components/editor/first-person/build-collider-world.test.ts +149 -0
- package/src/components/editor/first-person/build-collider-world.ts +203 -87
- package/src/components/editor/first-person/bvh-ecctrl.tsx +127 -62
- package/src/components/editor/first-person-controls.tsx +836 -31
- package/src/components/editor/floating-action-menu.tsx +287 -196
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floorplan-panel.tsx +5837 -12006
- package/src/components/editor/grid.tsx +51 -17
- package/src/components/editor/group-move-handle.tsx +316 -0
- package/src/components/editor/group-rotate-handle.tsx +382 -0
- package/src/components/editor/group-transform-shared.test.ts +205 -0
- package/src/components/editor/group-transform-shared.ts +256 -0
- package/src/components/editor/handles/handle-arrow.tsx +523 -0
- package/src/components/editor/handles/use-handle-drag.ts +213 -0
- package/src/components/editor/index.tsx +234 -190
- package/src/components/editor/measurement-pill.tsx +75 -0
- package/src/components/editor/node-arrow-handles.tsx +1453 -0
- package/src/components/editor/selection-manager.tsx +435 -158
- package/src/components/editor/site-edge-labels.tsx +44 -20
- package/src/components/editor/slab-hole-highlights.tsx +466 -0
- package/src/components/editor/snapshot-capture-overlay.tsx +539 -0
- package/src/components/editor/thumbnail-generator.tsx +27 -64
- package/src/components/editor/use-floorplan-background-placement.ts +122 -35
- package/src/components/editor/use-floorplan-hit-testing.ts +24 -0
- package/src/components/editor/use-floorplan-scene-data.ts +14 -2
- package/src/components/editor/wall-measurement-label.tsx +11 -6
- package/src/components/editor/wall-move-side-handles.tsx +855 -0
- package/src/components/editor/wall-opening-highlights.tsx +181 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +150 -0
- package/src/components/editor-2d/floorplan-action-menu-layer.tsx +6 -1
- package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +142 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +5 -3
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +227 -0
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +616 -0
- package/src/components/editor-2d/floorplan-render-context.tsx +62 -0
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +75 -0
- package/src/components/editor-2d/floorplan-wall-move-ghost-layer.tsx +47 -0
- package/src/components/editor-2d/renderers/floorplan-draft-layer.tsx +6 -1
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +266 -0
- package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +108 -61
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +59 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +1963 -0
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +29 -14
- package/src/components/editor-2d/svg-paths.ts +10 -2
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +30 -9
- package/src/components/systems/ceiling/ceiling-system.tsx +12 -8
- package/src/components/systems/roof/roof-edit-system.tsx +86 -12
- package/src/components/systems/stair/stair-edit-system.tsx +2 -2
- package/src/components/systems/zone/zone-label-editor-system.tsx +0 -0
- package/src/components/systems/zone/zone-system.tsx +0 -0
- package/src/components/tools/elevator/elevator-defaults.ts +8 -0
- package/src/components/tools/elevator/elevator-tool.tsx +349 -0
- package/src/components/tools/elevator/move-elevator-tool.tsx +252 -0
- package/src/components/tools/fence/fence-drafting.ts +64 -22
- package/src/components/tools/item/move-tool.tsx +40 -107
- package/src/components/tools/item/placement-math.test.ts +10 -0
- package/src/components/tools/item/placement-math.ts +33 -9
- package/src/components/tools/item/placement-strategies.ts +201 -25
- package/src/components/tools/item/placement-types.ts +10 -1
- package/src/components/tools/item/use-draft-node.ts +20 -2
- package/src/components/tools/item/use-placement-coordinator.tsx +700 -299
- package/src/components/tools/registry/move-registry-node-tool.tsx +602 -0
- package/src/components/tools/roof/roof-tool.tsx +108 -12
- package/src/components/tools/select/box-select-state.ts +77 -0
- package/src/components/tools/select/box-select-tool.tsx +324 -543
- package/src/components/tools/select/plane-box-select-tool.tsx +577 -0
- package/src/components/tools/select/screen-rectangle-selection.ts +84 -0
- package/src/components/tools/select/select-candidates.test.ts +109 -0
- package/src/components/tools/select/select-candidates.ts +85 -0
- package/src/components/tools/shared/affordance-dispatch.ts +30 -0
- package/src/components/tools/shared/drag-bounding-box.tsx +171 -0
- package/src/components/tools/shared/floor-stack-preview.ts +25 -0
- package/src/components/tools/shared/fresh-placement-visibility.ts +61 -0
- package/src/components/tools/shared/placement-box-geometry.ts +123 -0
- package/src/components/tools/shared/placement-box.tsx +123 -0
- package/src/components/tools/shared/polygon-editor.tsx +617 -167
- package/src/components/tools/shared/segment-angle.ts +57 -1
- package/src/components/tools/site/site-boundary-editor.tsx +464 -12
- package/src/components/tools/site/site-flag-model.ts +18 -0
- package/src/components/tools/stair/stair-defaults.ts +1 -1
- package/src/components/tools/stair/stair-tool.tsx +269 -35
- package/src/components/tools/tool-manager.tsx +172 -56
- package/src/components/tools/wall/wall-drafting.ts +98 -98
- package/src/components/tools/wall/wall-snap-geometry.test.ts +79 -0
- package/src/components/tools/wall/wall-snap-geometry.ts +270 -0
- package/src/components/tools/zone/zone-tool.tsx +36 -11
- package/src/components/ui/action-menu/action-button.tsx +21 -1
- package/src/components/ui/action-menu/camera-actions.tsx +0 -0
- package/src/components/ui/action-menu/control-modes.tsx +5 -76
- package/src/components/ui/action-menu/furnish-tools.tsx +6 -92
- package/src/components/ui/action-menu/index.tsx +33 -196
- package/src/components/ui/action-menu/structure-tools.tsx +13 -88
- package/src/components/ui/action-menu/view-toggles.tsx +262 -37
- package/src/components/ui/command-palette/editor-commands.tsx +24 -17
- package/src/components/ui/command-palette/index.tsx +6 -256
- package/src/components/ui/controls/material-paint-panel.tsx +83 -0
- package/src/components/ui/controls/material-picker.tsx +17 -35
- package/src/components/ui/controls/metric-control.tsx +33 -11
- package/src/components/ui/floating-level-selector.tsx +48 -11
- package/src/components/ui/helpers/helper-manager.tsx +21 -17
- package/src/components/ui/helpers/registered-tool-helper.tsx +25 -0
- package/src/components/ui/item-catalog/catalog-items.tsx +1743 -315
- package/src/components/ui/item-catalog/item-catalog.tsx +91 -46
- package/src/components/ui/level-duplicate-dialog.tsx +5 -6
- package/src/components/ui/panels/node-display.ts +1 -0
- package/src/components/ui/panels/paint-panel.tsx +66 -41
- package/src/components/ui/panels/panel-manager.tsx +33 -74
- package/src/components/ui/panels/panel-wrapper.tsx +217 -12
- package/src/components/ui/panels/parametric-inspector.tsx +469 -0
- package/src/components/ui/panels/reference-panel.tsx +28 -13
- package/src/components/ui/primitives/dropdown-menu.tsx +12 -8
- package/src/components/ui/primitives/number-input.tsx +1 -1
- package/src/components/ui/primitives/sidebar.tsx +0 -0
- package/src/components/ui/scene-loader.tsx +1 -3
- package/src/components/ui/sidebar/app-sidebar.tsx +0 -0
- package/src/components/ui/sidebar/icon-rail.tsx +0 -0
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +263 -0
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +404 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +59 -9
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +6 -1
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +259 -0
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -1
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +5 -2
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +83 -0
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +89 -0
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +75 -0
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +2 -2
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +83 -0
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +8 -12
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +2 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +93 -0
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +41 -5
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +128 -0
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +5 -2
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +83 -0
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +68 -34
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -1
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +52 -3
- package/src/components/ui/sidebar/tab-bar.tsx +61 -1
- package/src/components/ui/slider.tsx +1 -1
- package/src/components/viewer-overlay.tsx +169 -52
- package/src/components/viewer-zone-system.tsx +1 -1
- package/src/hooks/use-auto-save.ts +24 -3
- package/src/hooks/use-ceiling-events.ts +175 -0
- package/src/hooks/use-drag-action.ts +124 -0
- package/src/hooks/use-keyboard.ts +125 -62
- package/src/hooks/use-selection.ts +64 -0
- package/src/index.tsx +243 -4
- package/src/lib/constants.ts +6 -2
- package/src/lib/editor-api.ts +62 -0
- package/src/lib/elevator-support.ts +96 -0
- package/src/lib/floorplan/apply-alignment.ts +111 -0
- package/src/lib/floorplan/index.ts +7 -0
- package/src/lib/floorplan/selection-tool.ts +40 -0
- package/src/lib/floorplan/stairs.ts +12 -1
- package/src/lib/fresh-planar-placement.test.ts +102 -0
- package/src/lib/fresh-planar-placement.ts +63 -0
- package/src/lib/history.ts +2 -1
- package/src/lib/level-duplication.test.ts +0 -2
- package/src/lib/level-duplication.ts +55 -43
- package/src/lib/level-name.ts +11 -0
- package/src/lib/material-paint.ts +212 -20
- package/src/lib/placement-metadata.ts +29 -0
- package/src/lib/planar-cursor-placement.test.ts +43 -0
- package/src/lib/planar-cursor-placement.ts +42 -0
- package/src/lib/roof-duplication.ts +2 -2
- package/src/lib/scene-bounds.ts +1 -1
- package/src/lib/scene-clipboard.ts +267 -0
- package/src/lib/scene.ts +13 -4
- package/src/lib/sfx-bus.ts +25 -1
- package/src/lib/sfx-player.ts +75 -27
- package/src/lib/site-boundary.ts +1 -0
- package/src/lib/stair-duplication.ts +2 -2
- package/src/lib/stair-levels.test.ts +146 -0
- package/src/lib/stair-levels.ts +177 -0
- package/src/lib/world-grid-snap.ts +199 -0
- package/src/lib/zone-content.ts +130 -0
- package/src/store/use-alignment-guides.ts +21 -0
- package/src/store/use-editor.tsx +342 -91
- package/src/store/use-placement-preview.ts +31 -0
- package/src/store/use-wall-move-ghosts.ts +36 -0
- package/src/store/use-wall-snap-indicator.ts +32 -0
- package/tsconfig.json +2 -1
- package/src/components/editor/preset-thumbnail-generator.tsx +0 -125
- package/src/components/editor-2d/renderers/floorplan-roof-layer.tsx +0 -113
- package/src/components/feedback-dialog.tsx +0 -265
- package/src/components/pascal-radio.tsx +0 -280
- package/src/components/preview-button.tsx +0 -16
- package/src/components/tools/building/move-building-tool.tsx +0 -157
- package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +0 -43
- package/src/components/tools/ceiling/ceiling-hole-editor.tsx +0 -49
- package/src/components/tools/ceiling/ceiling-tool.tsx +0 -465
- package/src/components/tools/ceiling/move-ceiling-tool.tsx +0 -257
- package/src/components/tools/column/column-tool.tsx +0 -97
- package/src/components/tools/column/move-column-tool.tsx +0 -105
- package/src/components/tools/door/door-math.ts +0 -110
- package/src/components/tools/door/door-tool.tsx +0 -324
- package/src/components/tools/door/move-door-tool.tsx +0 -412
- package/src/components/tools/fence/curve-fence-tool.tsx +0 -179
- package/src/components/tools/fence/fence-tool.tsx +0 -346
- package/src/components/tools/fence/move-fence-endpoint-tool.tsx +0 -438
- package/src/components/tools/fence/move-fence-tool.tsx +0 -298
- package/src/components/tools/item/item-tool.tsx +0 -26
- package/src/components/tools/roof/move-roof-tool.tsx +0 -359
- package/src/components/tools/slab/move-slab-tool.tsx +0 -182
- package/src/components/tools/slab/slab-boundary-editor.tsx +0 -43
- package/src/components/tools/slab/slab-hole-editor.tsx +0 -49
- package/src/components/tools/slab/slab-tool.tsx +0 -322
- package/src/components/tools/spawn/move-spawn-tool.tsx +0 -101
- package/src/components/tools/spawn/spawn-tool.tsx +0 -130
- package/src/components/tools/wall/curve-wall-tool.tsx +0 -176
- package/src/components/tools/wall/move-wall-endpoint-tool.tsx +0 -423
- package/src/components/tools/wall/move-wall-tool.tsx +0 -356
- package/src/components/tools/wall/wall-tool.tsx +0 -332
- package/src/components/tools/window/move-window-tool.tsx +0 -447
- package/src/components/tools/window/window-math.ts +0 -117
- package/src/components/tools/window/window-tool.tsx +0 -332
- package/src/components/ui/helpers/ceiling-helper.tsx +0 -20
- package/src/components/ui/helpers/slab-helper.tsx +0 -20
- package/src/components/ui/helpers/wall-helper.tsx +0 -20
- package/src/components/ui/panels/ceiling-panel.tsx +0 -250
- package/src/components/ui/panels/column-panel.tsx +0 -715
- package/src/components/ui/panels/door-panel.tsx +0 -1292
- package/src/components/ui/panels/fence-panel.tsx +0 -227
- package/src/components/ui/panels/item-panel.tsx +0 -306
- package/src/components/ui/panels/presets/presets-popover.tsx +0 -511
- package/src/components/ui/panels/roof-panel.tsx +0 -230
- package/src/components/ui/panels/roof-segment-panel.tsx +0 -311
- package/src/components/ui/panels/slab-panel.tsx +0 -250
- package/src/components/ui/panels/spawn-panel.tsx +0 -155
- package/src/components/ui/panels/stair-panel.tsx +0 -525
- package/src/components/ui/panels/stair-segment-panel.tsx +0 -325
- package/src/components/ui/panels/wall-panel.tsx +0 -177
- package/src/components/ui/panels/window-panel.tsx +0 -960
- package/src/components/ui/viewer-toolbar.tsx +0 -436
- package/src/contexts/presets-context.tsx +0 -121
- package/src/hooks/use-contextual-tools.ts +0 -61
|
@@ -9,17 +9,58 @@ import {
|
|
|
9
9
|
TooltipContent,
|
|
10
10
|
TooltipTrigger,
|
|
11
11
|
} from './../../../components/ui/primitives/tooltip'
|
|
12
|
+
import { triggerSFX } from './../../../lib/sfx-bus'
|
|
12
13
|
import { cn } from './../../../lib/utils'
|
|
13
14
|
import useEditor, { type CatalogCategory } from './../../../store/use-editor'
|
|
14
15
|
import { CATALOG_ITEMS } from './catalog-items'
|
|
15
16
|
|
|
16
|
-
export function ItemCatalog({
|
|
17
|
+
export function ItemCatalog({
|
|
18
|
+
category,
|
|
19
|
+
items: itemsOverride,
|
|
20
|
+
activePlacementTag = null,
|
|
21
|
+
activeFunctionalTag = null,
|
|
22
|
+
search = '',
|
|
23
|
+
overrideItems,
|
|
24
|
+
leadingTile,
|
|
25
|
+
emptyState,
|
|
26
|
+
}: {
|
|
27
|
+
category: CatalogCategory
|
|
28
|
+
items?: AssetInput[]
|
|
29
|
+
activePlacementTag?: string | null
|
|
30
|
+
activeFunctionalTag?: string | null
|
|
31
|
+
search?: string
|
|
32
|
+
/** When set, bypasses all filtering and displays these items directly (used for server search results) */
|
|
33
|
+
overrideItems?: AssetInput[]
|
|
34
|
+
/** Rendered as the first grid cell, always visible when there are items. */
|
|
35
|
+
leadingTile?: React.ReactNode
|
|
36
|
+
/** Rendered when there are no items to show. Replaces the empty grid. */
|
|
37
|
+
emptyState?: React.ReactNode
|
|
38
|
+
}) {
|
|
17
39
|
const selectedItem = useEditor((state) => state.selectedItem)
|
|
18
40
|
const setSelectedItem = useEditor((state) => state.setSelectedItem)
|
|
41
|
+
const setMode = useEditor((state) => state.setMode)
|
|
42
|
+
const setTool = useEditor((state) => state.setTool)
|
|
19
43
|
|
|
20
|
-
const
|
|
44
|
+
const sourceItems = itemsOverride ?? CATALOG_ITEMS
|
|
45
|
+
// Server-provided results bypass all local filtering; otherwise filter by category/search/tags
|
|
46
|
+
const filteredItems =
|
|
47
|
+
overrideItems ??
|
|
48
|
+
(() => {
|
|
49
|
+
const categoryItems = search
|
|
50
|
+
? sourceItems
|
|
51
|
+
: sourceItems.filter((item) => item.category === category)
|
|
52
|
+
return categoryItems.filter((item) => {
|
|
53
|
+
const tags = item.tags ?? []
|
|
54
|
+
if (activePlacementTag && !tags.includes(activePlacementTag)) return false
|
|
55
|
+
if (activeFunctionalTag && !tags.includes(activeFunctionalTag)) return false
|
|
56
|
+
if (search && !item.name.toLowerCase().includes(search.toLowerCase())) return false
|
|
57
|
+
return true
|
|
58
|
+
})
|
|
59
|
+
})()
|
|
21
60
|
|
|
22
|
-
|
|
61
|
+
const categoryItems = filteredItems
|
|
62
|
+
|
|
63
|
+
// Auto-select first item if current selection is not in the filtered list
|
|
23
64
|
useEffect(() => {
|
|
24
65
|
const isCurrentItemInCategory = categoryItems.some((item) => item.src === selectedItem?.src)
|
|
25
66
|
if (!isCurrentItemInCategory && categoryItems.length > 0) {
|
|
@@ -27,58 +68,62 @@ export function ItemCatalog({ category }: { category: CatalogCategory }) {
|
|
|
27
68
|
}
|
|
28
69
|
}, [categoryItems, selectedItem?.src, setSelectedItem])
|
|
29
70
|
|
|
30
|
-
// Get attachment icon based on attachTo type
|
|
31
71
|
const getAttachmentIcon = (attachTo: AssetInput['attachTo']) => {
|
|
32
|
-
if (attachTo === 'wall' || attachTo === 'wall-side')
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
if (attachTo === 'ceiling') {
|
|
36
|
-
return '/icons/ceiling.png'
|
|
37
|
-
}
|
|
72
|
+
if (attachTo === 'wall' || attachTo === 'wall-side') return '/icons/wall.png'
|
|
73
|
+
if (attachTo === 'ceiling') return '/icons/ceiling.png'
|
|
38
74
|
return null
|
|
39
75
|
}
|
|
40
76
|
|
|
77
|
+
if (filteredItems.length === 0 && emptyState) {
|
|
78
|
+
return <>{emptyState}</>
|
|
79
|
+
}
|
|
80
|
+
|
|
41
81
|
return (
|
|
42
|
-
<div
|
|
43
|
-
|
|
82
|
+
<div
|
|
83
|
+
className="grid gap-2"
|
|
84
|
+
style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(90px, 1fr))' }}
|
|
85
|
+
>
|
|
86
|
+
{leadingTile}
|
|
87
|
+
{filteredItems.map((item, index) => {
|
|
44
88
|
const isSelected = selectedItem?.src === item?.src
|
|
45
89
|
const attachmentIcon = getAttachmentIcon(item?.attachTo)
|
|
46
90
|
return (
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
91
|
+
<button
|
|
92
|
+
className={cn(
|
|
93
|
+
'group relative flex flex-col gap-1.5 rounded-xl p-1.5 transition-colors hover:cursor-pointer hover:bg-sidebar-accent',
|
|
94
|
+
isSelected && 'bg-sidebar-accent ring-2 ring-primary-foreground',
|
|
95
|
+
)}
|
|
96
|
+
key={index}
|
|
97
|
+
onClick={() => {
|
|
98
|
+
triggerSFX('sfx:menu-click')
|
|
99
|
+
setSelectedItem(item)
|
|
100
|
+
setTool('item')
|
|
101
|
+
setMode('build')
|
|
102
|
+
}}
|
|
103
|
+
onMouseEnter={() => triggerSFX('sfx:menu-hover')}
|
|
104
|
+
type="button"
|
|
105
|
+
>
|
|
106
|
+
<div className="relative aspect-square w-full overflow-hidden rounded-lg">
|
|
107
|
+
<img
|
|
108
|
+
alt={item.name}
|
|
109
|
+
className="h-full w-full object-cover"
|
|
110
|
+
loading="eager"
|
|
111
|
+
src={resolveCdnUrl(item.thumbnail) || ''}
|
|
112
|
+
/>
|
|
113
|
+
{attachmentIcon && (
|
|
114
|
+
<div className="absolute right-1 bottom-1 flex h-4 w-4 items-center justify-center rounded bg-black/60">
|
|
115
|
+
<img
|
|
116
|
+
alt={item.attachTo === 'ceiling' ? 'Ceiling attachment' : 'Wall attachment'}
|
|
117
|
+
className="h-4 w-4"
|
|
118
|
+
src={attachmentIcon}
|
|
119
|
+
/>
|
|
120
|
+
</div>
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
123
|
+
<span className="truncate px-0.5 text-left font-medium text-[11px] text-muted-foreground group-hover:text-foreground">
|
|
79
124
|
{item.name}
|
|
80
|
-
</
|
|
81
|
-
</
|
|
125
|
+
</span>
|
|
126
|
+
</button>
|
|
82
127
|
)
|
|
83
128
|
})}
|
|
84
129
|
</div>
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import type { LevelNode } from '@pascal-app/core'
|
|
4
4
|
import { useEffect, useState } from 'react'
|
|
5
|
-
import { cn } from '../../lib/utils'
|
|
6
5
|
import type { LevelDuplicatePreset } from '../../lib/level-duplication'
|
|
6
|
+
import { getLevelDisplayName } from '../../lib/level-name'
|
|
7
|
+
import { cn } from '../../lib/utils'
|
|
7
8
|
import {
|
|
8
9
|
Dialog,
|
|
9
10
|
DialogContent,
|
|
@@ -42,7 +43,7 @@ const DUPLICATE_PRESETS: Array<{
|
|
|
42
43
|
|
|
43
44
|
function getLevelLabel(level: LevelNode | null) {
|
|
44
45
|
if (!level) return 'this level'
|
|
45
|
-
return level
|
|
46
|
+
return getLevelDisplayName(level)
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
export function LevelDuplicateDialog({
|
|
@@ -69,9 +70,7 @@ export function LevelDuplicateDialog({
|
|
|
69
70
|
<DialogContent className="sm:max-w-md" showCloseButton={false}>
|
|
70
71
|
<DialogHeader>
|
|
71
72
|
<DialogTitle>Duplicate Level</DialogTitle>
|
|
72
|
-
<DialogDescription>
|
|
73
|
-
Choose what to copy from {getLevelLabel(level)}.
|
|
74
|
-
</DialogDescription>
|
|
73
|
+
<DialogDescription>Choose what to copy from {getLevelLabel(level)}.</DialogDescription>
|
|
75
74
|
</DialogHeader>
|
|
76
75
|
|
|
77
76
|
<div className="grid gap-2">
|
|
@@ -95,7 +94,7 @@ export function LevelDuplicateDialog({
|
|
|
95
94
|
|
|
96
95
|
<DialogFooter>
|
|
97
96
|
<button
|
|
98
|
-
className="cursor-pointer rounded-md px-4 py-2 text-
|
|
97
|
+
className="cursor-pointer rounded-md px-4 py-2 text-muted-foreground text-sm transition-colors hover:bg-accent"
|
|
99
98
|
onClick={() => onOpenChange(false)}
|
|
100
99
|
type="button"
|
|
101
100
|
>
|
|
@@ -13,6 +13,7 @@ const TYPE_DEFAULTS: Record<string, NodeDisplay> = {
|
|
|
13
13
|
slab: { icon: '/icons/floor.png', label: 'Slab' },
|
|
14
14
|
ceiling: { icon: '/icons/ceiling.png', label: 'Ceiling' },
|
|
15
15
|
column: { icon: '/icons/column.png', label: 'Column' },
|
|
16
|
+
elevator: { icon: '/icons/elevator.png', label: 'Elevator' },
|
|
16
17
|
fence: { icon: '/icons/fence.png', label: 'Fence' },
|
|
17
18
|
roof: { icon: '/icons/roof.png', label: 'Roof' },
|
|
18
19
|
'roof-segment': { icon: '/icons/roof.png', label: 'Roof segment' },
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import useEditor from '../../../store/use-editor'
|
|
4
|
-
import { SliderControl } from '../controls/slider-control'
|
|
5
|
-
import { Input } from '../primitives/input'
|
|
6
4
|
import { PanelSection } from '../controls/panel-section'
|
|
5
|
+
import { Input } from '../primitives/input'
|
|
7
6
|
import { PanelWrapper } from './panel-wrapper'
|
|
8
7
|
|
|
9
8
|
function buildDefaultCustomMaterial() {
|
|
@@ -53,11 +52,7 @@ export function PaintPanel() {
|
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
return (
|
|
56
|
-
<PanelWrapper
|
|
57
|
-
onClose={() => setPaintPanelOpen(false)}
|
|
58
|
-
title="Material"
|
|
59
|
-
width={320}
|
|
60
|
-
>
|
|
55
|
+
<PanelWrapper onClose={() => setPaintPanelOpen(false)} title="Material" width={320}>
|
|
61
56
|
<PanelSection title="Custom Material">
|
|
62
57
|
<div className="space-y-3">
|
|
63
58
|
<div className="space-y-2">
|
|
@@ -78,41 +73,71 @@ export function PaintPanel() {
|
|
|
78
73
|
</div>
|
|
79
74
|
</div>
|
|
80
75
|
|
|
81
|
-
<div className="space-y-
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
76
|
+
<div className="space-y-2">
|
|
77
|
+
<div className="flex items-center justify-between">
|
|
78
|
+
<label className="font-medium text-muted-foreground text-xs uppercase tracking-[0.12em]">
|
|
79
|
+
Roughness
|
|
80
|
+
</label>
|
|
81
|
+
<span className="font-mono text-muted-foreground text-xs">
|
|
82
|
+
{currentProps.roughness.toFixed(2)}
|
|
83
|
+
</span>
|
|
84
|
+
</div>
|
|
85
|
+
<input
|
|
86
|
+
className="h-2 w-full cursor-pointer appearance-none rounded-full bg-accent"
|
|
87
|
+
max={1}
|
|
88
|
+
min={0}
|
|
89
|
+
onChange={(e) =>
|
|
90
|
+
updateCustomMaterial({ roughness: Number.parseFloat(e.target.value) })
|
|
91
|
+
}
|
|
92
|
+
step={0.01}
|
|
93
|
+
type="range"
|
|
94
|
+
value={currentProps.roughness}
|
|
95
|
+
/>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<div className="space-y-2">
|
|
99
|
+
<div className="flex items-center justify-between">
|
|
100
|
+
<label className="font-medium text-muted-foreground text-xs uppercase tracking-[0.12em]">
|
|
101
|
+
Metalness
|
|
102
|
+
</label>
|
|
103
|
+
<span className="font-mono text-muted-foreground text-xs">
|
|
104
|
+
{currentProps.metalness.toFixed(2)}
|
|
105
|
+
</span>
|
|
106
|
+
</div>
|
|
107
|
+
<input
|
|
108
|
+
className="h-2 w-full cursor-pointer appearance-none rounded-full bg-accent"
|
|
109
|
+
max={1}
|
|
110
|
+
min={0}
|
|
111
|
+
onChange={(e) =>
|
|
112
|
+
updateCustomMaterial({ metalness: Number.parseFloat(e.target.value) })
|
|
113
|
+
}
|
|
114
|
+
step={0.01}
|
|
115
|
+
type="range"
|
|
116
|
+
value={currentProps.metalness}
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<div className="space-y-2">
|
|
121
|
+
<div className="flex items-center justify-between">
|
|
122
|
+
<label className="font-medium text-muted-foreground text-xs uppercase tracking-[0.12em]">
|
|
123
|
+
Opacity
|
|
124
|
+
</label>
|
|
125
|
+
<span className="font-mono text-muted-foreground text-xs">
|
|
126
|
+
{currentProps.opacity.toFixed(2)}
|
|
127
|
+
</span>
|
|
115
128
|
</div>
|
|
129
|
+
<input
|
|
130
|
+
className="h-2 w-full cursor-pointer appearance-none rounded-full bg-accent"
|
|
131
|
+
max={1}
|
|
132
|
+
min={0}
|
|
133
|
+
onChange={(e) => {
|
|
134
|
+
const opacity = Number.parseFloat(e.target.value)
|
|
135
|
+
updateCustomMaterial({ opacity }, opacity < 1 || currentProps.transparent)
|
|
136
|
+
}}
|
|
137
|
+
step={0.01}
|
|
138
|
+
type="range"
|
|
139
|
+
value={currentProps.opacity}
|
|
140
|
+
/>
|
|
116
141
|
</div>
|
|
117
142
|
|
|
118
143
|
<div className="space-y-2">
|
|
@@ -5,8 +5,11 @@ import {
|
|
|
5
5
|
type AnyNodeId,
|
|
6
6
|
type BuildingNode,
|
|
7
7
|
type CeilingNode,
|
|
8
|
+
type ChimneyNode,
|
|
8
9
|
type ColumnNode,
|
|
9
10
|
type DoorNode,
|
|
11
|
+
type DormerNode,
|
|
12
|
+
type ElevatorNode,
|
|
10
13
|
type FenceNode,
|
|
11
14
|
type ItemNode,
|
|
12
15
|
type RoofNode,
|
|
@@ -23,31 +26,22 @@ import { useCallback, useEffect, useState } from 'react'
|
|
|
23
26
|
import { useIsMobile } from '../../../hooks/use-mobile'
|
|
24
27
|
import { sfxEmitter } from '../../../lib/sfx-bus'
|
|
25
28
|
import useEditor from '../../../store/use-editor'
|
|
26
|
-
import { CeilingPanel } from './ceiling-panel'
|
|
27
|
-
import { ColumnPanel } from './column-panel'
|
|
28
|
-
import { DoorPanel } from './door-panel'
|
|
29
|
-
import { FencePanel } from './fence-panel'
|
|
30
|
-
import { ItemPanel } from './item-panel'
|
|
31
29
|
import { MobilePanelSheet } from './mobile-panel-sheet'
|
|
32
30
|
import { MobileSelectionBar } from './mobile-selection-bar'
|
|
33
31
|
import { getNodeDisplay } from './node-display'
|
|
34
32
|
import { PaintPanel } from './paint-panel'
|
|
33
|
+
import { ParametricInspector } from './parametric-inspector'
|
|
35
34
|
import { ReferencePanel } from './reference-panel'
|
|
36
|
-
import { RoofPanel } from './roof-panel'
|
|
37
|
-
import { RoofSegmentPanel } from './roof-segment-panel'
|
|
38
|
-
import { SlabPanel } from './slab-panel'
|
|
39
|
-
import { SpawnPanel } from './spawn-panel'
|
|
40
|
-
import { StairPanel } from './stair-panel'
|
|
41
|
-
import { StairSegmentPanel } from './stair-segment-panel'
|
|
42
|
-
import { WallPanel } from './wall-panel'
|
|
43
|
-
import { WindowPanel } from './window-panel'
|
|
44
35
|
|
|
45
36
|
type MovableNode =
|
|
46
37
|
| ItemNode
|
|
47
38
|
| WindowNode
|
|
48
39
|
| DoorNode
|
|
40
|
+
| ElevatorNode
|
|
49
41
|
| CeilingNode
|
|
42
|
+
| ChimneyNode
|
|
50
43
|
| ColumnNode
|
|
44
|
+
| DormerNode
|
|
51
45
|
| SlabNode
|
|
52
46
|
| WallNode
|
|
53
47
|
| FenceNode
|
|
@@ -61,8 +55,11 @@ const MOVABLE_TYPES = new Set<string>([
|
|
|
61
55
|
'item',
|
|
62
56
|
'window',
|
|
63
57
|
'door',
|
|
58
|
+
'elevator',
|
|
64
59
|
'ceiling',
|
|
60
|
+
'chimney',
|
|
65
61
|
'column',
|
|
62
|
+
'dormer',
|
|
66
63
|
'slab',
|
|
67
64
|
'wall',
|
|
68
65
|
'fence',
|
|
@@ -77,36 +74,17 @@ function isMovableNode(node: AnyNode | null): node is MovableNode {
|
|
|
77
74
|
return !!node && MOVABLE_TYPES.has(node.type)
|
|
78
75
|
}
|
|
79
76
|
|
|
80
|
-
function panelForType(type: string | null) {
|
|
77
|
+
function panelForType(type: string | null, footer?: React.ReactNode) {
|
|
81
78
|
if (!type) return null
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
case 'stair-segment':
|
|
92
|
-
return <StairSegmentPanel />
|
|
93
|
-
case 'slab':
|
|
94
|
-
return <SlabPanel />
|
|
95
|
-
case 'ceiling':
|
|
96
|
-
return <CeilingPanel />
|
|
97
|
-
case 'column':
|
|
98
|
-
return <ColumnPanel />
|
|
99
|
-
case 'wall':
|
|
100
|
-
return <WallPanel />
|
|
101
|
-
case 'fence':
|
|
102
|
-
return <FencePanel />
|
|
103
|
-
case 'door':
|
|
104
|
-
return <DoorPanel />
|
|
105
|
-
case 'window':
|
|
106
|
-
return <WindowPanel />
|
|
107
|
-
default:
|
|
108
|
-
return null
|
|
109
|
-
}
|
|
79
|
+
// Every kind now renders through `<ParametricInspector>`, which either
|
|
80
|
+
// composes auto-derived editors from `parametrics.groups` or lazy-
|
|
81
|
+
// loads the kind-owned panel via `parametrics.customPanel`. The
|
|
82
|
+
// hardcoded switch is gone — all per-kind panel layout lives in
|
|
83
|
+
// `nodes/src/<kind>/panel.tsx`. The `type` arg is preserved for
|
|
84
|
+
// future cases where we might want a non-registry fallback (e.g.
|
|
85
|
+
// reference scale, paint mode); leave the function shape intact.
|
|
86
|
+
void type
|
|
87
|
+
return <ParametricInspector footer={footer} />
|
|
110
88
|
}
|
|
111
89
|
|
|
112
90
|
function MobilePanelLayer({
|
|
@@ -190,9 +168,11 @@ function MobilePanelLayer({
|
|
|
190
168
|
)
|
|
191
169
|
}
|
|
192
170
|
|
|
193
|
-
export function PanelManager() {
|
|
171
|
+
export function PanelManager({ inspectorFooter }: { inspectorFooter?: React.ReactNode }) {
|
|
194
172
|
const isMobile = useIsMobile()
|
|
195
173
|
const selectedIds = useViewer((s) => s.selection.selectedIds)
|
|
174
|
+
const selectedZoneId = useViewer((s) => s.selection.zoneId)
|
|
175
|
+
const setSelection = useViewer((s) => s.setSelection)
|
|
196
176
|
const selectedReferenceId = useEditor((s) => s.selectedReferenceId)
|
|
197
177
|
const isPaintPanelOpen = useEditor((s) => s.isPaintPanelOpen)
|
|
198
178
|
const mode = useEditor((s) => s.mode)
|
|
@@ -237,37 +217,16 @@ export function PanelManager() {
|
|
|
237
217
|
return <PaintPanel />
|
|
238
218
|
}
|
|
239
219
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
case 'stair':
|
|
250
|
-
return <StairPanel />
|
|
251
|
-
case 'stair-segment':
|
|
252
|
-
return <StairSegmentPanel />
|
|
253
|
-
case 'slab':
|
|
254
|
-
return <SlabPanel />
|
|
255
|
-
case 'spawn':
|
|
256
|
-
return <SpawnPanel />
|
|
257
|
-
case 'ceiling':
|
|
258
|
-
return <CeilingPanel />
|
|
259
|
-
case 'column':
|
|
260
|
-
return <ColumnPanel />
|
|
261
|
-
case 'wall':
|
|
262
|
-
return <WallPanel />
|
|
263
|
-
case 'fence':
|
|
264
|
-
return <FencePanel />
|
|
265
|
-
case 'door':
|
|
266
|
-
return <DoorPanel />
|
|
267
|
-
case 'window':
|
|
268
|
-
return <WindowPanel />
|
|
269
|
-
}
|
|
220
|
+
if (selectedZoneId && selectedIds.length === 0) {
|
|
221
|
+
return (
|
|
222
|
+
<ParametricInspector
|
|
223
|
+
footer={inspectorFooter}
|
|
224
|
+
key={selectedZoneId}
|
|
225
|
+
nodeId={selectedZoneId as AnyNodeId}
|
|
226
|
+
onClose={() => setSelection({ zoneId: null })}
|
|
227
|
+
/>
|
|
228
|
+
)
|
|
270
229
|
}
|
|
271
230
|
|
|
272
|
-
return
|
|
231
|
+
return panelForType(selectedNodeType, inspectorFooter)
|
|
273
232
|
}
|