@pascal-app/editor 0.9.1 → 0.9.2
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 -9
- package/src/components/editor/alignment-3d-guide-layer.tsx +10 -2
- package/src/components/editor/bake-exporter.tsx +47 -0
- package/src/components/editor/custom-camera-controls.tsx +63 -25
- package/src/components/editor/editor-layout-mobile.tsx +5 -0
- package/src/components/editor/editor-layout-v2.tsx +18 -0
- package/src/components/editor/export-manager.tsx +71 -63
- package/src/components/editor/fence-tangent-lines-3d.tsx +87 -0
- package/src/components/editor/first-person/build-collider-world.test.ts +36 -3
- package/src/components/editor/first-person/build-collider-world.ts +32 -5
- package/src/components/editor/first-person-controls.tsx +118 -7
- package/src/components/editor/floating-action-menu.tsx +410 -33
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floorplan-background-selection.test.ts +58 -0
- package/src/components/editor/floorplan-background-selection.ts +3 -2
- package/src/components/editor/floorplan-panel.tsx +1804 -849
- package/src/components/editor/grid.tsx +166 -39
- package/src/components/editor/group-actions.ts +457 -0
- package/src/components/editor/group-floating-action-menu.tsx +130 -0
- package/src/components/editor/group-move-3d.ts +397 -0
- package/src/components/editor/group-rotate-handle.tsx +102 -64
- package/src/components/editor/group-selection-box-3d.tsx +173 -0
- package/src/components/editor/group-transform-shared.test.ts +192 -1
- package/src/components/editor/group-transform-shared.ts +240 -7
- package/src/components/editor/handles/handle-arrow.tsx +81 -23
- package/src/components/editor/handles/use-handle-drag.ts +37 -4
- package/src/components/editor/index.tsx +248 -27
- package/src/components/editor/measurement-pill.tsx +62 -22
- package/src/components/editor/node-action-menu.tsx +14 -1
- package/src/components/editor/node-arrow-handles.tsx +366 -173
- package/src/components/editor/opening-guides-3d-layer.tsx +144 -0
- package/src/components/editor/quick-measurement-card.tsx +70 -0
- package/src/components/editor/quick-measurement-hud.tsx +28 -0
- package/src/components/editor/riser-diagram-panel.tsx +137 -0
- package/src/components/editor/selection-manager.tsx +812 -466
- package/src/components/editor/site-edge-labels.tsx +4 -4
- package/src/components/editor/slab-hole-highlights.tsx +13 -5
- package/src/components/editor/snapshot-capture-overlay.tsx +255 -115
- package/src/components/editor/three-context-bridge.ts +22 -0
- package/src/components/editor/thumbnail-generator.tsx +136 -43
- package/src/components/editor/use-floorplan-background-placement.ts +113 -45
- package/src/components/editor/use-floorplan-scene-data.ts +5 -5
- package/src/components/editor/use-mesh-settle-epoch.ts +26 -0
- package/src/components/editor/wall-measurement-label.tsx +3 -13
- package/src/components/editor/wall-move-side-handles.tsx +36 -21
- package/src/components/editor/wall-snap-beacon-layer.tsx +160 -6
- package/src/components/editor-2d/floorplan-action-menu-layer.tsx +9 -5
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +1 -1
- package/src/components/editor-2d/floorplan-group-action-menu.tsx +87 -0
- package/src/components/editor-2d/floorplan-group-move.tsx +701 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +113 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1656 -0
- package/src/components/editor-2d/floorplan-quick-measure-layer.tsx +212 -0
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +169 -4
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +300 -59
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +5 -3
- package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +15 -0
- package/src/components/editor-2d/renderers/floorplan-label-angle.ts +14 -0
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +4 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +308 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +1704 -342
- package/src/components/editor-2d/renderers/floorplan-voronoi-layer.tsx +128 -0
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +419 -60
- package/src/components/systems/ceiling/ceiling-system.tsx +97 -4
- package/src/components/systems/roof/roof-edit-system.tsx +1768 -13
- package/src/components/systems/selection-affordance-manager.tsx +38 -0
- package/src/components/systems/zone/zone-label-editor-system.tsx +14 -1
- package/src/components/systems/zone/zone-system.tsx +42 -13
- package/src/components/tools/elevator/elevator-tool.tsx +34 -8
- package/src/components/tools/elevator/move-elevator-tool.tsx +13 -2
- package/src/components/tools/fence/fence-drafting.ts +80 -8
- package/src/components/tools/item/move-tool.tsx +17 -13
- package/src/components/tools/item/placement-math.test.ts +13 -1
- package/src/components/tools/item/placement-math.ts +28 -2
- package/src/components/tools/item/placement-strategies.ts +246 -5
- package/src/components/tools/item/placement-types.ts +13 -1
- package/src/components/tools/item/use-draft-node.ts +31 -1
- package/src/components/tools/item/use-placement-coordinator.tsx +761 -121
- package/src/components/tools/registry/move-registry-node-tool.tsx +561 -99
- package/src/components/tools/roof/roof-tool.tsx +320 -94
- package/src/components/tools/select/box-select-state.ts +18 -7
- package/src/components/tools/select/box-select-tool.tsx +176 -40
- package/src/components/tools/select/marquee-geometry.test.ts +161 -0
- package/src/components/tools/select/marquee-geometry.ts +155 -0
- package/src/components/tools/select/plane-box-select-tool.tsx +1 -8
- package/src/components/tools/select/select-candidates.ts +1 -1
- package/src/components/tools/shared/cursor-sphere.tsx +62 -26
- package/src/components/tools/shared/drag-bounding-box.tsx +28 -4
- package/src/components/tools/shared/facing-indicator.tsx +87 -0
- package/src/components/tools/shared/facing-pose-indicator.tsx +54 -0
- package/src/components/tools/shared/placement-box.tsx +183 -1
- package/src/components/tools/shared/polygon-editor.tsx +276 -35
- package/src/components/tools/site/site-boundary-editor.tsx +88 -36
- package/src/components/tools/stair/stair-defaults.ts +1 -0
- package/src/components/tools/stair/stair-tool.tsx +82 -11
- package/src/components/tools/tool-manager.tsx +86 -25
- package/src/components/tools/wall/wall-drafting.test.ts +330 -0
- package/src/components/tools/wall/wall-drafting.ts +180 -103
- package/src/components/tools/wall/wall-snap-geometry.test.ts +44 -0
- package/src/components/tools/wall/wall-snap-geometry.ts +67 -6
- package/src/components/tools/zone/zone-boundary-editor.tsx +5 -1
- package/src/components/tools/zone/zone-tool.tsx +82 -88
- package/src/components/ui/action-menu/camera-actions.tsx +24 -17
- package/src/components/ui/action-menu/control-modes.tsx +40 -93
- package/src/components/ui/action-menu/furnish-tools.tsx +5 -5
- package/src/components/ui/action-menu/index.tsx +2 -4
- package/src/components/ui/action-menu/measurement-control.tsx +188 -0
- package/src/components/ui/action-menu/structure-tools.tsx +22 -13
- package/src/components/ui/action-menu/view-toggles.tsx +42 -90
- package/src/components/ui/command-palette/editor-commands.tsx +2 -2
- package/src/components/ui/command-palette/index.tsx +4 -3
- package/src/components/ui/controls/material-paint-panel.tsx +86 -17
- package/src/components/ui/controls/material-picker.tsx +83 -152
- package/src/components/ui/controls/material-properties-editor.tsx +108 -0
- package/src/components/ui/controls/metric-control.tsx +118 -44
- package/src/components/ui/controls/scene-material-list.tsx +247 -0
- package/src/components/ui/controls/slider-control.tsx +88 -38
- package/src/components/ui/floating-level-selector.tsx +1 -1
- package/src/components/ui/helpers/building-helper.tsx +10 -23
- package/src/components/ui/helpers/contextual-helper-panel.tsx +428 -0
- package/src/components/ui/helpers/helper-manager.tsx +227 -14
- package/src/components/ui/helpers/item-helper.tsx +28 -32
- package/src/components/ui/helpers/registered-tool-helper.tsx +45 -11
- package/src/components/ui/helpers/roof-helper.tsx +10 -12
- package/src/components/ui/icon-ref.tsx +47 -0
- package/src/components/ui/item-catalog/catalog-items.tsx +39 -1
- package/src/components/ui/item-catalog/item-catalog.tsx +13 -36
- package/src/components/ui/level-duplicate-dialog.tsx +1 -1
- package/src/components/ui/panels/node-display.ts +17 -17
- package/src/components/ui/panels/panel-manager.tsx +22 -13
- package/src/components/ui/panels/panel-wrapper.tsx +24 -3
- package/src/components/ui/panels/parametric-inspector.tsx +15 -2
- package/src/components/ui/panels/reference-panel.tsx +54 -19
- package/src/components/ui/primitives/shortcut-token.tsx +39 -3
- package/src/components/ui/primitives/sidebar.tsx +1 -0
- package/src/components/ui/sidebar/app-sidebar.tsx +5 -1
- package/src/components/ui/sidebar/icon-rail.tsx +50 -31
- package/src/components/ui/sidebar/panels/plugins-panel.tsx +218 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +71 -40
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +60 -6
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +12 -4
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -2
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +40 -17
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +31 -17
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +72 -25
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +12 -8
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +2 -2
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +44 -3
- package/src/components/ui/sidebar/panels/site-panel/tree-structure.ts +36 -0
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -2
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -3
- package/src/components/ui/sidebar/tab-bar.tsx +42 -28
- package/src/components/ui/sidebar/use-plugin-panels.tsx +132 -0
- package/src/components/ui/snap-target-badge.test.tsx +40 -0
- package/src/components/ui/snap-target-badge.tsx +88 -0
- package/src/components/viewer-overlay.tsx +85 -10
- package/src/components/viewer-zone-system.tsx +6 -1
- package/src/hooks/use-auto-save.test.ts +14 -0
- package/src/hooks/use-auto-save.ts +54 -7
- package/src/hooks/use-ceiling-events.ts +3 -2
- package/src/hooks/use-drag-action.ts +4 -1
- package/src/hooks/use-keyboard.ts +385 -40
- package/src/index.tsx +219 -6
- package/src/lib/active-placement-surface.ts +35 -0
- package/src/lib/ceiling-plan-snap.ts +24 -0
- package/src/lib/contextual-help.test.ts +112 -0
- package/src/lib/contextual-help.ts +144 -0
- package/src/lib/continuation.ts +64 -0
- package/src/lib/direct-manipulation.test.ts +192 -0
- package/src/lib/direct-manipulation.ts +109 -0
- package/src/lib/editor-api.ts +23 -35
- package/src/lib/floorplan/apply-alignment.test.ts +25 -0
- package/src/lib/floorplan/apply-alignment.ts +18 -12
- package/src/lib/floorplan/floorplan-export.tsx +364 -0
- package/src/lib/floorplan/geometry.ts +53 -0
- package/src/lib/floorplan/index.ts +3 -0
- package/src/lib/floorplan/items.ts +5 -2
- package/src/lib/floorplan/plan-coords.ts +21 -0
- package/src/lib/fresh-planar-placement.test.ts +240 -3
- package/src/lib/fresh-planar-placement.ts +68 -1
- package/src/lib/glb-export.test.ts +441 -0
- package/src/lib/glb-export.ts +874 -0
- package/src/lib/history.ts +9 -0
- package/src/lib/interaction/hot-set.test.ts +133 -0
- package/src/lib/interaction/hot-set.ts +67 -0
- package/src/lib/interaction/overlay-policy.test.ts +51 -0
- package/src/lib/interaction/overlay-policy.ts +59 -0
- package/src/lib/interaction/scope.ts +211 -0
- package/src/lib/level-selection.ts +2 -2
- package/src/lib/material-paint.ts +36 -49
- package/src/lib/measurement-kind.test.ts +30 -0
- package/src/lib/measurement-kind.ts +19 -0
- package/src/lib/measurement-label.test.ts +47 -0
- package/src/lib/measurement-label.ts +68 -0
- package/src/lib/measurement-parser.ts +116 -0
- package/src/lib/measurements.test.ts +183 -0
- package/src/lib/measurements.ts +199 -0
- package/src/lib/paint-scope.test.ts +454 -0
- package/src/lib/paint-scope.ts +461 -0
- package/src/lib/placement-drag-release.ts +23 -0
- package/src/lib/planar-cursor-placement.test.ts +57 -0
- package/src/lib/plugin-panels.test.ts +19 -0
- package/src/lib/plugin-panels.ts +91 -0
- package/src/lib/quick-measurement.test.ts +77 -0
- package/src/lib/quick-measurement.ts +109 -0
- package/src/lib/roof-duplication.ts +6 -6
- package/src/lib/roof-hover-outline-proxy.ts +22 -0
- package/src/lib/roof-wall-hit.ts +164 -0
- package/src/lib/scene-clipboard.test.ts +196 -0
- package/src/lib/scene-clipboard.ts +73 -8
- package/src/lib/scene.ts +28 -5
- package/src/lib/selection-routing.test.ts +298 -0
- package/src/lib/selection-routing.ts +175 -0
- package/src/lib/sfx/index.ts +1 -0
- package/src/lib/sfx/movement-tick.test.ts +65 -0
- package/src/lib/sfx/movement-tick.ts +18 -0
- package/src/lib/sfx-bus.ts +50 -13
- package/src/lib/sfx-player.test.ts +124 -0
- package/src/lib/sfx-player.ts +111 -66
- package/src/lib/slab-plan-snap.test.ts +93 -0
- package/src/lib/slab-plan-snap.ts +108 -0
- package/src/lib/snapping-mode.test.ts +138 -0
- package/src/lib/snapping-mode.ts +179 -0
- package/src/lib/stair-duplication.ts +4 -4
- package/src/lib/surface-plan-snap.test.ts +71 -0
- package/src/lib/surface-plan-snap.ts +256 -0
- package/src/lib/use-linear-display.ts +40 -0
- package/src/lib/world-grid-snap.ts +37 -5
- package/src/store/use-direct-manipulation-feedback.ts +20 -0
- package/src/store/use-editor.tsx +484 -114
- package/src/store/use-facing-pose.ts +44 -0
- package/src/store/use-fence-curve-draft.ts +21 -0
- package/src/store/use-floorplan-draft-preview.ts +101 -0
- package/src/store/use-floorplan-marquee.ts +50 -0
- package/src/store/use-interaction-scope.test.ts +186 -0
- package/src/store/use-interaction-scope.ts +132 -0
- package/src/store/use-measurement-draft.test.ts +530 -0
- package/src/store/use-measurement-draft.ts +543 -0
- package/src/store/use-opening-guides.ts +41 -0
- package/src/store/use-placement-preview.ts +11 -3
- package/src/store/use-quick-measurement-hud.test.ts +53 -0
- package/src/store/use-quick-measurement-hud.ts +77 -0
- package/src/store/use-segment-draft-chain.ts +28 -0
- package/src/store/use-stair-build-preview.ts +43 -0
- package/src/store/use-wall-snap-indicator.ts +2 -0
- package/src/components/editor/first-person/bvh-ecctrl.tsx +0 -860
- package/src/components/editor/group-move-handle.tsx +0 -316
- package/src/components/ui/panels/paint-panel.tsx +0 -163
- package/src/lib/level-name.ts +0 -11
|
@@ -2,6 +2,7 @@ import { type AnyNodeId, type DormerNode, useScene } from '@pascal-app/core'
|
|
|
2
2
|
import { useViewer } from '@pascal-app/viewer'
|
|
3
3
|
import Image from 'next/image'
|
|
4
4
|
import { memo, useCallback, useState } from 'react'
|
|
5
|
+
import { SnapTargetIcon } from '../../../snap-target-badge'
|
|
5
6
|
import useEditor from './../../../../../store/use-editor'
|
|
6
7
|
import { InlineRenameInput } from './inline-rename-input'
|
|
7
8
|
import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
|
|
@@ -57,13 +58,15 @@ export const DormerTreeNode = memo(function DormerTreeNode({
|
|
|
57
58
|
expanded={false}
|
|
58
59
|
hasChildren={false}
|
|
59
60
|
icon={
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
61
|
+
<SnapTargetIcon target="roof">
|
|
62
|
+
<Image
|
|
63
|
+
alt=""
|
|
64
|
+
className="object-contain opacity-60"
|
|
65
|
+
height={14}
|
|
66
|
+
src="/icons/roof.webp"
|
|
67
|
+
width={14}
|
|
68
|
+
/>
|
|
69
|
+
</SnapTargetIcon>
|
|
67
70
|
}
|
|
68
71
|
isHovered={isHovered}
|
|
69
72
|
isLast={isLast}
|
|
@@ -49,7 +49,7 @@ export const ElevatorTreeNode = memo(function ElevatorTreeNode({
|
|
|
49
49
|
expanded={false}
|
|
50
50
|
hasChildren={false}
|
|
51
51
|
icon={
|
|
52
|
-
<Image alt="" className="object-contain" height={14} src="/icons/elevator.
|
|
52
|
+
<Image alt="" className="object-contain" height={14} src="/icons/elevator.webp" width={14} />
|
|
53
53
|
}
|
|
54
54
|
isHovered={isHovered}
|
|
55
55
|
isLast={isLast}
|
|
@@ -43,7 +43,7 @@ export const FenceTreeNode = memo(function FenceTreeNode({
|
|
|
43
43
|
expanded={false}
|
|
44
44
|
hasChildren={false}
|
|
45
45
|
icon={
|
|
46
|
-
<Image alt="" className="object-contain" height={14} src="/icons/fence.
|
|
46
|
+
<Image alt="" className="object-contain" height={14} src="/icons/fence.webp" width={14} />
|
|
47
47
|
}
|
|
48
48
|
isHovered={isHovered}
|
|
49
49
|
isLast={isLast}
|
|
@@ -2,6 +2,7 @@ import { type AnyNodeId, type GutterNode, useScene } from '@pascal-app/core'
|
|
|
2
2
|
import { useViewer } from '@pascal-app/viewer'
|
|
3
3
|
import Image from 'next/image'
|
|
4
4
|
import { memo, useCallback, useState } from 'react'
|
|
5
|
+
import { SnapTargetIcon } from '../../../snap-target-badge'
|
|
5
6
|
import useEditor from './../../../../../store/use-editor'
|
|
6
7
|
import { InlineRenameInput } from './inline-rename-input'
|
|
7
8
|
import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
|
|
@@ -51,13 +52,15 @@ export const GutterTreeNode = memo(function GutterTreeNode({
|
|
|
51
52
|
expanded={false}
|
|
52
53
|
hasChildren={false}
|
|
53
54
|
icon={
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
<SnapTargetIcon target="roof">
|
|
56
|
+
<Image
|
|
57
|
+
alt=""
|
|
58
|
+
className="object-contain opacity-60"
|
|
59
|
+
height={14}
|
|
60
|
+
src="/icons/roof.webp"
|
|
61
|
+
width={14}
|
|
62
|
+
/>
|
|
63
|
+
</SnapTargetIcon>
|
|
61
64
|
}
|
|
62
65
|
isHovered={isHovered}
|
|
63
66
|
isLast={isLast}
|
|
@@ -36,8 +36,16 @@ import {
|
|
|
36
36
|
buildLevelDuplicateCreateOps,
|
|
37
37
|
type LevelDuplicatePreset,
|
|
38
38
|
} from './../../../../../lib/level-duplication'
|
|
39
|
-
import { getDefaultLevelName } from '
|
|
39
|
+
import { getDefaultLevelName } from '@pascal-app/core'
|
|
40
40
|
import { deleteLevelWithFallbackSelection } from './../../../../../lib/level-selection'
|
|
41
|
+
import {
|
|
42
|
+
formatAreaLabel,
|
|
43
|
+
getAreaUnitLabel,
|
|
44
|
+
getLinearUnitLabel,
|
|
45
|
+
linearUnitToMeters,
|
|
46
|
+
metersToLinearUnit,
|
|
47
|
+
squareMetersToAreaUnit,
|
|
48
|
+
} from './../../../../../lib/measurements'
|
|
41
49
|
import { createLocalGuideImage } from './../../../../../lib/local-guide-image'
|
|
42
50
|
import { cn } from './../../../../../lib/utils'
|
|
43
51
|
import useEditor from './../../../../../store/use-editor'
|
|
@@ -93,6 +101,7 @@ const PropertyLineSection = memo(function PropertyLineSection() {
|
|
|
93
101
|
const updateNode = useScene((state) => state.updateNode)
|
|
94
102
|
const mode = useEditor((state) => state.mode)
|
|
95
103
|
const setMode = useEditor((state) => state.setMode)
|
|
104
|
+
const viewerUnit = useViewer((state) => state.unit)
|
|
96
105
|
|
|
97
106
|
if (!siteNode) return null
|
|
98
107
|
|
|
@@ -101,6 +110,14 @@ const PropertyLineSection = memo(function PropertyLineSection() {
|
|
|
101
110
|
const perimeter = calculatePerimeter(points)
|
|
102
111
|
const isEditing = mode === 'edit'
|
|
103
112
|
|
|
113
|
+
// Property-line coordinates and readouts follow the metric/imperial toggle.
|
|
114
|
+
const isImperial = viewerUnit === 'imperial'
|
|
115
|
+
const linearLabel = getLinearUnitLabel(viewerUnit)
|
|
116
|
+
const toDisplayLinear = (meters: number) => metersToLinearUnit(meters, viewerUnit)
|
|
117
|
+
const toStoredLinear = (display: number) => linearUnitToMeters(display, viewerUnit)
|
|
118
|
+
const displayArea = squareMetersToAreaUnit(area, viewerUnit)
|
|
119
|
+
const displayPerimeter = toDisplayLinear(perimeter)
|
|
120
|
+
|
|
104
121
|
const handleToggleEdit = () => {
|
|
105
122
|
setMode(isEditing ? 'select' : 'edit')
|
|
106
123
|
}
|
|
@@ -166,10 +183,16 @@ const PropertyLineSection = memo(function PropertyLineSection() {
|
|
|
166
183
|
{/* Measurements */}
|
|
167
184
|
<div className="relative flex gap-3 pr-3 pb-2 pl-10">
|
|
168
185
|
<div className="text-muted-foreground text-xs">
|
|
169
|
-
Area:
|
|
186
|
+
Area:{' '}
|
|
187
|
+
<span className="text-foreground">
|
|
188
|
+
{displayArea.toFixed(1)} {getAreaUnitLabel(viewerUnit)}
|
|
189
|
+
</span>
|
|
170
190
|
</div>
|
|
171
191
|
<div className="text-muted-foreground text-xs">
|
|
172
|
-
Perimeter:
|
|
192
|
+
Perimeter:{' '}
|
|
193
|
+
<span className="text-foreground">
|
|
194
|
+
{displayPerimeter.toFixed(1)} {linearLabel}
|
|
195
|
+
</span>
|
|
173
196
|
</div>
|
|
174
197
|
</div>
|
|
175
198
|
|
|
@@ -184,21 +207,21 @@ const PropertyLineSection = memo(function PropertyLineSection() {
|
|
|
184
207
|
<input
|
|
185
208
|
className="w-16 rounded border border-border/50 bg-accent/50 px-1.5 py-0.5 text-foreground text-xs focus:border-primary focus:outline-none"
|
|
186
209
|
onChange={(e) =>
|
|
187
|
-
handlePointChange(index, 0, Number.parseFloat(e.target.value) || 0)
|
|
210
|
+
handlePointChange(index, 0, toStoredLinear(Number.parseFloat(e.target.value) || 0))
|
|
188
211
|
}
|
|
189
212
|
step={0.5}
|
|
190
213
|
type="number"
|
|
191
|
-
value={point[0]}
|
|
214
|
+
value={Number(toDisplayLinear(point[0]).toFixed(2))}
|
|
192
215
|
/>
|
|
193
216
|
<label className="shrink-0 text-muted-foreground">Z</label>
|
|
194
217
|
<input
|
|
195
218
|
className="w-16 rounded border border-border/50 bg-accent/50 px-1.5 py-0.5 text-foreground text-xs focus:border-primary focus:outline-none"
|
|
196
219
|
onChange={(e) =>
|
|
197
|
-
handlePointChange(index, 1, Number.parseFloat(e.target.value) || 0)
|
|
220
|
+
handlePointChange(index, 1, toStoredLinear(Number.parseFloat(e.target.value) || 0))
|
|
198
221
|
}
|
|
199
222
|
step={0.5}
|
|
200
223
|
type="number"
|
|
201
|
-
value={point[1]}
|
|
224
|
+
value={Number(toDisplayLinear(point[1]).toFixed(2))}
|
|
202
225
|
/>
|
|
203
226
|
<button
|
|
204
227
|
className={cn(
|
|
@@ -356,13 +379,13 @@ const ReferenceItem = memo(function ReferenceItem({
|
|
|
356
379
|
<img
|
|
357
380
|
alt="Scan"
|
|
358
381
|
className="h-3.5 w-3.5 shrink-0 object-contain opacity-70 transition-opacity group-hover/ref:opacity-100"
|
|
359
|
-
src="/icons/mesh.
|
|
382
|
+
src="/icons/mesh.webp"
|
|
360
383
|
/>
|
|
361
384
|
) : (
|
|
362
385
|
<img
|
|
363
386
|
alt="Guide"
|
|
364
387
|
className="h-3.5 w-3.5 shrink-0 object-contain opacity-70 transition-opacity group-hover/ref:opacity-100"
|
|
365
|
-
src="/icons/floorplan.
|
|
388
|
+
src="/icons/floorplan.webp"
|
|
366
389
|
/>
|
|
367
390
|
)}
|
|
368
391
|
<InlineRenameInput
|
|
@@ -721,7 +744,7 @@ const LevelItem = memo(function LevelItem({
|
|
|
721
744
|
'h-4 w-4 shrink-0 object-contain transition-all duration-200',
|
|
722
745
|
!isSelected && 'opacity-60 grayscale',
|
|
723
746
|
)}
|
|
724
|
-
src="/icons/level.
|
|
747
|
+
src="/icons/level.webp"
|
|
725
748
|
/>
|
|
726
749
|
<InlineRenameInput
|
|
727
750
|
defaultName={getDefaultLevelName(level.level)}
|
|
@@ -999,7 +1022,7 @@ const LayerToggle = memo(function LayerToggle() {
|
|
|
999
1022
|
'mb-1 h-6 w-6 transition-all',
|
|
1000
1023
|
activeTab !== 'structure' && 'opacity-50 grayscale',
|
|
1001
1024
|
)}
|
|
1002
|
-
src="/icons/room.
|
|
1025
|
+
src="/icons/room.webp"
|
|
1003
1026
|
/>
|
|
1004
1027
|
Structure
|
|
1005
1028
|
</div>
|
|
@@ -1035,7 +1058,7 @@ const LayerToggle = memo(function LayerToggle() {
|
|
|
1035
1058
|
'mb-1 h-6 w-6 transition-all',
|
|
1036
1059
|
activeTab !== 'furnish' && 'opacity-50 grayscale',
|
|
1037
1060
|
)}
|
|
1038
|
-
src="/icons/couch.
|
|
1061
|
+
src="/icons/couch.webp"
|
|
1039
1062
|
/>
|
|
1040
1063
|
Furnish
|
|
1041
1064
|
</div>
|
|
@@ -1072,7 +1095,7 @@ const LayerToggle = memo(function LayerToggle() {
|
|
|
1072
1095
|
'mb-1 h-6 w-6 transition-all',
|
|
1073
1096
|
activeTab !== 'zones' && 'opacity-50 grayscale',
|
|
1074
1097
|
)}
|
|
1075
|
-
src="/icons/kitchen.
|
|
1098
|
+
src="/icons/kitchen.webp"
|
|
1076
1099
|
/>
|
|
1077
1100
|
Zones
|
|
1078
1101
|
</div>
|
|
@@ -1097,6 +1120,7 @@ const ZoneItem = memo(function ZoneItem({ zone, isLast }: { zone: ZoneNode; isLa
|
|
|
1097
1120
|
const setHoveredId = useViewer((state) => state.setHoveredId)
|
|
1098
1121
|
const setPhase = useEditor((state) => state.setPhase)
|
|
1099
1122
|
const setMode = useEditor((state) => state.setMode)
|
|
1123
|
+
const unit = useViewer((state) => state.unit)
|
|
1100
1124
|
|
|
1101
1125
|
const isSelected = selectedZoneId === zone.id
|
|
1102
1126
|
const isHovered = hoveredId === zone.id
|
|
@@ -1109,8 +1133,7 @@ const ZoneItem = memo(function ZoneItem({ zone, isLast }: { zone: ZoneNode; isLa
|
|
|
1109
1133
|
}
|
|
1110
1134
|
}, [isSelected])
|
|
1111
1135
|
|
|
1112
|
-
const
|
|
1113
|
-
const defaultName = `Zone (${area}m²)`
|
|
1136
|
+
const defaultName = `Zone (${formatAreaLabel(calculatePolygonArea(zone.polygon), unit)})`
|
|
1114
1137
|
|
|
1115
1138
|
const handleClick = () => {
|
|
1116
1139
|
setSelection({ zoneId: zone.id })
|
|
@@ -1413,7 +1436,7 @@ const BuildingItem = memo(function BuildingItem({
|
|
|
1413
1436
|
'h-5 w-5 object-contain transition-all',
|
|
1414
1437
|
!isBuildingActive && 'opacity-60 grayscale',
|
|
1415
1438
|
)}
|
|
1416
|
-
src="/icons/building.
|
|
1439
|
+
src="/icons/building.webp"
|
|
1417
1440
|
/>
|
|
1418
1441
|
<span className="truncate font-medium text-sm">{building.name || 'Building'}</span>
|
|
1419
1442
|
</div>
|
|
@@ -1569,7 +1592,7 @@ export function SitePanel({ projectId, onUploadAsset, onDeleteAsset }: SitePanel
|
|
|
1569
1592
|
'h-5 w-5 object-contain transition-all',
|
|
1570
1593
|
phase !== 'site' && 'opacity-60 grayscale',
|
|
1571
1594
|
)}
|
|
1572
|
-
src="/icons/site-flag.
|
|
1595
|
+
src="/icons/site-flag.webp"
|
|
1573
1596
|
/>
|
|
1574
1597
|
<span className="font-medium text-sm">{siteNode.name || 'Site'}</span>
|
|
1575
1598
|
</div>
|
|
@@ -3,19 +3,25 @@ import { useViewer } from '@pascal-app/viewer'
|
|
|
3
3
|
import Image from 'next/image'
|
|
4
4
|
import { memo, useCallback, useEffect, useState } from 'react'
|
|
5
5
|
import { useShallow } from 'zustand/react/shallow'
|
|
6
|
-
import
|
|
6
|
+
import { resolveNodeSnapTarget, SnapTargetIcon } from '../../../snap-target-badge'
|
|
7
7
|
import { InlineRenameInput } from './inline-rename-input'
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
focusTreeNode,
|
|
10
|
+
handleTreeSelection,
|
|
11
|
+
routeTreeSelectionToNode,
|
|
12
|
+
TreeNode,
|
|
13
|
+
TreeNodeWrapper,
|
|
14
|
+
} from './tree-node'
|
|
9
15
|
import { TreeNodeActions } from './tree-node-actions'
|
|
10
16
|
|
|
11
17
|
const CATEGORY_ICONS: Record<string, string> = {
|
|
12
|
-
door: '/icons/door.
|
|
13
|
-
window: '/icons/window.
|
|
14
|
-
furniture: '/icons/couch.
|
|
15
|
-
appliance: '/icons/appliance.
|
|
16
|
-
kitchen: '/icons/kitchen.
|
|
17
|
-
bathroom: '/icons/bathroom.
|
|
18
|
-
outdoor: '/icons/tree.
|
|
18
|
+
door: '/icons/door.webp',
|
|
19
|
+
window: '/icons/window.webp',
|
|
20
|
+
furniture: '/icons/couch.webp',
|
|
21
|
+
appliance: '/icons/appliance.webp',
|
|
22
|
+
kitchen: '/icons/kitchen.webp',
|
|
23
|
+
bathroom: '/icons/bathroom.webp',
|
|
24
|
+
outdoor: '/icons/tree.webp',
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
interface ItemTreeNodeProps {
|
|
@@ -35,7 +41,8 @@ export const ItemTreeNode = memo(function ItemTreeNode({
|
|
|
35
41
|
const children = useScene(
|
|
36
42
|
useShallow((s) => (s.nodes[nodeId] as ItemNode | undefined)?.children ?? []),
|
|
37
43
|
)
|
|
38
|
-
const
|
|
44
|
+
const node = useScene((s) => s.nodes[nodeId] as ItemNode | undefined)
|
|
45
|
+
const asset = node?.asset
|
|
39
46
|
const isSelected = useViewer((state) => state.selection.selectedIds.includes(nodeId))
|
|
40
47
|
const isHovered = useViewer((state) => state.hoveredId === nodeId)
|
|
41
48
|
const setSelection = useViewer((state) => state.setSelection)
|
|
@@ -63,17 +70,15 @@ export const ItemTreeNode = memo(function ItemTreeNode({
|
|
|
63
70
|
const handleClick = useCallback(
|
|
64
71
|
(e: React.MouseEvent) => {
|
|
65
72
|
e.stopPropagation()
|
|
66
|
-
|
|
73
|
+
handleTreeSelection(
|
|
67
74
|
e,
|
|
68
75
|
nodeId,
|
|
69
76
|
useViewer.getState().selection.selectedIds,
|
|
70
77
|
setSelection,
|
|
71
78
|
)
|
|
72
|
-
|
|
73
|
-
useEditor.getState().setPhase('furnish')
|
|
74
|
-
}
|
|
79
|
+
routeTreeSelectionToNode(node)
|
|
75
80
|
},
|
|
76
|
-
[nodeId, setSelection],
|
|
81
|
+
[node, nodeId, setSelection],
|
|
77
82
|
)
|
|
78
83
|
|
|
79
84
|
const handleDoubleClick = useCallback(() => focusTreeNode(nodeId), [nodeId])
|
|
@@ -83,7 +88,8 @@ export const ItemTreeNode = memo(function ItemTreeNode({
|
|
|
83
88
|
const handleStartEditing = useCallback(() => setIsEditing(true), [])
|
|
84
89
|
const handleStopEditing = useCallback(() => setIsEditing(false), [])
|
|
85
90
|
|
|
86
|
-
const iconSrc = CATEGORY_ICONS[asset?.category ?? ''] || '/icons/couch.
|
|
91
|
+
const iconSrc = CATEGORY_ICONS[asset?.category ?? ''] || '/icons/couch.webp'
|
|
92
|
+
const snapTarget = resolveNodeSnapTarget(node)
|
|
87
93
|
const defaultName = asset?.name || 'Item'
|
|
88
94
|
const hasChildren = children.length > 0
|
|
89
95
|
|
|
@@ -93,7 +99,15 @@ export const ItemTreeNode = memo(function ItemTreeNode({
|
|
|
93
99
|
depth={depth}
|
|
94
100
|
expanded={expanded}
|
|
95
101
|
hasChildren={hasChildren}
|
|
96
|
-
icon={
|
|
102
|
+
icon={
|
|
103
|
+
snapTarget ? (
|
|
104
|
+
<SnapTargetIcon target={snapTarget}>
|
|
105
|
+
<Image alt="" className="object-contain" height={14} src={iconSrc} width={14} />
|
|
106
|
+
</SnapTargetIcon>
|
|
107
|
+
) : (
|
|
108
|
+
<Image alt="" className="object-contain" height={14} src={iconSrc} width={14} />
|
|
109
|
+
)
|
|
110
|
+
}
|
|
97
111
|
isHovered={isHovered}
|
|
98
112
|
isLast={isLast}
|
|
99
113
|
isSelected={isSelected}
|
|
@@ -3,7 +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 '
|
|
6
|
+
import { getDefaultLevelName } from '@pascal-app/core'
|
|
7
7
|
import { InlineRenameInput } from './inline-rename-input'
|
|
8
8
|
import { focusTreeNode, TreeNode, TreeNodeWrapper } from './tree-node'
|
|
9
9
|
import { TreeNodeActions } from './tree-node-actions'
|
|
@@ -1,11 +1,20 @@
|
|
|
1
|
+
import { Icon as IconifyIcon } from '@iconify/react'
|
|
1
2
|
import { type AnyNodeId, nodeRegistry, useScene } from '@pascal-app/core'
|
|
2
3
|
import { useViewer } from '@pascal-app/viewer'
|
|
3
4
|
import Image from 'next/image'
|
|
4
|
-
import { memo, useCallback, useState } from 'react'
|
|
5
|
-
import
|
|
5
|
+
import { memo, useCallback, useEffect, useState } from 'react'
|
|
6
|
+
import { useShallow } from 'zustand/react/shallow'
|
|
7
|
+
import { resolveNodeSnapTarget, SnapTargetIcon } from '../../../snap-target-badge'
|
|
6
8
|
import { InlineRenameInput } from './inline-rename-input'
|
|
7
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
focusTreeNode,
|
|
11
|
+
handleTreeSelection,
|
|
12
|
+
routeTreeSelectionToNode,
|
|
13
|
+
TreeNode,
|
|
14
|
+
TreeNodeWrapper,
|
|
15
|
+
} from './tree-node'
|
|
8
16
|
import { TreeNodeActions } from './tree-node-actions'
|
|
17
|
+
import { resolveTreeChildIds, treeContainsDescendant } from './tree-structure'
|
|
9
18
|
|
|
10
19
|
interface RegistryTreeNodeProps {
|
|
11
20
|
nodeId: AnyNodeId
|
|
@@ -14,11 +23,11 @@ interface RegistryTreeNodeProps {
|
|
|
14
23
|
}
|
|
15
24
|
|
|
16
25
|
/**
|
|
17
|
-
* Generic,
|
|
18
|
-
* `def.
|
|
26
|
+
* Generic, registry-driven tree-node row powered by `def.presentation` and
|
|
27
|
+
* `def.tree`. Replaces the per-kind boilerplate
|
|
19
28
|
* components that differed only in their default name and icon — today the
|
|
20
|
-
* roof vents
|
|
21
|
-
*
|
|
29
|
+
* roof vents plus cabinet rows. Register a kind in `treeNodeByType` against
|
|
30
|
+
* this component instead of authoring another copy.
|
|
22
31
|
*/
|
|
23
32
|
export const RegistryTreeNode = memo(function RegistryTreeNode({
|
|
24
33
|
nodeId,
|
|
@@ -26,48 +35,76 @@ export const RegistryTreeNode = memo(function RegistryTreeNode({
|
|
|
26
35
|
isLast,
|
|
27
36
|
}: RegistryTreeNodeProps) {
|
|
28
37
|
const [isEditing, setIsEditing] = useState(false)
|
|
38
|
+
const [expanded, setExpanded] = useState(true)
|
|
29
39
|
const isVisible = useScene((s) => s.nodes[nodeId]?.visible !== false)
|
|
30
40
|
const node = useScene((s) => s.nodes[nodeId])
|
|
41
|
+
const children = useScene(useShallow((s) => resolveTreeChildIds(nodeId, s.nodes)))
|
|
31
42
|
const isSelected = useViewer((state) => state.selection.selectedIds.includes(nodeId))
|
|
32
43
|
const isHovered = useViewer((state) => state.hoveredId === nodeId)
|
|
33
44
|
const setSelection = useViewer((state) => state.setSelection)
|
|
34
45
|
const setHoveredId = useViewer((state) => state.setHoveredId)
|
|
35
46
|
|
|
36
47
|
const presentation = node ? nodeRegistry.get(node.type)?.presentation : undefined
|
|
48
|
+
const tree = node ? nodeRegistry.get(node.type)?.tree : undefined
|
|
37
49
|
const icon = presentation?.icon
|
|
38
|
-
const iconSrc = icon?.kind === 'url' ? icon.src : '/icons/roof.
|
|
39
|
-
const
|
|
50
|
+
const iconSrc = icon?.kind === 'url' ? icon.src : '/icons/roof.webp'
|
|
51
|
+
const iconElement =
|
|
52
|
+
icon?.kind === 'iconify' ? (
|
|
53
|
+
<IconifyIcon className="opacity-60" height={14} icon={icon.name} width={14} />
|
|
54
|
+
) : (
|
|
55
|
+
<Image
|
|
56
|
+
alt=""
|
|
57
|
+
className="object-contain opacity-60"
|
|
58
|
+
height={14}
|
|
59
|
+
src={iconSrc}
|
|
60
|
+
width={14}
|
|
61
|
+
/>
|
|
62
|
+
)
|
|
63
|
+
const snapTarget = resolveNodeSnapTarget(node)
|
|
64
|
+
const defaultName =
|
|
65
|
+
node ? tree?.label?.(node, useScene.getState().nodes) || node.name || presentation?.label || 'Node' : 'Node'
|
|
66
|
+
const hasChildren = children.length > 0
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
return useViewer.subscribe((state) => {
|
|
70
|
+
const { selectedIds } = state.selection
|
|
71
|
+
if (selectedIds.length === 0) return
|
|
72
|
+
const nodes = useScene.getState().nodes
|
|
73
|
+
for (const id of selectedIds) {
|
|
74
|
+
if (treeContainsDescendant(nodeId, id as AnyNodeId, nodes)) {
|
|
75
|
+
setExpanded(true)
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
}, [nodeId])
|
|
40
81
|
|
|
41
82
|
const handleClick = useCallback(
|
|
42
83
|
(e: React.MouseEvent) => {
|
|
43
84
|
e.stopPropagation()
|
|
44
|
-
|
|
85
|
+
handleTreeSelection(
|
|
45
86
|
e,
|
|
46
87
|
nodeId,
|
|
47
88
|
useViewer.getState().selection.selectedIds,
|
|
48
89
|
setSelection,
|
|
49
90
|
)
|
|
50
|
-
|
|
51
|
-
useEditor.getState().setPhase('structure')
|
|
52
|
-
}
|
|
91
|
+
routeTreeSelectionToNode(node)
|
|
53
92
|
},
|
|
54
|
-
[nodeId, setSelection],
|
|
93
|
+
[node, nodeId, setSelection],
|
|
55
94
|
)
|
|
56
95
|
|
|
57
96
|
return (
|
|
58
97
|
<TreeNodeWrapper
|
|
59
98
|
actions={<TreeNodeActions nodeId={nodeId} />}
|
|
60
99
|
depth={depth}
|
|
61
|
-
expanded={
|
|
62
|
-
hasChildren={
|
|
100
|
+
expanded={expanded}
|
|
101
|
+
hasChildren={hasChildren}
|
|
63
102
|
icon={
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
width={14}
|
|
70
|
-
/>
|
|
103
|
+
snapTarget ? (
|
|
104
|
+
<SnapTargetIcon target={snapTarget}>{iconElement}</SnapTargetIcon>
|
|
105
|
+
) : (
|
|
106
|
+
iconElement
|
|
107
|
+
)
|
|
71
108
|
}
|
|
72
109
|
isHovered={isHovered}
|
|
73
110
|
isLast={isLast}
|
|
@@ -87,7 +124,17 @@ export const RegistryTreeNode = memo(function RegistryTreeNode({
|
|
|
87
124
|
onDoubleClick={() => focusTreeNode(nodeId)}
|
|
88
125
|
onMouseEnter={() => setHoveredId(nodeId)}
|
|
89
126
|
onMouseLeave={() => setHoveredId(null)}
|
|
90
|
-
onToggle={() =>
|
|
91
|
-
|
|
127
|
+
onToggle={() => setExpanded((prev) => !prev)}
|
|
128
|
+
>
|
|
129
|
+
{hasChildren &&
|
|
130
|
+
children.map((childId, index) => (
|
|
131
|
+
<TreeNode
|
|
132
|
+
depth={depth + 1}
|
|
133
|
+
isLast={index === children.length - 1}
|
|
134
|
+
key={childId}
|
|
135
|
+
nodeId={childId as AnyNodeId}
|
|
136
|
+
/>
|
|
137
|
+
))}
|
|
138
|
+
</TreeNodeWrapper>
|
|
92
139
|
)
|
|
93
140
|
})
|
|
@@ -28,6 +28,7 @@ export const RoofTreeNode = memo(function RoofTreeNode({
|
|
|
28
28
|
const isHovered = useViewer((state) => state.hoveredId === nodeId)
|
|
29
29
|
const setSelection = useViewer((state) => state.setSelection)
|
|
30
30
|
const setHoveredId = useViewer((state) => state.setHoveredId)
|
|
31
|
+
const setRoofHostDragArmedId = useEditor((state) => state.setRoofHostDragArmedId)
|
|
31
32
|
const { drag, dropTarget } = useTreeNodeDrag()
|
|
32
33
|
|
|
33
34
|
const segments = useScene(
|
|
@@ -57,8 +58,11 @@ export const RoofTreeNode = memo(function RoofTreeNode({
|
|
|
57
58
|
if (!handled && useEditor.getState().phase === 'furnish') {
|
|
58
59
|
useEditor.getState().setPhase('structure')
|
|
59
60
|
}
|
|
61
|
+
if (!e.metaKey && !e.ctrlKey && !e.shiftKey) {
|
|
62
|
+
setRoofHostDragArmedId(nodeId)
|
|
63
|
+
}
|
|
60
64
|
},
|
|
61
|
-
[nodeId, setSelection],
|
|
65
|
+
[nodeId, setRoofHostDragArmedId, setSelection],
|
|
62
66
|
)
|
|
63
67
|
|
|
64
68
|
const handleDoubleClick = useCallback(() => focusTreeNode(nodeId), [nodeId])
|
|
@@ -98,7 +102,7 @@ export const RoofTreeNode = memo(function RoofTreeNode({
|
|
|
98
102
|
expanded={expanded}
|
|
99
103
|
hasChildren={segments.length > 0}
|
|
100
104
|
icon={
|
|
101
|
-
<Image alt="" className="object-contain" height={14} src="/icons/roof.
|
|
105
|
+
<Image alt="" className="object-contain" height={14} src="/icons/roof.webp" width={14} />
|
|
102
106
|
}
|
|
103
107
|
isDropTarget={isValidDropTarget && isDropTarget}
|
|
104
108
|
isHovered={isHovered || isDropTarget}
|
|
@@ -230,7 +234,7 @@ function RoofSegmentTreeNode({
|
|
|
230
234
|
alt=""
|
|
231
235
|
className="object-contain opacity-60"
|
|
232
236
|
height={14}
|
|
233
|
-
src="/icons/roof.
|
|
237
|
+
src="/icons/roof.webp"
|
|
234
238
|
width={14}
|
|
235
239
|
/>
|
|
236
240
|
}
|
|
@@ -5,9 +5,14 @@ import { useViewer } from '@pascal-app/viewer'
|
|
|
5
5
|
import Image from 'next/image'
|
|
6
6
|
import { memo, useCallback, useEffect, useState } from 'react'
|
|
7
7
|
import { useShallow } from 'zustand/react/shallow'
|
|
8
|
-
import useEditor from './../../../../../store/use-editor'
|
|
9
8
|
import { InlineRenameInput } from './inline-rename-input'
|
|
10
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
focusTreeNode,
|
|
11
|
+
handleTreeSelection,
|
|
12
|
+
routeTreeSelectionToNode,
|
|
13
|
+
TreeNode,
|
|
14
|
+
TreeNodeWrapper,
|
|
15
|
+
} from './tree-node'
|
|
11
16
|
import { TreeNodeActions } from './tree-node-actions'
|
|
12
17
|
|
|
13
18
|
interface ShelfTreeNodeProps {
|
|
@@ -34,6 +39,7 @@ export const ShelfTreeNode = memo(function ShelfTreeNode({
|
|
|
34
39
|
const children = useScene(
|
|
35
40
|
useShallow((s) => (s.nodes[nodeId] as ShelfNode | undefined)?.children ?? []),
|
|
36
41
|
)
|
|
42
|
+
const node = useScene((s) => s.nodes[nodeId] as ShelfNode | undefined)
|
|
37
43
|
const isSelected = useViewer((state) => state.selection.selectedIds.includes(nodeId))
|
|
38
44
|
const isHovered = useViewer((state) => state.hoveredId === nodeId)
|
|
39
45
|
const setSelection = useViewer((state) => state.setSelection)
|
|
@@ -63,17 +69,15 @@ export const ShelfTreeNode = memo(function ShelfTreeNode({
|
|
|
63
69
|
const handleClick = useCallback(
|
|
64
70
|
(e: React.MouseEvent) => {
|
|
65
71
|
e.stopPropagation()
|
|
66
|
-
|
|
72
|
+
handleTreeSelection(
|
|
67
73
|
e,
|
|
68
74
|
nodeId,
|
|
69
75
|
useViewer.getState().selection.selectedIds,
|
|
70
76
|
setSelection,
|
|
71
77
|
)
|
|
72
|
-
|
|
73
|
-
useEditor.getState().setPhase('structure')
|
|
74
|
-
}
|
|
78
|
+
routeTreeSelectionToNode(node)
|
|
75
79
|
},
|
|
76
|
-
[nodeId, setSelection],
|
|
80
|
+
[node, nodeId, setSelection],
|
|
77
81
|
)
|
|
78
82
|
|
|
79
83
|
const handleDoubleClick = useCallback(() => focusTreeNode(nodeId), [nodeId])
|
|
@@ -92,7 +96,7 @@ export const ShelfTreeNode = memo(function ShelfTreeNode({
|
|
|
92
96
|
expanded={expanded}
|
|
93
97
|
hasChildren={hasChildren}
|
|
94
98
|
icon={
|
|
95
|
-
<Image alt="" className="object-contain" height={14} src="/icons/shelf.
|
|
99
|
+
<Image alt="" className="object-contain" height={14} src="/icons/shelf.webp" width={14} />
|
|
96
100
|
}
|
|
97
101
|
isHovered={isHovered}
|
|
98
102
|
isLast={isLast}
|
|
@@ -2,6 +2,7 @@ import { type AnyNodeId, type SlabNode, useScene } from '@pascal-app/core'
|
|
|
2
2
|
import { useViewer } from '@pascal-app/viewer'
|
|
3
3
|
import Image from 'next/image'
|
|
4
4
|
import { memo, useCallback, useState } from 'react'
|
|
5
|
+
import { formatAreaLabel } from './../../../../../lib/measurements'
|
|
5
6
|
import useEditor from './../../../../../store/use-editor'
|
|
6
7
|
import { InlineRenameInput } from './inline-rename-input'
|
|
7
8
|
import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
|
|
@@ -25,6 +26,7 @@ export const SlabTreeNode = memo(function SlabTreeNode({
|
|
|
25
26
|
const isHovered = useViewer((state) => state.hoveredId === nodeId)
|
|
26
27
|
const setSelection = useViewer((state) => state.setSelection)
|
|
27
28
|
const setHoveredId = useViewer((state) => state.setHoveredId)
|
|
29
|
+
const unit = useViewer((state) => state.unit)
|
|
28
30
|
|
|
29
31
|
const handleClick = useCallback(
|
|
30
32
|
(e: React.MouseEvent) => {
|
|
@@ -45,8 +47,7 @@ export const SlabTreeNode = memo(function SlabTreeNode({
|
|
|
45
47
|
const handleStartEditing = useCallback(() => setIsEditing(true), [])
|
|
46
48
|
const handleStopEditing = useCallback(() => setIsEditing(false), [])
|
|
47
49
|
|
|
48
|
-
const
|
|
49
|
-
const defaultName = `Slab (${area}m²)`
|
|
50
|
+
const defaultName = `Slab (${formatAreaLabel(calculatePolygonArea(polygon), unit)})`
|
|
50
51
|
|
|
51
52
|
return (
|
|
52
53
|
<TreeNodeWrapper
|
|
@@ -55,7 +56,7 @@ export const SlabTreeNode = memo(function SlabTreeNode({
|
|
|
55
56
|
expanded={false}
|
|
56
57
|
hasChildren={false}
|
|
57
58
|
icon={
|
|
58
|
-
<Image alt="" className="object-contain" height={14} src="/icons/floor.
|
|
59
|
+
<Image alt="" className="object-contain" height={14} src="/icons/floor.webp" width={14} />
|
|
59
60
|
}
|
|
60
61
|
isHovered={isHovered}
|
|
61
62
|
isLast={isLast}
|