diff --git a/packages/editor/src/components/ui/panels/door-panel.tsx b/packages/editor/src/components/ui/panels/door-panel.tsx index 01d41bd2..b447d61f 100755 --- a/packages/editor/src/components/ui/panels/door-panel.tsx +++ b/packages/editor/src/components/ui/panels/door-panel.tsx @@ -127,8 +127,11 @@ export function DoorPanel() { const handleUpdate = useCallback( (updates: Partial) => { if (!(selectedId && node)) return + const liveNode = useScene.getState().nodes[selectedId as AnyNodeId] + if (liveNode?.type !== 'door') return + const hasChange = Object.entries(updates).some(([key, value]) => { - const currentValue = node[key as keyof DoorNode] + const currentValue = liveNode[key as keyof DoorNode] return !isSameDoorValue(currentValue, value) }) if (!hasChange) return @@ -137,7 +140,9 @@ export function DoorPanel() { useInteractive.getState().removeDoorOpenState(selectedId as AnyNodeId) } updateNode(selectedId as AnyNode['id'], updates) - useScene.getState().dirtyNodes.add(selectedId as AnyNodeId) + const scene = useScene.getState() + scene.dirtyNodes.add(selectedId as AnyNodeId) + if (liveNode.parentId) scene.dirtyNodes.add(liveNode.parentId as AnyNodeId) }, [selectedId, node, updateNode], ) @@ -348,7 +353,9 @@ export function DoorPanel() { const isRollupGarageDoor = doorType === 'garage-rollup' const isTiltupGarageDoor = doorType === 'garage-tiltup' const typeMode = isOpening ? 'opening' : isGarageDoor ? 'garage' : 'door' - const supportsHandleSide = isSwingDoor + const supportsHingeSide = doorType === 'hinged' + const supportsHandleSide = doorType === 'hinged' + const supportsTopShape = !isGarageDoor const maxDoorWidth = isGarageDoor ? 6 : 3 const setOpeningTopRadius = (index: number, value: number, commit = false) => { @@ -395,6 +402,7 @@ export function DoorPanel() { handleSide: 'right', trackStyle: 'visible', operationState: Math.max(node.operationState ?? 0, 0.65), + threshold: false, contentPadding: [0.03, 0.04], segments: foldingDoorSegments, } @@ -411,6 +419,7 @@ export function DoorPanel() { trackStyle: 'pocket', slideDirection: node.slideDirection ?? 'left', operationState: node.operationState ?? 0, + threshold: false, contentPadding: [0.035, 0.045], segments: foldingDoorSegments, } @@ -427,6 +436,7 @@ export function DoorPanel() { trackStyle: 'visible', slideDirection: node.slideDirection ?? 'left', operationState: node.operationState ?? 0, + threshold: false, contentPadding: [0.035, 0.045], segments: foldingDoorSegments, } @@ -443,6 +453,7 @@ export function DoorPanel() { trackStyle: 'visible', slideDirection: node.slideDirection ?? 'left', operationState: node.operationState ?? 0, + threshold: false, contentPadding: [0.03, 0.04], segments: frenchDoorSegments, } @@ -456,6 +467,7 @@ export function DoorPanel() { ...dimensionUpdates, handle: false, threshold: false, + openingShape: 'rectangle', trackStyle: 'overhead', operationState: 0, garagePanelCount: Math.max(3, Math.min(8, node.garagePanelCount ?? 4)), @@ -472,6 +484,7 @@ export function DoorPanel() { ...dimensionUpdates, handle: false, threshold: false, + openingShape: 'rectangle', trackStyle: 'overhead', operationState: 0, garagePanelCount: 4, @@ -488,6 +501,7 @@ export function DoorPanel() { ...dimensionUpdates, handle: false, threshold: false, + openingShape: 'rectangle', trackStyle: 'overhead', operationState: 0, garagePanelCount: 4, @@ -738,7 +752,7 @@ export function DoorPanel() { /> - {!isOpening && ( + {!isOpening && supportsTopShape && (
-
- - Hinges Side - - handleUpdate({ hingesSide: v })} - options={[ - { label: 'Left', value: 'left' }, - { label: 'Right', value: 'right' }, - ]} - value={node.hingesSide} - /> -
+ {supportsHingeSide && ( +
+ + Hinges Side + + handleUpdate({ hingesSide: v })} + options={[ + { label: 'Left', value: 'left' }, + { label: 'Right', value: 'right' }, + ]} + value={node.hingesSide} + /> +
+ )}
Direction diff --git a/packages/editor/src/components/ui/panels/window-panel.tsx b/packages/editor/src/components/ui/panels/window-panel.tsx index 32ee9d4d..c61bdecf 100755 --- a/packages/editor/src/components/ui/panels/window-panel.tsx +++ b/packages/editor/src/components/ui/panels/window-panel.tsx @@ -16,7 +16,6 @@ import { cn } from '../../../lib/utils' import { sfxEmitter } from '../../../lib/sfx-bus' import useEditor from '../../../store/use-editor' import { ActionButton, ActionGroup } from '../controls/action-button' -import { MetricControl } from '../controls/metric-control' import { PanelSection } from '../controls/panel-section' import { SegmentedControl } from '../controls/segmented-control' import { SliderControl } from '../controls/slider-control' @@ -81,14 +80,16 @@ const windowTypeOptions: Array<{ label: string; value: WindowNode['windowType'] { label: 'Louvered', value: 'louvered' }, ] -const rectangleOnlyWindowTypes = new Set([ - 'sliding', - 'single-hung', - 'double-hung', - 'bay', - 'bow', +const shapedWindowTypes = new Set([ + 'fixed', + 'casement', + 'awning', + 'hopper', + 'louvered', ]) +const silllessWindowTypes = new Set(['bay', 'bow']) + export function WindowPanel() { const selectedId = useViewer((s) => s.selection.selectedIds[0]) const setSelection = useViewer((s) => s.setSelection) @@ -110,14 +111,19 @@ export function WindowPanel() { const handleUpdate = useCallback( (updates: Partial) => { if (!(selectedId && node)) return + const liveNode = useScene.getState().nodes[selectedId as AnyNodeId] + if (liveNode?.type !== 'window') return + const hasChange = Object.entries(updates).some(([key, value]) => { - const currentValue = node[key as keyof WindowNode] + const currentValue = liveNode[key as keyof WindowNode] return !isSameWindowValue(currentValue, value) }) if (!hasChange) return updateNode(selectedId as AnyNode['id'], updates) - useScene.getState().dirtyNodes.add(selectedId as AnyNodeId) + const scene = useScene.getState() + scene.dirtyNodes.add(selectedId as AnyNodeId) + if (liveNode.parentId) scene.dirtyNodes.add(liveNode.parentId as AnyNodeId) }, [selectedId, node, updateNode], ) @@ -316,6 +322,9 @@ export function WindowPanel() { node.windowType === 'single-hung' || node.windowType === 'double-hung' || node.windowType === 'louvered' + const supportsWindowShape = shapedWindowTypes.has(node.windowType ?? 'fixed') + const supportsGrid = node.windowType === 'fixed' + const supportsSill = !silllessWindowTypes.has(node.windowType) const setOperationState = (value: number) => { useInteractive.getState().cancelWindowAnimation(node.id) @@ -464,10 +473,10 @@ export function WindowPanel() { handleUpdate({ windowType: option.value, ...(option.value === 'awning' ? { awningDirection } : {}), - ...(rectangleOnlyWindowTypes.has(option.value) + ...(!shapedWindowTypes.has(option.value) ? { openingShape: 'rectangle' } : {}), - ...(option.value === 'bay' || option.value === 'bow' ? { sill: false } : {}), + ...(silllessWindowTypes.has(option.value) ? { sill: false } : {}), }) } type="button" @@ -597,7 +606,7 @@ export function WindowPanel() { /> - {!isOpening && !rectangleOnlyWindowTypes.has(node.windowType) && ( + {!isOpening && supportsWindowShape && ( @@ -814,128 +823,132 @@ export function WindowPanel() { /> - - { - const n = Math.max(1, Math.min(8, Math.round(v))) - handleUpdate({ columnRatios: Array(n).fill(1 / n) }) - }} - precision={0} - step={1} - value={numCols} - /> - { - const n = Math.max(1, Math.min(8, Math.round(v))) - handleUpdate({ rowRatios: Array(n).fill(1 / n) }) - }} - precision={0} - step={1} - value={numRows} - /> + {supportsGrid && ( + + { + const n = Math.max(1, Math.min(8, Math.round(v))) + handleUpdate({ columnRatios: Array(n).fill(1 / n) }) + }} + precision={0} + step={1} + value={numCols} + /> + { + const n = Math.max(1, Math.min(8, Math.round(v))) + handleUpdate({ rowRatios: Array(n).fill(1 / n) }) + }} + precision={0} + step={1} + value={numRows} + /> - {numCols > 1 && ( -
-
- Col Widths + {numCols > 1 && ( +
+
+ Col Widths +
+ {normCols.map((ratio, i) => ( + setColumnRatio(i, v / 100)} + precision={1} + step={1} + unit="%" + value={Math.round(ratio * 100 * 10) / 10} + /> + ))} +
+ handleUpdate({ columnDividerThickness: v })} + precision={3} + step={0.01} + unit="m" + value={Math.round((node.columnDividerThickness ?? 0.03) * 1000) / 1000} + /> +
- {normCols.map((ratio, i) => ( + )} + + {numRows > 1 && ( +
+
+ Row Heights +
+ {normRows.map((ratio, i) => ( + setRowRatio(i, v / 100)} + precision={1} + step={1} + unit="%" + value={Math.round(ratio * 100 * 10) / 10} + /> + ))} +
+ handleUpdate({ rowDividerThickness: v })} + precision={3} + step={0.01} + unit="m" + value={Math.round((node.rowDividerThickness ?? 0.03) * 1000) / 1000} + /> +
+
+ )} + + )} + + {supportsSill && ( + + handleUpdate({ sill: checked })} + /> + {node.sill && ( +
setColumnRatio(i, v / 100)} - precision={1} - step={1} - unit="%" - value={Math.round(ratio * 100 * 10) / 10} - /> - ))} -
- handleUpdate({ columnDividerThickness: v })} + label="Depth" + min={0} + onChange={(v) => handleUpdate({ sillDepth: v })} precision={3} step={0.01} unit="m" - value={Math.round((node.columnDividerThickness ?? 0.03) * 1000) / 1000} + value={Math.round(node.sillDepth * 1000) / 1000} /> -
-
- )} - - {numRows > 1 && ( -
-
- Row Heights -
- {normRows.map((ratio, i) => ( setRowRatio(i, v / 100)} - precision={1} - step={1} - unit="%" - value={Math.round(ratio * 100 * 10) / 10} - /> - ))} -
- handleUpdate({ rowDividerThickness: v })} + label="Thickness" + min={0} + onChange={(v) => handleUpdate({ sillThickness: v })} precision={3} step={0.01} unit="m" - value={Math.round((node.rowDividerThickness ?? 0.03) * 1000) / 1000} + value={Math.round(node.sillThickness * 1000) / 1000} />
-
- )} -
- - - handleUpdate({ sill: checked })} - /> - {node.sill && ( -
- handleUpdate({ sillDepth: v })} - precision={3} - step={0.01} - unit="m" - value={Math.round(node.sillDepth * 1000) / 1000} - /> - handleUpdate({ sillThickness: v })} - precision={3} - step={0.01} - unit="m" - value={Math.round(node.sillThickness * 1000) / 1000} - /> -
- )} -
+ )} + + )} )}