@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
|
@@ -38,7 +38,7 @@ export const FenceTreeNode = memo(function FenceTreeNode({
|
|
|
38
38
|
|
|
39
39
|
return (
|
|
40
40
|
<TreeNodeWrapper
|
|
41
|
-
actions={<TreeNodeActions
|
|
41
|
+
actions={<TreeNodeActions nodeId={node.id} />}
|
|
42
42
|
depth={depth}
|
|
43
43
|
expanded={false}
|
|
44
44
|
hasChildren={false}
|
|
@@ -53,7 +53,7 @@ export const FenceTreeNode = memo(function FenceTreeNode({
|
|
|
53
53
|
<InlineRenameInput
|
|
54
54
|
defaultName="Fence"
|
|
55
55
|
isEditing={isEditing}
|
|
56
|
-
|
|
56
|
+
nodeId={node.id}
|
|
57
57
|
onStartEditing={() => setIsEditing(true)}
|
|
58
58
|
onStopEditing={() => setIsEditing(false)}
|
|
59
59
|
/>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { type AnyNodeId, type GutterNode, useScene } from '@pascal-app/core'
|
|
2
|
+
import { useViewer } from '@pascal-app/viewer'
|
|
3
|
+
import Image from 'next/image'
|
|
4
|
+
import { memo, useCallback, useState } from 'react'
|
|
5
|
+
import useEditor from './../../../../../store/use-editor'
|
|
6
|
+
import { InlineRenameInput } from './inline-rename-input'
|
|
7
|
+
import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
|
|
8
|
+
import { TreeNodeActions } from './tree-node-actions'
|
|
9
|
+
|
|
10
|
+
interface GutterTreeNodeProps {
|
|
11
|
+
nodeId: AnyNodeId
|
|
12
|
+
depth: number
|
|
13
|
+
isLast?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const GutterTreeNode = memo(function GutterTreeNode({
|
|
17
|
+
nodeId,
|
|
18
|
+
depth,
|
|
19
|
+
isLast,
|
|
20
|
+
}: GutterTreeNodeProps) {
|
|
21
|
+
const [isEditing, setIsEditing] = useState(false)
|
|
22
|
+
const isVisible = useScene((s) => s.nodes[nodeId]?.visible !== false)
|
|
23
|
+
const node = useScene((s) => s.nodes[nodeId] as GutterNode | undefined)
|
|
24
|
+
const isSelected = useViewer((state) => state.selection.selectedIds.includes(nodeId))
|
|
25
|
+
const isHovered = useViewer((state) => state.hoveredId === nodeId)
|
|
26
|
+
const setSelection = useViewer((state) => state.setSelection)
|
|
27
|
+
const setHoveredId = useViewer((state) => state.setHoveredId)
|
|
28
|
+
|
|
29
|
+
const handleClick = useCallback(
|
|
30
|
+
(e: React.MouseEvent) => {
|
|
31
|
+
e.stopPropagation()
|
|
32
|
+
const handled = handleTreeSelection(
|
|
33
|
+
e,
|
|
34
|
+
nodeId,
|
|
35
|
+
useViewer.getState().selection.selectedIds,
|
|
36
|
+
setSelection,
|
|
37
|
+
)
|
|
38
|
+
if (!handled && useEditor.getState().phase === 'furnish') {
|
|
39
|
+
useEditor.getState().setPhase('structure')
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
[nodeId, setSelection],
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
const defaultName = node?.name || 'Gutter'
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<TreeNodeWrapper
|
|
49
|
+
actions={<TreeNodeActions nodeId={nodeId} />}
|
|
50
|
+
depth={depth}
|
|
51
|
+
expanded={false}
|
|
52
|
+
hasChildren={false}
|
|
53
|
+
icon={
|
|
54
|
+
<Image
|
|
55
|
+
alt=""
|
|
56
|
+
className="object-contain opacity-60"
|
|
57
|
+
height={14}
|
|
58
|
+
src="/icons/roof.png"
|
|
59
|
+
width={14}
|
|
60
|
+
/>
|
|
61
|
+
}
|
|
62
|
+
isHovered={isHovered}
|
|
63
|
+
isLast={isLast}
|
|
64
|
+
isSelected={isSelected}
|
|
65
|
+
isVisible={isVisible}
|
|
66
|
+
label={
|
|
67
|
+
<InlineRenameInput
|
|
68
|
+
defaultName={defaultName}
|
|
69
|
+
isEditing={isEditing}
|
|
70
|
+
nodeId={nodeId}
|
|
71
|
+
onStartEditing={() => setIsEditing(true)}
|
|
72
|
+
onStopEditing={() => setIsEditing(false)}
|
|
73
|
+
/>
|
|
74
|
+
}
|
|
75
|
+
nodeId={nodeId}
|
|
76
|
+
onClick={handleClick}
|
|
77
|
+
onDoubleClick={() => focusTreeNode(nodeId)}
|
|
78
|
+
onMouseEnter={() => setHoveredId(nodeId)}
|
|
79
|
+
onMouseLeave={() => setHoveredId(null)}
|
|
80
|
+
onToggle={() => {}}
|
|
81
|
+
/>
|
|
82
|
+
)
|
|
83
|
+
})
|
|
@@ -3,9 +3,9 @@ import {
|
|
|
3
3
|
type AnyNodeId,
|
|
4
4
|
type BuildingNode,
|
|
5
5
|
emitter,
|
|
6
|
-
GuideNode,
|
|
6
|
+
type GuideNode,
|
|
7
7
|
LevelNode,
|
|
8
|
-
ScanNode,
|
|
8
|
+
type ScanNode,
|
|
9
9
|
type SiteNode,
|
|
10
10
|
useScene,
|
|
11
11
|
type ZoneNode,
|
|
@@ -32,14 +32,13 @@ import {
|
|
|
32
32
|
PopoverContent,
|
|
33
33
|
PopoverTrigger,
|
|
34
34
|
} from './../../../../../components/ui/primitives/popover'
|
|
35
|
-
import { deleteLevelWithFallbackSelection } from './../../../../../lib/level-selection'
|
|
36
|
-
import { createLocalGuideImage } from './../../../../../lib/local-guide-image'
|
|
37
|
-
|
|
38
35
|
import {
|
|
39
36
|
buildLevelDuplicateCreateOps,
|
|
40
37
|
type LevelDuplicatePreset,
|
|
41
38
|
} from './../../../../../lib/level-duplication'
|
|
42
|
-
|
|
39
|
+
import { getDefaultLevelName } from './../../../../../lib/level-name'
|
|
40
|
+
import { deleteLevelWithFallbackSelection } from './../../../../../lib/level-selection'
|
|
41
|
+
import { createLocalGuideImage } from './../../../../../lib/local-guide-image'
|
|
43
42
|
import { cn } from './../../../../../lib/utils'
|
|
44
43
|
import useEditor from './../../../../../store/use-editor'
|
|
45
44
|
import { useUploadStore } from '../../../../../store/use-upload'
|
|
@@ -725,7 +724,7 @@ const LevelItem = memo(function LevelItem({
|
|
|
725
724
|
src="/icons/level.png"
|
|
726
725
|
/>
|
|
727
726
|
<InlineRenameInput
|
|
728
|
-
defaultName={
|
|
727
|
+
defaultName={getDefaultLevelName(level.level)}
|
|
729
728
|
isEditing={isEditing}
|
|
730
729
|
nodeId={level.id}
|
|
731
730
|
onStartEditing={() => setIsEditing(true)}
|
|
@@ -1543,10 +1542,7 @@ export function SitePanel({ projectId, onUploadAsset, onDeleteAsset }: SitePanel
|
|
|
1543
1542
|
useShallow((s) => {
|
|
1544
1543
|
if (!siteNode) return []
|
|
1545
1544
|
return siteNode.children
|
|
1546
|
-
.map((
|
|
1547
|
-
const id = typeof child === 'string' ? child : child.id
|
|
1548
|
-
return s.nodes[id] as BuildingNode | undefined
|
|
1549
|
-
})
|
|
1545
|
+
.map((childId) => s.nodes[childId as AnyNodeId] as BuildingNode | undefined)
|
|
1550
1546
|
.filter((node): node is BuildingNode => node?.type === 'building')
|
|
1551
1547
|
}),
|
|
1552
1548
|
)
|
|
@@ -1573,7 +1569,7 @@ export function SitePanel({ projectId, onUploadAsset, onDeleteAsset }: SitePanel
|
|
|
1573
1569
|
'h-5 w-5 object-contain transition-all',
|
|
1574
1570
|
phase !== 'site' && 'opacity-60 grayscale',
|
|
1575
1571
|
)}
|
|
1576
|
-
src="/icons/site.png"
|
|
1572
|
+
src="/icons/site-flag.png"
|
|
1577
1573
|
/>
|
|
1578
1574
|
<span className="font-medium text-sm">{siteNode.name || 'Site'}</span>
|
|
1579
1575
|
</div>
|
|
@@ -3,6 +3,7 @@ import { useViewer } from '@pascal-app/viewer'
|
|
|
3
3
|
import { Layers } from 'lucide-react'
|
|
4
4
|
import { memo, useCallback, useState } from 'react'
|
|
5
5
|
import { useShallow } from 'zustand/react/shallow'
|
|
6
|
+
import { getDefaultLevelName } from '../../../../../lib/level-name'
|
|
6
7
|
import { InlineRenameInput } from './inline-rename-input'
|
|
7
8
|
import { focusTreeNode, TreeNode, TreeNodeWrapper } from './tree-node'
|
|
8
9
|
import { TreeNodeActions } from './tree-node-actions'
|
|
@@ -35,7 +36,7 @@ export const LevelTreeNode = memo(function LevelTreeNode({
|
|
|
35
36
|
const handleStartEditing = useCallback(() => setIsEditing(true), [])
|
|
36
37
|
const handleStopEditing = useCallback(() => setIsEditing(false), [])
|
|
37
38
|
|
|
38
|
-
const defaultName =
|
|
39
|
+
const defaultName = getDefaultLevelName(level)
|
|
39
40
|
|
|
40
41
|
return (
|
|
41
42
|
<TreeNodeWrapper
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { type AnyNodeId, nodeRegistry, useScene } from '@pascal-app/core'
|
|
2
|
+
import { useViewer } from '@pascal-app/viewer'
|
|
3
|
+
import Image from 'next/image'
|
|
4
|
+
import { memo, useCallback, useState } from 'react'
|
|
5
|
+
import useEditor from './../../../../../store/use-editor'
|
|
6
|
+
import { InlineRenameInput } from './inline-rename-input'
|
|
7
|
+
import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
|
|
8
|
+
import { TreeNodeActions } from './tree-node-actions'
|
|
9
|
+
|
|
10
|
+
interface RegistryTreeNodeProps {
|
|
11
|
+
nodeId: AnyNodeId
|
|
12
|
+
depth: number
|
|
13
|
+
isLast?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Generic, leaf tree-node row driven entirely by the kind's
|
|
18
|
+
* `def.presentation` (icon + label). Replaces the per-kind boilerplate
|
|
19
|
+
* components that differed only in their default name and icon — today the
|
|
20
|
+
* roof vents (box / ridge / turbine / cupola / eyebrow). Register a kind in
|
|
21
|
+
* `treeNodeByType` against this component instead of authoring another copy.
|
|
22
|
+
*/
|
|
23
|
+
export const RegistryTreeNode = memo(function RegistryTreeNode({
|
|
24
|
+
nodeId,
|
|
25
|
+
depth,
|
|
26
|
+
isLast,
|
|
27
|
+
}: RegistryTreeNodeProps) {
|
|
28
|
+
const [isEditing, setIsEditing] = useState(false)
|
|
29
|
+
const isVisible = useScene((s) => s.nodes[nodeId]?.visible !== false)
|
|
30
|
+
const node = useScene((s) => s.nodes[nodeId])
|
|
31
|
+
const isSelected = useViewer((state) => state.selection.selectedIds.includes(nodeId))
|
|
32
|
+
const isHovered = useViewer((state) => state.hoveredId === nodeId)
|
|
33
|
+
const setSelection = useViewer((state) => state.setSelection)
|
|
34
|
+
const setHoveredId = useViewer((state) => state.setHoveredId)
|
|
35
|
+
|
|
36
|
+
const presentation = node ? nodeRegistry.get(node.type)?.presentation : undefined
|
|
37
|
+
const icon = presentation?.icon
|
|
38
|
+
const iconSrc = icon?.kind === 'url' ? icon.src : '/icons/roof.png'
|
|
39
|
+
const defaultName = node?.name || presentation?.label || 'Node'
|
|
40
|
+
|
|
41
|
+
const handleClick = useCallback(
|
|
42
|
+
(e: React.MouseEvent) => {
|
|
43
|
+
e.stopPropagation()
|
|
44
|
+
const handled = handleTreeSelection(
|
|
45
|
+
e,
|
|
46
|
+
nodeId,
|
|
47
|
+
useViewer.getState().selection.selectedIds,
|
|
48
|
+
setSelection,
|
|
49
|
+
)
|
|
50
|
+
if (!handled && useEditor.getState().phase === 'furnish') {
|
|
51
|
+
useEditor.getState().setPhase('structure')
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
[nodeId, setSelection],
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<TreeNodeWrapper
|
|
59
|
+
actions={<TreeNodeActions nodeId={nodeId} />}
|
|
60
|
+
depth={depth}
|
|
61
|
+
expanded={false}
|
|
62
|
+
hasChildren={false}
|
|
63
|
+
icon={
|
|
64
|
+
<Image
|
|
65
|
+
alt=""
|
|
66
|
+
className="object-contain opacity-60"
|
|
67
|
+
height={14}
|
|
68
|
+
src={iconSrc}
|
|
69
|
+
width={14}
|
|
70
|
+
/>
|
|
71
|
+
}
|
|
72
|
+
isHovered={isHovered}
|
|
73
|
+
isLast={isLast}
|
|
74
|
+
isSelected={isSelected}
|
|
75
|
+
isVisible={isVisible}
|
|
76
|
+
label={
|
|
77
|
+
<InlineRenameInput
|
|
78
|
+
defaultName={defaultName}
|
|
79
|
+
isEditing={isEditing}
|
|
80
|
+
nodeId={nodeId}
|
|
81
|
+
onStartEditing={() => setIsEditing(true)}
|
|
82
|
+
onStopEditing={() => setIsEditing(false)}
|
|
83
|
+
/>
|
|
84
|
+
}
|
|
85
|
+
nodeId={nodeId}
|
|
86
|
+
onClick={handleClick}
|
|
87
|
+
onDoubleClick={() => focusTreeNode(nodeId)}
|
|
88
|
+
onMouseEnter={() => setHoveredId(nodeId)}
|
|
89
|
+
onMouseLeave={() => setHoveredId(null)}
|
|
90
|
+
onToggle={() => {}}
|
|
91
|
+
/>
|
|
92
|
+
)
|
|
93
|
+
})
|
|
@@ -6,7 +6,7 @@ import { memo, useCallback, useEffect, useState } from 'react'
|
|
|
6
6
|
import { useShallow } from 'zustand/react/shallow'
|
|
7
7
|
import useEditor from '../../../../../store/use-editor'
|
|
8
8
|
import { InlineRenameInput } from './inline-rename-input'
|
|
9
|
-
import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
|
|
9
|
+
import { focusTreeNode, handleTreeSelection, TreeNode, TreeNodeWrapper } from './tree-node'
|
|
10
10
|
import { TreeNodeActions } from './tree-node-actions'
|
|
11
11
|
import { DropIndicatorLine, useTreeNodeDrag } from './tree-node-drag'
|
|
12
12
|
|
|
@@ -163,12 +163,36 @@ function RoofSegmentTreeNode({
|
|
|
163
163
|
isLast?: boolean
|
|
164
164
|
}) {
|
|
165
165
|
const [isEditing, setIsEditing] = useState(false)
|
|
166
|
+
const [expanded, setExpanded] = useState(true)
|
|
167
|
+
const accessoryIds = useScene(
|
|
168
|
+
useShallow(
|
|
169
|
+
(s) => (s.nodes[node.id] as RoofSegmentNode | undefined)?.children ?? ([] as string[]),
|
|
170
|
+
),
|
|
171
|
+
)
|
|
166
172
|
const isSelected = useViewer((state) => state.selection.selectedIds.includes(node.id))
|
|
167
173
|
const isHovered = useViewer((state) => state.hoveredId === node.id)
|
|
168
174
|
const setSelection = useViewer((state) => state.setSelection)
|
|
169
175
|
const setHoveredId = useViewer((state) => state.setHoveredId)
|
|
170
176
|
const { startDrag, isDragging } = useTreeNodeDrag()
|
|
171
177
|
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
return useViewer.subscribe((state) => {
|
|
180
|
+
const { selectedIds } = state.selection
|
|
181
|
+
if (selectedIds.length === 0) return
|
|
182
|
+
const nodes = useScene.getState().nodes
|
|
183
|
+
for (const id of selectedIds) {
|
|
184
|
+
let current = nodes[id as AnyNodeId]
|
|
185
|
+
while (current?.parentId) {
|
|
186
|
+
if (current.parentId === node.id) {
|
|
187
|
+
setExpanded(true)
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
current = nodes[current.parentId as AnyNodeId]
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
}, [node.id])
|
|
195
|
+
|
|
172
196
|
const handleClick = useCallback(
|
|
173
197
|
(e: React.MouseEvent) => {
|
|
174
198
|
if (isDragging) return
|
|
@@ -192,13 +216,15 @@ function RoofSegmentTreeNode({
|
|
|
192
216
|
|
|
193
217
|
const defaultName = `${node.roofType.charAt(0).toUpperCase() + node.roofType.slice(1)} (${node.width.toFixed(1)}x${node.depth.toFixed(1)}m)`
|
|
194
218
|
|
|
219
|
+
const hasAccessories = accessoryIds.length > 0
|
|
220
|
+
|
|
195
221
|
return (
|
|
196
222
|
<div data-drop-child={node.id}>
|
|
197
223
|
<TreeNodeWrapper
|
|
198
224
|
actions={<TreeNodeActions nodeId={node.id} />}
|
|
199
225
|
depth={depth}
|
|
200
|
-
expanded={
|
|
201
|
-
hasChildren={
|
|
226
|
+
expanded={expanded}
|
|
227
|
+
hasChildren={hasAccessories}
|
|
202
228
|
icon={
|
|
203
229
|
<Image
|
|
204
230
|
alt=""
|
|
@@ -228,8 +254,18 @@ function RoofSegmentTreeNode({
|
|
|
228
254
|
onMouseEnter={() => setHoveredId(node.id)}
|
|
229
255
|
onMouseLeave={() => setHoveredId(null)}
|
|
230
256
|
onPointerDown={handlePointerDown}
|
|
231
|
-
onToggle={() =>
|
|
232
|
-
|
|
257
|
+
onToggle={() => setExpanded((prev) => !prev)}
|
|
258
|
+
>
|
|
259
|
+
{hasAccessories &&
|
|
260
|
+
accessoryIds.map((childId, index) => (
|
|
261
|
+
<TreeNode
|
|
262
|
+
depth={depth + 1}
|
|
263
|
+
isLast={index === accessoryIds.length - 1}
|
|
264
|
+
key={childId}
|
|
265
|
+
nodeId={childId as AnyNodeId}
|
|
266
|
+
/>
|
|
267
|
+
))}
|
|
268
|
+
</TreeNodeWrapper>
|
|
233
269
|
</div>
|
|
234
270
|
)
|
|
235
271
|
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { type AnyNodeId, type ShelfNode, useScene } from '@pascal-app/core'
|
|
4
|
+
import { useViewer } from '@pascal-app/viewer'
|
|
5
|
+
import Image from 'next/image'
|
|
6
|
+
import { memo, useCallback, useEffect, useState } from 'react'
|
|
7
|
+
import { useShallow } from 'zustand/react/shallow'
|
|
8
|
+
import useEditor from './../../../../../store/use-editor'
|
|
9
|
+
import { InlineRenameInput } from './inline-rename-input'
|
|
10
|
+
import { focusTreeNode, handleTreeSelection, TreeNode, TreeNodeWrapper } from './tree-node'
|
|
11
|
+
import { TreeNodeActions } from './tree-node-actions'
|
|
12
|
+
|
|
13
|
+
interface ShelfTreeNodeProps {
|
|
14
|
+
nodeId: ShelfNode['id']
|
|
15
|
+
depth: number
|
|
16
|
+
isLast?: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Sidebar tree entry for shelf. Mirrors `item-tree-node`'s shape so the
|
|
21
|
+
* shelf's hosted items list as collapsible children — same pattern items
|
|
22
|
+
* use for their nested items. The shelf has its own `children: ItemNode[`id`]`
|
|
23
|
+
* field on the schema; items reparent into it via `def.surfaces` + the
|
|
24
|
+
* placement coordinator's shelf strategy.
|
|
25
|
+
*/
|
|
26
|
+
export const ShelfTreeNode = memo(function ShelfTreeNode({
|
|
27
|
+
nodeId,
|
|
28
|
+
depth,
|
|
29
|
+
isLast,
|
|
30
|
+
}: ShelfTreeNodeProps) {
|
|
31
|
+
const [isEditing, setIsEditing] = useState(false)
|
|
32
|
+
const [expanded, setExpanded] = useState(true)
|
|
33
|
+
const isVisible = useScene((s) => s.nodes[nodeId]?.visible !== false)
|
|
34
|
+
const children = useScene(
|
|
35
|
+
useShallow((s) => (s.nodes[nodeId] as ShelfNode | undefined)?.children ?? []),
|
|
36
|
+
)
|
|
37
|
+
const isSelected = useViewer((state) => state.selection.selectedIds.includes(nodeId))
|
|
38
|
+
const isHovered = useViewer((state) => state.hoveredId === nodeId)
|
|
39
|
+
const setSelection = useViewer((state) => state.setSelection)
|
|
40
|
+
const setHoveredId = useViewer((state) => state.setHoveredId)
|
|
41
|
+
|
|
42
|
+
// Expand when a descendant is selected — same imperative subscription
|
|
43
|
+
// the item tree-node uses, so we don't re-render when unrelated
|
|
44
|
+
// selection-state ticks.
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
return useViewer.subscribe((state) => {
|
|
47
|
+
const { selectedIds } = state.selection
|
|
48
|
+
if (selectedIds.length === 0) return
|
|
49
|
+
const nodes = useScene.getState().nodes
|
|
50
|
+
for (const id of selectedIds) {
|
|
51
|
+
let current = nodes[id as AnyNodeId]
|
|
52
|
+
while (current?.parentId) {
|
|
53
|
+
if (current.parentId === nodeId) {
|
|
54
|
+
setExpanded(true)
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
current = nodes[current.parentId as AnyNodeId]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
}, [nodeId])
|
|
62
|
+
|
|
63
|
+
const handleClick = useCallback(
|
|
64
|
+
(e: React.MouseEvent) => {
|
|
65
|
+
e.stopPropagation()
|
|
66
|
+
const handled = handleTreeSelection(
|
|
67
|
+
e,
|
|
68
|
+
nodeId,
|
|
69
|
+
useViewer.getState().selection.selectedIds,
|
|
70
|
+
setSelection,
|
|
71
|
+
)
|
|
72
|
+
if (!handled && useEditor.getState().phase === 'furnish') {
|
|
73
|
+
useEditor.getState().setPhase('structure')
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
[nodeId, setSelection],
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
const handleDoubleClick = useCallback(() => focusTreeNode(nodeId), [nodeId])
|
|
80
|
+
const handleMouseEnter = useCallback(() => setHoveredId(nodeId), [nodeId, setHoveredId])
|
|
81
|
+
const handleMouseLeave = useCallback(() => setHoveredId(null), [setHoveredId])
|
|
82
|
+
const handleToggle = useCallback(() => setExpanded((prev) => !prev), [])
|
|
83
|
+
const handleStartEditing = useCallback(() => setIsEditing(true), [])
|
|
84
|
+
const handleStopEditing = useCallback(() => setIsEditing(false), [])
|
|
85
|
+
|
|
86
|
+
const hasChildren = children.length > 0
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<TreeNodeWrapper
|
|
90
|
+
actions={<TreeNodeActions nodeId={nodeId} />}
|
|
91
|
+
depth={depth}
|
|
92
|
+
expanded={expanded}
|
|
93
|
+
hasChildren={hasChildren}
|
|
94
|
+
icon={
|
|
95
|
+
<Image alt="" className="object-contain" height={14} src="/icons/shelf.png" width={14} />
|
|
96
|
+
}
|
|
97
|
+
isHovered={isHovered}
|
|
98
|
+
isLast={isLast}
|
|
99
|
+
isSelected={isSelected}
|
|
100
|
+
isVisible={isVisible}
|
|
101
|
+
label={
|
|
102
|
+
<InlineRenameInput
|
|
103
|
+
defaultName="Shelf"
|
|
104
|
+
isEditing={isEditing}
|
|
105
|
+
nodeId={nodeId}
|
|
106
|
+
onStartEditing={handleStartEditing}
|
|
107
|
+
onStopEditing={handleStopEditing}
|
|
108
|
+
/>
|
|
109
|
+
}
|
|
110
|
+
nodeId={nodeId}
|
|
111
|
+
onClick={handleClick}
|
|
112
|
+
onDoubleClick={handleDoubleClick}
|
|
113
|
+
onMouseEnter={handleMouseEnter}
|
|
114
|
+
onMouseLeave={handleMouseLeave}
|
|
115
|
+
onToggle={handleToggle}
|
|
116
|
+
>
|
|
117
|
+
{hasChildren &&
|
|
118
|
+
children.map((childId, index) => (
|
|
119
|
+
<TreeNode
|
|
120
|
+
depth={depth + 1}
|
|
121
|
+
isLast={index === children.length - 1}
|
|
122
|
+
key={childId}
|
|
123
|
+
nodeId={childId}
|
|
124
|
+
/>
|
|
125
|
+
))}
|
|
126
|
+
</TreeNodeWrapper>
|
|
127
|
+
)
|
|
128
|
+
})
|
|
@@ -91,8 +91,11 @@ function calculatePolygonArea(polygon: Array<[number, number]>): number {
|
|
|
91
91
|
|
|
92
92
|
for (let i = 0; i < n; i++) {
|
|
93
93
|
const j = (i + 1) % n
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
const pi = polygon[i]
|
|
95
|
+
const pj = polygon[j]
|
|
96
|
+
if (!(pi && pj)) continue
|
|
97
|
+
area += pi[0] * pj[1]
|
|
98
|
+
area -= pj[0] * pi[1]
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
return Math.abs(area) / 2
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { type AnyNodeId, type SolarPanelNode, useScene } from '@pascal-app/core'
|
|
2
|
+
import { useViewer } from '@pascal-app/viewer'
|
|
3
|
+
import Image from 'next/image'
|
|
4
|
+
import { memo, useCallback, useState } from 'react'
|
|
5
|
+
import useEditor from './../../../../../store/use-editor'
|
|
6
|
+
import { InlineRenameInput } from './inline-rename-input'
|
|
7
|
+
import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
|
|
8
|
+
import { TreeNodeActions } from './tree-node-actions'
|
|
9
|
+
|
|
10
|
+
interface SolarPanelTreeNodeProps {
|
|
11
|
+
nodeId: AnyNodeId
|
|
12
|
+
depth: number
|
|
13
|
+
isLast?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const SolarPanelTreeNode = memo(function SolarPanelTreeNode({
|
|
17
|
+
nodeId,
|
|
18
|
+
depth,
|
|
19
|
+
isLast,
|
|
20
|
+
}: SolarPanelTreeNodeProps) {
|
|
21
|
+
const [isEditing, setIsEditing] = useState(false)
|
|
22
|
+
const isVisible = useScene((s) => s.nodes[nodeId]?.visible !== false)
|
|
23
|
+
const node = useScene((s) => s.nodes[nodeId] as SolarPanelNode | undefined)
|
|
24
|
+
const isSelected = useViewer((state) => state.selection.selectedIds.includes(nodeId))
|
|
25
|
+
const isHovered = useViewer((state) => state.hoveredId === nodeId)
|
|
26
|
+
const setSelection = useViewer((state) => state.setSelection)
|
|
27
|
+
const setHoveredId = useViewer((state) => state.setHoveredId)
|
|
28
|
+
|
|
29
|
+
const handleClick = useCallback(
|
|
30
|
+
(e: React.MouseEvent) => {
|
|
31
|
+
e.stopPropagation()
|
|
32
|
+
const handled = handleTreeSelection(
|
|
33
|
+
e,
|
|
34
|
+
nodeId,
|
|
35
|
+
useViewer.getState().selection.selectedIds,
|
|
36
|
+
setSelection,
|
|
37
|
+
)
|
|
38
|
+
if (!handled && useEditor.getState().phase === 'furnish') {
|
|
39
|
+
useEditor.getState().setPhase('structure')
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
[nodeId, setSelection],
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
const defaultName = node?.name || 'Solar Panel'
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<TreeNodeWrapper
|
|
49
|
+
actions={<TreeNodeActions nodeId={nodeId} />}
|
|
50
|
+
depth={depth}
|
|
51
|
+
expanded={false}
|
|
52
|
+
hasChildren={false}
|
|
53
|
+
icon={
|
|
54
|
+
<Image
|
|
55
|
+
alt=""
|
|
56
|
+
className="object-contain opacity-60"
|
|
57
|
+
height={14}
|
|
58
|
+
src="/icons/roof.png"
|
|
59
|
+
width={14}
|
|
60
|
+
/>
|
|
61
|
+
}
|
|
62
|
+
isHovered={isHovered}
|
|
63
|
+
isLast={isLast}
|
|
64
|
+
isSelected={isSelected}
|
|
65
|
+
isVisible={isVisible}
|
|
66
|
+
label={
|
|
67
|
+
<InlineRenameInput
|
|
68
|
+
defaultName={defaultName}
|
|
69
|
+
isEditing={isEditing}
|
|
70
|
+
nodeId={nodeId}
|
|
71
|
+
onStartEditing={() => setIsEditing(true)}
|
|
72
|
+
onStopEditing={() => setIsEditing(false)}
|
|
73
|
+
/>
|
|
74
|
+
}
|
|
75
|
+
nodeId={nodeId}
|
|
76
|
+
onClick={handleClick}
|
|
77
|
+
onDoubleClick={() => focusTreeNode(nodeId)}
|
|
78
|
+
onMouseEnter={() => setHoveredId(nodeId)}
|
|
79
|
+
onMouseLeave={() => setHoveredId(null)}
|
|
80
|
+
onToggle={() => {}}
|
|
81
|
+
/>
|
|
82
|
+
)
|
|
83
|
+
})
|