@pascal-app/editor 0.7.0 → 0.8.0
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 +6 -6
- package/src/components/editor/custom-camera-controls.tsx +2 -1
- package/src/components/editor/editor-layout-v2.tsx +4 -3
- package/src/components/editor/first-person/build-collider-world.ts +5 -7
- package/src/components/editor/first-person/bvh-ecctrl.tsx +119 -54
- package/src/components/editor/first-person-controls.tsx +11 -11
- package/src/components/editor/floating-action-menu.tsx +0 -0
- package/src/components/editor/floorplan-panel.tsx +44 -37
- package/src/components/editor/index.tsx +68 -53
- package/src/components/editor/selection-manager.tsx +2 -2
- package/src/components/editor/snapshot-capture-overlay.tsx +465 -0
- package/src/components/editor/thumbnail-generator.tsx +18 -61
- package/src/components/editor/use-floorplan-background-placement.ts +3 -3
- package/src/components/editor/wall-measurement-label.tsx +0 -0
- package/src/components/editor-2d/renderers/floorplan-draft-layer.tsx +6 -1
- package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +6 -1
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +5 -5
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +10 -12
- package/src/components/systems/roof/roof-edit-system.tsx +1 -1
- package/src/components/systems/stair/stair-edit-system.tsx +1 -1
- 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/ceiling/move-ceiling-tool.tsx +9 -2
- package/src/components/tools/fence/curve-fence-tool.tsx +4 -5
- package/src/components/tools/fence/fence-tool.tsx +2 -2
- package/src/components/tools/fence/move-fence-endpoint-tool.tsx +11 -8
- package/src/components/tools/fence/move-fence-tool.tsx +13 -9
- package/src/components/tools/item/move-tool.tsx +3 -6
- package/src/components/tools/item/placement-math.ts +2 -4
- package/src/components/tools/item/placement-strategies.ts +11 -10
- package/src/components/tools/item/use-draft-node.ts +0 -1
- package/src/components/tools/item/use-placement-coordinator.tsx +9 -111
- package/src/components/tools/roof/move-roof-tool.tsx +7 -2
- package/src/components/tools/select/box-select-tool.tsx +12 -17
- package/src/components/tools/shared/segment-angle.ts +1 -1
- package/src/components/tools/tool-manager.tsx +12 -12
- package/src/components/tools/wall/curve-wall-tool.tsx +8 -6
- package/src/components/tools/wall/move-wall-endpoint-tool.tsx +11 -8
- package/src/components/tools/wall/move-wall-tool.tsx +6 -4
- package/src/components/tools/wall/wall-drafting.ts +0 -0
- package/src/components/tools/wall/wall-tool.tsx +3 -3
- package/src/components/tools/zone/zone-tool.tsx +20 -5
- package/src/components/ui/action-menu/camera-actions.tsx +0 -0
- package/src/components/ui/action-menu/control-modes.tsx +7 -1
- package/src/components/ui/action-menu/furnish-tools.tsx +6 -92
- package/src/components/ui/action-menu/index.tsx +35 -86
- package/src/components/ui/action-menu/view-toggles.tsx +19 -31
- package/src/components/ui/command-palette/editor-commands.tsx +6 -4
- package/src/components/ui/command-palette/index.tsx +4 -255
- package/src/components/ui/controls/material-picker.tsx +8 -5
- package/src/components/ui/floating-level-selector.tsx +1 -1
- package/src/components/ui/helpers/helper-manager.tsx +5 -0
- package/src/components/ui/item-catalog/catalog-items.tsx +1742 -315
- package/src/components/ui/item-catalog/item-catalog.tsx +88 -46
- package/src/components/ui/level-duplicate-dialog.tsx +3 -5
- package/src/components/ui/panels/ceiling-panel.tsx +2 -3
- package/src/components/ui/panels/column-panel.tsx +62 -18
- package/src/components/ui/panels/door-panel.tsx +272 -265
- package/src/components/ui/panels/fence-panel.tsx +0 -5
- package/src/components/ui/panels/paint-panel.tsx +66 -41
- package/src/components/ui/panels/panel-manager.tsx +3 -32
- package/src/components/ui/panels/reference-panel.tsx +28 -13
- package/src/components/ui/panels/roof-panel.tsx +52 -2
- package/src/components/ui/panels/roof-segment-panel.tsx +0 -0
- package/src/components/ui/panels/slab-panel.tsx +0 -0
- package/src/components/ui/panels/spawn-panel.tsx +10 -4
- package/src/components/ui/panels/stair-panel.tsx +66 -14
- package/src/components/ui/panels/wall-panel.tsx +97 -1
- package/src/components/ui/panels/window-panel.tsx +13 -5
- package/src/components/ui/primitives/number-input.tsx +1 -1
- package/src/components/ui/primitives/sidebar.tsx +0 -0
- 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/index.tsx +330 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +0 -0
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +0 -0
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +4 -6
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +0 -0
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -7
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +3 -1
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -1
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +1 -1
- package/src/components/ui/slider.tsx +1 -1
- package/src/components/viewer-overlay.tsx +0 -0
- package/src/components/viewer-zone-system.tsx +0 -0
- package/src/hooks/use-auto-save.ts +14 -0
- package/src/hooks/use-keyboard.ts +10 -0
- package/src/index.tsx +8 -1
- package/src/lib/level-duplication.test.ts +0 -2
- package/src/lib/level-duplication.ts +1 -1
- package/src/lib/material-paint.ts +1 -1
- package/src/lib/roof-duplication.ts +1 -1
- package/src/lib/scene-bounds.ts +1 -1
- package/src/lib/scene.ts +0 -0
- package/src/lib/sfx-bus.ts +2 -0
- package/src/lib/sfx-player.ts +5 -5
- package/src/lib/stair-duplication.ts +2 -2
- package/src/store/use-editor.tsx +27 -59
- package/tsconfig.json +2 -1
- 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/ui/viewer-toolbar.tsx +0 -436
|
@@ -12,8 +12,8 @@ import { useViewer } from '@pascal-app/viewer'
|
|
|
12
12
|
import { BookMarked, Copy, DoorOpen, FlipHorizontal2, Move, Trash2 } from 'lucide-react'
|
|
13
13
|
import { useCallback, useRef } from 'react'
|
|
14
14
|
import { usePresetsAdapter } from '../../../contexts/presets-context'
|
|
15
|
-
import { cn } from '../../../lib/utils'
|
|
16
15
|
import { sfxEmitter } from '../../../lib/sfx-bus'
|
|
16
|
+
import { cn } from '../../../lib/utils'
|
|
17
17
|
import useEditor from '../../../store/use-editor'
|
|
18
18
|
import { ActionButton, ActionGroup } from '../controls/action-button'
|
|
19
19
|
import { MetricControl } from '../controls/metric-control'
|
|
@@ -148,7 +148,13 @@ export function DoorPanel() {
|
|
|
148
148
|
const liveNode = useScene.getState().nodes[selectedId as AnyNodeId]
|
|
149
149
|
if (liveNode?.type !== 'door') return
|
|
150
150
|
|
|
151
|
-
if (
|
|
151
|
+
if (
|
|
152
|
+
!(
|
|
153
|
+
previewRef.current &&
|
|
154
|
+
previewRef.current.id === selectedId &&
|
|
155
|
+
previewRef.current.key === key
|
|
156
|
+
)
|
|
157
|
+
) {
|
|
152
158
|
previewRef.current = {
|
|
153
159
|
id: selectedId as AnyNodeId,
|
|
154
160
|
key,
|
|
@@ -333,7 +339,8 @@ export function DoorPanel() {
|
|
|
333
339
|
const normHeights = node.segments.map((seg) => seg.heightRatio / hSum)
|
|
334
340
|
const isOpening = node.openingKind === 'opening'
|
|
335
341
|
const openingShape = node.openingShape ?? 'rectangle'
|
|
336
|
-
const doorShape =
|
|
342
|
+
const doorShape =
|
|
343
|
+
openingShape === 'arch' || openingShape === 'rounded' ? openingShape : 'rectangle'
|
|
337
344
|
const openingRadiusMode = node.openingRadiusMode ?? 'all'
|
|
338
345
|
const openingTopRadii = node.openingTopRadii ?? [0.15, 0.15]
|
|
339
346
|
const cornerRadius = node.cornerRadius ?? 0.15
|
|
@@ -578,7 +585,8 @@ export function DoorPanel() {
|
|
|
578
585
|
isSelected
|
|
579
586
|
? 'border-orange-400/60 bg-orange-400/10 text-foreground'
|
|
580
587
|
: 'border-border/50 bg-[#2C2C2E] text-muted-foreground hover:bg-[#3e3e3e] hover:text-foreground',
|
|
581
|
-
!option.available &&
|
|
588
|
+
!option.available &&
|
|
589
|
+
'cursor-not-allowed opacity-45 hover:bg-[#2C2C2E] hover:text-muted-foreground',
|
|
582
590
|
)}
|
|
583
591
|
disabled={!option.available}
|
|
584
592
|
key={option.value}
|
|
@@ -962,312 +970,311 @@ export function DoorPanel() {
|
|
|
962
970
|
/>
|
|
963
971
|
</PanelSection>
|
|
964
972
|
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
/>
|
|
977
|
-
<SliderControl
|
|
978
|
-
label="Vertical"
|
|
979
|
-
max={0.2}
|
|
980
|
-
min={0}
|
|
981
|
-
onChange={(v) => handleUpdate({ contentPadding: [node.contentPadding[0], v] })}
|
|
982
|
-
precision={3}
|
|
983
|
-
step={0.005}
|
|
984
|
-
unit="m"
|
|
985
|
-
value={Math.round(node.contentPadding[1] * 1000) / 1000}
|
|
986
|
-
/>
|
|
987
|
-
</PanelSection>
|
|
988
|
-
)}
|
|
989
|
-
|
|
990
|
-
{isSwingDoor && (
|
|
991
|
-
<PanelSection title="Swing">
|
|
992
|
-
<div className="flex flex-col gap-2 px-1 pb-1">
|
|
993
|
-
<div className="space-y-1">
|
|
994
|
-
<span className="font-medium text-[10px] text-muted-foreground/80 uppercase tracking-wider">
|
|
995
|
-
Hinges Side
|
|
996
|
-
</span>
|
|
997
|
-
<SegmentedControl
|
|
998
|
-
onChange={(v) => handleUpdate({ hingesSide: v })}
|
|
999
|
-
options={[
|
|
1000
|
-
{ label: 'Left', value: 'left' },
|
|
1001
|
-
{ label: 'Right', value: 'right' },
|
|
1002
|
-
]}
|
|
1003
|
-
value={node.hingesSide}
|
|
1004
|
-
/>
|
|
1005
|
-
</div>
|
|
1006
|
-
<div className="space-y-1">
|
|
1007
|
-
<span className="font-medium text-[10px] text-muted-foreground/80 uppercase tracking-wider">
|
|
1008
|
-
Direction
|
|
1009
|
-
</span>
|
|
1010
|
-
<SegmentedControl
|
|
1011
|
-
onChange={(v) => handleUpdate({ swingDirection: v })}
|
|
1012
|
-
options={[
|
|
1013
|
-
{ label: 'Inward', value: 'inward' },
|
|
1014
|
-
{ label: 'Outward', value: 'outward' },
|
|
1015
|
-
]}
|
|
1016
|
-
value={node.swingDirection}
|
|
973
|
+
{!isGarageDoor && (
|
|
974
|
+
<PanelSection title="Content Padding">
|
|
975
|
+
<SliderControl
|
|
976
|
+
label="Horizontal"
|
|
977
|
+
max={0.2}
|
|
978
|
+
min={0}
|
|
979
|
+
onChange={(v) => handleUpdate({ contentPadding: [v, node.contentPadding[1]] })}
|
|
980
|
+
precision={3}
|
|
981
|
+
step={0.005}
|
|
982
|
+
unit="m"
|
|
983
|
+
value={Math.round(node.contentPadding[0] * 1000) / 1000}
|
|
1017
984
|
/>
|
|
1018
|
-
</div>
|
|
1019
|
-
</div>
|
|
1020
|
-
</PanelSection>
|
|
1021
|
-
)}
|
|
1022
|
-
|
|
1023
|
-
{isSwingDoor && (
|
|
1024
|
-
<PanelSection title="Threshold">
|
|
1025
|
-
<ToggleControl
|
|
1026
|
-
checked={node.threshold}
|
|
1027
|
-
label="Enable Threshold"
|
|
1028
|
-
onChange={(checked) => handleUpdate({ threshold: checked })}
|
|
1029
|
-
/>
|
|
1030
|
-
{node.threshold && (
|
|
1031
|
-
<div className="mt-1 flex flex-col gap-1">
|
|
1032
985
|
<SliderControl
|
|
1033
|
-
label="
|
|
1034
|
-
max={0.
|
|
1035
|
-
min={0
|
|
1036
|
-
onChange={(v) => handleUpdate({
|
|
986
|
+
label="Vertical"
|
|
987
|
+
max={0.2}
|
|
988
|
+
min={0}
|
|
989
|
+
onChange={(v) => handleUpdate({ contentPadding: [node.contentPadding[0], v] })}
|
|
1037
990
|
precision={3}
|
|
1038
991
|
step={0.005}
|
|
1039
992
|
unit="m"
|
|
1040
|
-
value={Math.round(node.
|
|
993
|
+
value={Math.round(node.contentPadding[1] * 1000) / 1000}
|
|
1041
994
|
/>
|
|
1042
|
-
</
|
|
995
|
+
</PanelSection>
|
|
1043
996
|
)}
|
|
1044
|
-
</PanelSection>
|
|
1045
|
-
)}
|
|
1046
997
|
|
|
1047
|
-
{!isGarageDoor && (
|
|
1048
|
-
<PanelSection title="Handle">
|
|
1049
998
|
{isSwingDoor && (
|
|
1050
|
-
<
|
|
1051
|
-
|
|
1052
|
-
label="Enable Handle"
|
|
1053
|
-
onChange={(checked) => handleUpdate({ handle: checked })}
|
|
1054
|
-
/>
|
|
1055
|
-
)}
|
|
1056
|
-
{(node.handle || !isSwingDoor) && (
|
|
1057
|
-
<div className="mt-1 flex flex-col gap-1">
|
|
1058
|
-
<SliderControl
|
|
1059
|
-
label="Height"
|
|
1060
|
-
max={node.height - 0.1}
|
|
1061
|
-
min={0.5}
|
|
1062
|
-
onChange={(v) => handleUpdate({ handleHeight: v })}
|
|
1063
|
-
precision={2}
|
|
1064
|
-
step={0.05}
|
|
1065
|
-
unit="m"
|
|
1066
|
-
value={Math.round(node.handleHeight * 100) / 100}
|
|
1067
|
-
/>
|
|
1068
|
-
{supportsHandleSide && (
|
|
999
|
+
<PanelSection title="Swing">
|
|
1000
|
+
<div className="flex flex-col gap-2 px-1 pb-1">
|
|
1069
1001
|
<div className="space-y-1">
|
|
1070
1002
|
<span className="font-medium text-[10px] text-muted-foreground/80 uppercase tracking-wider">
|
|
1071
|
-
|
|
1003
|
+
Hinges Side
|
|
1072
1004
|
</span>
|
|
1073
1005
|
<SegmentedControl
|
|
1074
|
-
onChange={(v) => handleUpdate({
|
|
1006
|
+
onChange={(v) => handleUpdate({ hingesSide: v })}
|
|
1075
1007
|
options={[
|
|
1076
1008
|
{ label: 'Left', value: 'left' },
|
|
1077
1009
|
{ label: 'Right', value: 'right' },
|
|
1078
1010
|
]}
|
|
1079
|
-
value={node.
|
|
1011
|
+
value={node.hingesSide}
|
|
1080
1012
|
/>
|
|
1081
1013
|
</div>
|
|
1082
|
-
|
|
1083
|
-
|
|
1014
|
+
<div className="space-y-1">
|
|
1015
|
+
<span className="font-medium text-[10px] text-muted-foreground/80 uppercase tracking-wider">
|
|
1016
|
+
Direction
|
|
1017
|
+
</span>
|
|
1018
|
+
<SegmentedControl
|
|
1019
|
+
onChange={(v) => handleUpdate({ swingDirection: v })}
|
|
1020
|
+
options={[
|
|
1021
|
+
{ label: 'Inward', value: 'inward' },
|
|
1022
|
+
{ label: 'Outward', value: 'outward' },
|
|
1023
|
+
]}
|
|
1024
|
+
value={node.swingDirection}
|
|
1025
|
+
/>
|
|
1026
|
+
</div>
|
|
1027
|
+
</div>
|
|
1028
|
+
</PanelSection>
|
|
1084
1029
|
)}
|
|
1085
|
-
</PanelSection>
|
|
1086
|
-
)}
|
|
1087
1030
|
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
/>
|
|
1095
|
-
<ToggleControl
|
|
1096
|
-
checked={node.panicBar}
|
|
1097
|
-
label="Panic Bar"
|
|
1098
|
-
onChange={(checked) => handleUpdate({ panicBar: checked })}
|
|
1099
|
-
/>
|
|
1100
|
-
{node.panicBar && (
|
|
1101
|
-
<div className="mt-1 flex flex-col gap-1">
|
|
1102
|
-
<SliderControl
|
|
1103
|
-
label="Bar Height"
|
|
1104
|
-
max={node.height - 0.1}
|
|
1105
|
-
min={0.5}
|
|
1106
|
-
onChange={(v) => handleUpdate({ panicBarHeight: v })}
|
|
1107
|
-
precision={2}
|
|
1108
|
-
step={0.05}
|
|
1109
|
-
unit="m"
|
|
1110
|
-
value={Math.round(node.panicBarHeight * 100) / 100}
|
|
1031
|
+
{isSwingDoor && (
|
|
1032
|
+
<PanelSection title="Threshold">
|
|
1033
|
+
<ToggleControl
|
|
1034
|
+
checked={node.threshold}
|
|
1035
|
+
label="Enable Threshold"
|
|
1036
|
+
onChange={(checked) => handleUpdate({ threshold: checked })}
|
|
1111
1037
|
/>
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
<div className="mb-2 flex flex-col gap-1" key={i}>
|
|
1125
|
-
<div className="flex items-center justify-between pb-1">
|
|
1126
|
-
<span className="font-medium text-white/80 text-xs">Segment {i + 1}</span>
|
|
1038
|
+
{node.threshold && (
|
|
1039
|
+
<div className="mt-1 flex flex-col gap-1">
|
|
1040
|
+
<SliderControl
|
|
1041
|
+
label="Height"
|
|
1042
|
+
max={0.1}
|
|
1043
|
+
min={0.005}
|
|
1044
|
+
onChange={(v) => handleUpdate({ thresholdHeight: v })}
|
|
1045
|
+
precision={3}
|
|
1046
|
+
step={0.005}
|
|
1047
|
+
unit="m"
|
|
1048
|
+
value={Math.round(node.thresholdHeight * 1000) / 1000}
|
|
1049
|
+
/>
|
|
1127
1050
|
</div>
|
|
1051
|
+
)}
|
|
1052
|
+
</PanelSection>
|
|
1053
|
+
)}
|
|
1128
1054
|
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
}}
|
|
1136
|
-
options={[
|
|
1137
|
-
{ label: 'Panel', value: 'panel' },
|
|
1138
|
-
{ label: 'Glass', value: 'glass' },
|
|
1139
|
-
{ label: 'Empty', value: 'empty' },
|
|
1140
|
-
]}
|
|
1141
|
-
value={seg.type}
|
|
1142
|
-
/>
|
|
1143
|
-
|
|
1144
|
-
<SliderControl
|
|
1145
|
-
label="Height"
|
|
1146
|
-
max={95}
|
|
1147
|
-
min={5}
|
|
1148
|
-
onChange={(v) => setSegmentHeightRatio(i, v / 100)}
|
|
1149
|
-
precision={1}
|
|
1150
|
-
step={1}
|
|
1151
|
-
unit="%"
|
|
1152
|
-
value={Math.round(normHeights[i]! * 100 * 10) / 10}
|
|
1055
|
+
{!isGarageDoor && (
|
|
1056
|
+
<PanelSection title="Handle">
|
|
1057
|
+
{isSwingDoor && (
|
|
1058
|
+
<ToggleControl
|
|
1059
|
+
checked={node.handle}
|
|
1060
|
+
label="Enable Handle"
|
|
1061
|
+
onChange={(checked) => handleUpdate({ handle: checked })}
|
|
1153
1062
|
/>
|
|
1063
|
+
)}
|
|
1064
|
+
{(node.handle || !isSwingDoor) && (
|
|
1065
|
+
<div className="mt-1 flex flex-col gap-1">
|
|
1066
|
+
<SliderControl
|
|
1067
|
+
label="Height"
|
|
1068
|
+
max={node.height - 0.1}
|
|
1069
|
+
min={0.5}
|
|
1070
|
+
onChange={(v) => handleUpdate({ handleHeight: v })}
|
|
1071
|
+
precision={2}
|
|
1072
|
+
step={0.05}
|
|
1073
|
+
unit="m"
|
|
1074
|
+
value={Math.round(node.handleHeight * 100) / 100}
|
|
1075
|
+
/>
|
|
1076
|
+
{supportsHandleSide && (
|
|
1077
|
+
<div className="space-y-1">
|
|
1078
|
+
<span className="font-medium text-[10px] text-muted-foreground/80 uppercase tracking-wider">
|
|
1079
|
+
Handle Side
|
|
1080
|
+
</span>
|
|
1081
|
+
<SegmentedControl
|
|
1082
|
+
onChange={(v) => handleUpdate({ handleSide: v })}
|
|
1083
|
+
options={[
|
|
1084
|
+
{ label: 'Left', value: 'left' },
|
|
1085
|
+
{ label: 'Right', value: 'right' },
|
|
1086
|
+
]}
|
|
1087
|
+
value={node.handleSide}
|
|
1088
|
+
/>
|
|
1089
|
+
</div>
|
|
1090
|
+
)}
|
|
1091
|
+
</div>
|
|
1092
|
+
)}
|
|
1093
|
+
</PanelSection>
|
|
1094
|
+
)}
|
|
1154
1095
|
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1096
|
+
{isSwingDoor && (
|
|
1097
|
+
<PanelSection title="Hardware">
|
|
1098
|
+
<ToggleControl
|
|
1099
|
+
checked={node.doorCloser}
|
|
1100
|
+
label="Door Closer"
|
|
1101
|
+
onChange={(checked) => handleUpdate({ doorCloser: checked })}
|
|
1102
|
+
/>
|
|
1103
|
+
<ToggleControl
|
|
1104
|
+
checked={node.panicBar}
|
|
1105
|
+
label="Panic Bar"
|
|
1106
|
+
onChange={(checked) => handleUpdate({ panicBar: checked })}
|
|
1107
|
+
/>
|
|
1108
|
+
{node.panicBar && (
|
|
1109
|
+
<div className="mt-1 flex flex-col gap-1">
|
|
1110
|
+
<SliderControl
|
|
1111
|
+
label="Bar Height"
|
|
1112
|
+
max={node.height - 0.1}
|
|
1113
|
+
min={0.5}
|
|
1114
|
+
onChange={(v) => handleUpdate({ panicBarHeight: v })}
|
|
1115
|
+
precision={2}
|
|
1116
|
+
step={0.05}
|
|
1117
|
+
unit="m"
|
|
1118
|
+
value={Math.round(node.panicBarHeight * 100) / 100}
|
|
1119
|
+
/>
|
|
1120
|
+
</div>
|
|
1121
|
+
)}
|
|
1122
|
+
</PanelSection>
|
|
1123
|
+
)}
|
|
1170
1124
|
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
))}
|
|
1186
|
-
<SliderControl
|
|
1187
|
-
label="Divider"
|
|
1188
|
-
max={0.1}
|
|
1189
|
-
min={0.005}
|
|
1190
|
-
onChange={(v) => {
|
|
1125
|
+
{!isGarageDoor && (
|
|
1126
|
+
<PanelSection title="Segments">
|
|
1127
|
+
{node.segments.map((seg, i) => {
|
|
1128
|
+
const numCols = seg.columnRatios.length
|
|
1129
|
+
const colSum = seg.columnRatios.reduce((a, b) => a + b, 0)
|
|
1130
|
+
const normCols = seg.columnRatios.map((r) => r / colSum)
|
|
1131
|
+
return (
|
|
1132
|
+
<div className="mb-2 flex flex-col gap-1" key={i}>
|
|
1133
|
+
<div className="flex items-center justify-between pb-1">
|
|
1134
|
+
<span className="font-medium text-white/80 text-xs">Segment {i + 1}</span>
|
|
1135
|
+
</div>
|
|
1136
|
+
|
|
1137
|
+
<SegmentedControl
|
|
1138
|
+
onChange={(t) => {
|
|
1191
1139
|
const updated = node.segments.map((s, idx) =>
|
|
1192
|
-
idx === i ? { ...s,
|
|
1140
|
+
idx === i ? { ...s, type: t } : s,
|
|
1193
1141
|
)
|
|
1194
1142
|
handleUpdate({ segments: updated })
|
|
1195
1143
|
}}
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1144
|
+
options={[
|
|
1145
|
+
{ label: 'Panel', value: 'panel' },
|
|
1146
|
+
{ label: 'Glass', value: 'glass' },
|
|
1147
|
+
{ label: 'Empty', value: 'empty' },
|
|
1148
|
+
]}
|
|
1149
|
+
value={seg.type}
|
|
1200
1150
|
/>
|
|
1201
|
-
</div>
|
|
1202
|
-
)}
|
|
1203
1151
|
|
|
1204
|
-
{seg.type === 'panel' && (
|
|
1205
|
-
<div className="mt-1 border-border/50 border-t pt-1">
|
|
1206
1152
|
<SliderControl
|
|
1207
|
-
label="
|
|
1208
|
-
max={
|
|
1209
|
-
min={
|
|
1210
|
-
onChange={(v) =>
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
}}
|
|
1216
|
-
precision={3}
|
|
1217
|
-
step={0.005}
|
|
1218
|
-
unit="m"
|
|
1219
|
-
value={Math.round(seg.panelInset * 1000) / 1000}
|
|
1153
|
+
label="Height"
|
|
1154
|
+
max={95}
|
|
1155
|
+
min={5}
|
|
1156
|
+
onChange={(v) => setSegmentHeightRatio(i, v / 100)}
|
|
1157
|
+
precision={1}
|
|
1158
|
+
step={1}
|
|
1159
|
+
unit="%"
|
|
1160
|
+
value={Math.round(normHeights[i]! * 100 * 10) / 10}
|
|
1220
1161
|
/>
|
|
1162
|
+
|
|
1221
1163
|
<SliderControl
|
|
1222
|
-
label="
|
|
1223
|
-
max={
|
|
1224
|
-
min={
|
|
1164
|
+
label="Columns"
|
|
1165
|
+
max={8}
|
|
1166
|
+
min={1}
|
|
1225
1167
|
onChange={(v) => {
|
|
1168
|
+
const n = Math.max(1, Math.min(8, Math.round(v)))
|
|
1226
1169
|
const updated = node.segments.map((s, idx) =>
|
|
1227
|
-
idx === i ? { ...s,
|
|
1170
|
+
idx === i ? { ...s, columnRatios: Array(n).fill(1 / n) } : s,
|
|
1228
1171
|
)
|
|
1229
1172
|
handleUpdate({ segments: updated })
|
|
1230
1173
|
}}
|
|
1231
|
-
precision={
|
|
1232
|
-
step={
|
|
1233
|
-
|
|
1234
|
-
value={Math.round(seg.panelDepth * 1000) / 1000}
|
|
1174
|
+
precision={0}
|
|
1175
|
+
step={1}
|
|
1176
|
+
value={numCols}
|
|
1235
1177
|
/>
|
|
1178
|
+
|
|
1179
|
+
{numCols > 1 && (
|
|
1180
|
+
<div className="mt-1 border-border/50 border-t pt-1">
|
|
1181
|
+
{normCols.map((ratio, ci) => (
|
|
1182
|
+
<SliderControl
|
|
1183
|
+
key={`c-${ci}`}
|
|
1184
|
+
label={`C${ci + 1}`}
|
|
1185
|
+
max={95}
|
|
1186
|
+
min={5}
|
|
1187
|
+
onChange={(v) => setSegmentColumnRatio(i, ci, v / 100)}
|
|
1188
|
+
precision={1}
|
|
1189
|
+
step={1}
|
|
1190
|
+
unit="%"
|
|
1191
|
+
value={Math.round(ratio * 100 * 10) / 10}
|
|
1192
|
+
/>
|
|
1193
|
+
))}
|
|
1194
|
+
<SliderControl
|
|
1195
|
+
label="Divider"
|
|
1196
|
+
max={0.1}
|
|
1197
|
+
min={0.005}
|
|
1198
|
+
onChange={(v) => {
|
|
1199
|
+
const updated = node.segments.map((s, idx) =>
|
|
1200
|
+
idx === i ? { ...s, dividerThickness: v } : s,
|
|
1201
|
+
)
|
|
1202
|
+
handleUpdate({ segments: updated })
|
|
1203
|
+
}}
|
|
1204
|
+
precision={3}
|
|
1205
|
+
step={0.005}
|
|
1206
|
+
unit="m"
|
|
1207
|
+
value={Math.round(seg.dividerThickness * 1000) / 1000}
|
|
1208
|
+
/>
|
|
1209
|
+
</div>
|
|
1210
|
+
)}
|
|
1211
|
+
|
|
1212
|
+
{seg.type === 'panel' && (
|
|
1213
|
+
<div className="mt-1 border-border/50 border-t pt-1">
|
|
1214
|
+
<SliderControl
|
|
1215
|
+
label="Inset"
|
|
1216
|
+
max={0.1}
|
|
1217
|
+
min={0}
|
|
1218
|
+
onChange={(v) => {
|
|
1219
|
+
const updated = node.segments.map((s, idx) =>
|
|
1220
|
+
idx === i ? { ...s, panelInset: v } : s,
|
|
1221
|
+
)
|
|
1222
|
+
handleUpdate({ segments: updated })
|
|
1223
|
+
}}
|
|
1224
|
+
precision={3}
|
|
1225
|
+
step={0.005}
|
|
1226
|
+
unit="m"
|
|
1227
|
+
value={Math.round(seg.panelInset * 1000) / 1000}
|
|
1228
|
+
/>
|
|
1229
|
+
<SliderControl
|
|
1230
|
+
label="Depth"
|
|
1231
|
+
max={0.1}
|
|
1232
|
+
min={0}
|
|
1233
|
+
onChange={(v) => {
|
|
1234
|
+
const updated = node.segments.map((s, idx) =>
|
|
1235
|
+
idx === i ? { ...s, panelDepth: v } : s,
|
|
1236
|
+
)
|
|
1237
|
+
handleUpdate({ segments: updated })
|
|
1238
|
+
}}
|
|
1239
|
+
precision={3}
|
|
1240
|
+
step={0.005}
|
|
1241
|
+
unit="m"
|
|
1242
|
+
value={Math.round(seg.panelDepth * 1000) / 1000}
|
|
1243
|
+
/>
|
|
1244
|
+
</div>
|
|
1245
|
+
)}
|
|
1236
1246
|
</div>
|
|
1247
|
+
)
|
|
1248
|
+
})}
|
|
1249
|
+
|
|
1250
|
+
<div className="flex gap-1.5 px-1 pt-1">
|
|
1251
|
+
<ActionButton
|
|
1252
|
+
label="+ Add Segment"
|
|
1253
|
+
onClick={() => {
|
|
1254
|
+
const updated = [
|
|
1255
|
+
...node.segments,
|
|
1256
|
+
{
|
|
1257
|
+
type: 'panel' as const,
|
|
1258
|
+
heightRatio: 1,
|
|
1259
|
+
columnRatios: [1],
|
|
1260
|
+
dividerThickness: 0.03,
|
|
1261
|
+
panelDepth: 0.01,
|
|
1262
|
+
panelInset: 0.04,
|
|
1263
|
+
},
|
|
1264
|
+
]
|
|
1265
|
+
handleUpdate({ segments: updated })
|
|
1266
|
+
}}
|
|
1267
|
+
/>
|
|
1268
|
+
{node.segments.length > 1 && (
|
|
1269
|
+
<ActionButton
|
|
1270
|
+
className="text-white/60 hover:text-white"
|
|
1271
|
+
label="- Remove"
|
|
1272
|
+
onClick={() => handleUpdate({ segments: node.segments.slice(0, -1) })}
|
|
1273
|
+
/>
|
|
1237
1274
|
)}
|
|
1238
1275
|
</div>
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
<div className="flex gap-1.5 px-1 pt-1">
|
|
1243
|
-
<ActionButton
|
|
1244
|
-
label="+ Add Segment"
|
|
1245
|
-
onClick={() => {
|
|
1246
|
-
const updated = [
|
|
1247
|
-
...node.segments,
|
|
1248
|
-
{
|
|
1249
|
-
type: 'panel' as const,
|
|
1250
|
-
heightRatio: 1,
|
|
1251
|
-
columnRatios: [1],
|
|
1252
|
-
dividerThickness: 0.03,
|
|
1253
|
-
panelDepth: 0.01,
|
|
1254
|
-
panelInset: 0.04,
|
|
1255
|
-
},
|
|
1256
|
-
]
|
|
1257
|
-
handleUpdate({ segments: updated })
|
|
1258
|
-
}}
|
|
1259
|
-
/>
|
|
1260
|
-
{node.segments.length > 1 && (
|
|
1261
|
-
<ActionButton
|
|
1262
|
-
className="text-white/60 hover:text-white"
|
|
1263
|
-
label="- Remove"
|
|
1264
|
-
onClick={() => handleUpdate({ segments: node.segments.slice(0, -1) })}
|
|
1265
|
-
/>
|
|
1266
|
-
)}
|
|
1267
|
-
</div>
|
|
1268
|
-
</PanelSection>
|
|
1269
|
-
)}
|
|
1270
|
-
|
|
1276
|
+
</PanelSection>
|
|
1277
|
+
)}
|
|
1271
1278
|
</>
|
|
1272
1279
|
)}
|
|
1273
1280
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
import {
|
|
5
4
|
type AnyNode,
|
|
6
5
|
type AnyNodeId,
|
|
@@ -86,10 +85,6 @@ export function FencePanel() {
|
|
|
86
85
|
setSelection({ selectedIds: [] })
|
|
87
86
|
}, [setSelection])
|
|
88
87
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
88
|
if (!(node && node.type === 'fence' && selectedId && selectedCount === 1)) return null
|
|
94
89
|
|
|
95
90
|
const length = getWallCurveLength(node)
|