Phase 5 batch: door + window migrate to registry (always-on)
Both kinds share traits — hosted on walls, cuttable, animated open/
close state via a geometry system + animation system. Stage A
migration: register + wrap-export the legacy renderer + bundle both
per-kind systems. Pure geometry + floor-plan ports are later
milestones.
Files added (packages/nodes/src/door/, packages/nodes/src/window/):
- schema.ts: re-export from core.
- parametrics.ts: minimal — dimensions only. Door has 29 sliders +
segmented controls + presets in its legacy panel; window has 15+
sliders. Auto-inspector can't cover them at Stage A — legacy
panel keeps rendering via panel-manager.tsx case fall-through.
Stage E may extend parametrics or use parametrics.customPanel
escape hatch.
- definition.ts: capabilities (no `movable` — wall-bound drag is
bespoke; capability-driven dispatch keeps legacy MoveDoorTool /
MoveWindowTool), parametrics, renderer, system. defaults() uses
`DoorNode.parse({...stub})` to leverage zod's schema-level
`.default()` annotations — door has 40+ fields, window has 20+;
listing them inline duplicates the schema.
- renderer.tsx: wrap-export of legacy DoorRenderer / WindowRenderer
(thin 33-36 lines each).
- system.tsx: bundles each kind's TWO systems — DoorSystem +
DoorAnimationSystem, WindowSystem + WindowAnimationSystem. Both
per-kind systems mount via RegisteredSystems when the kind is
registry-driven; `<LegacySystem kind="door|window">` wrappers
around each individual system short-circuit.
- index.ts: barrel.
Files changed:
- packages/viewer/src/index.ts: new public exports for DoorRenderer,
DoorSystem, DoorAnimationSystem, WindowRenderer, WindowSystem,
WindowAnimationSystem.
- packages/nodes/src/index.ts: appends doorDefinition + windowDefinition.
- packages/editor/src/components/ui/panels/door-panel.tsx + window-
panel.tsx: panel slider-drag fix recipe applied. Drop the
subscribed `updateNode` action, drop the `node` dep from
handleUpdate / previewDoorUpdate / commitDoorPreview useCallbacks.
Use useScene.getState() inside. Door panel has 29 SliderControls,
window 15+ — both at high risk of the Maximum update depth
cascade without the fix.
Phase 5 progress: shelf ✅, spawn ✅, wall ✅, fence ✅, slab ✅, ceiling ✅,
door ✅, window ✅. Eight kinds on the registry. Item / stair / roof /
zone / containers remain.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
2dd50fa5be
commit
9eced06f32
@@ -109,7 +109,6 @@ function isSameDoorValue(current: unknown, next: unknown): boolean {
|
||||
export function DoorPanel() {
|
||||
const selectedId = useViewer((s) => s.selection.selectedIds[0])
|
||||
const setSelection = useViewer((s) => s.setSelection)
|
||||
const updateNode = useScene((s) => s.updateNode)
|
||||
const deleteNode = useScene((s) => s.deleteNode)
|
||||
const setMovingNode = useEditor((s) => s.setMovingNode)
|
||||
const previewRef = useRef<{
|
||||
@@ -124,9 +123,11 @@ export function DoorPanel() {
|
||||
selectedId ? (s.nodes[selectedId as AnyNode['id']] as DoorNode | undefined) : undefined,
|
||||
)
|
||||
|
||||
// Panel slider-drag fix recipe (plans/editor-node-registry.md). Without
|
||||
// it, the 29+ SliderControls in this panel would loop on drag.
|
||||
const handleUpdate = useCallback(
|
||||
(updates: Partial<DoorNode>) => {
|
||||
if (!(selectedId && node)) return
|
||||
if (!selectedId) return
|
||||
const liveNode = useScene.getState().nodes[selectedId as AnyNodeId]
|
||||
if (liveNode?.type !== 'door') return
|
||||
|
||||
@@ -139,12 +140,12 @@ export function DoorPanel() {
|
||||
if ('operationState' in updates || 'swingAngle' in updates || 'doorType' in updates) {
|
||||
useInteractive.getState().removeDoorOpenState(selectedId as AnyNodeId)
|
||||
}
|
||||
updateNode(selectedId as AnyNode['id'], updates)
|
||||
useScene.getState().updateNode(selectedId as AnyNode['id'], updates)
|
||||
const scene = useScene.getState()
|
||||
scene.dirtyNodes.add(selectedId as AnyNodeId)
|
||||
if (liveNode.parentId) scene.dirtyNodes.add(liveNode.parentId as AnyNodeId)
|
||||
},
|
||||
[selectedId, node, updateNode],
|
||||
[selectedId],
|
||||
)
|
||||
|
||||
const previewDoorUpdate = useCallback(
|
||||
@@ -188,10 +189,10 @@ export function DoorPanel() {
|
||||
}
|
||||
previewRef.current = null
|
||||
|
||||
updateNode(selectedId as AnyNode['id'], { [key]: value } as Partial<DoorNode>)
|
||||
useScene.getState().updateNode(selectedId as AnyNode['id'], { [key]: value } as Partial<DoorNode>)
|
||||
scene.dirtyNodes.add(selectedId as AnyNodeId)
|
||||
},
|
||||
[selectedId, updateNode],
|
||||
[selectedId],
|
||||
)
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
|
||||
@@ -93,7 +93,6 @@ const silllessWindowTypes = new Set<WindowNode['windowType']>(['bay', 'bow'])
|
||||
export function WindowPanel() {
|
||||
const selectedId = useViewer((s) => s.selection.selectedIds[0])
|
||||
const setSelection = useViewer((s) => s.setSelection)
|
||||
const updateNode = useScene((s) => s.updateNode)
|
||||
const deleteNode = useScene((s) => s.deleteNode)
|
||||
const setMovingNode = useEditor((s) => s.setMovingNode)
|
||||
const previewRef = useRef<{
|
||||
@@ -108,9 +107,11 @@ export function WindowPanel() {
|
||||
selectedId ? (s.nodes[selectedId as AnyNode['id']] as WindowNode | undefined) : undefined,
|
||||
)
|
||||
|
||||
// Panel slider-drag fix recipe (plans/editor-node-registry.md). Without
|
||||
// it, the 15+ SliderControls in this panel would loop on drag.
|
||||
const handleUpdate = useCallback(
|
||||
(updates: Partial<WindowNode>) => {
|
||||
if (!(selectedId && node)) return
|
||||
if (!selectedId) return
|
||||
const liveNode = useScene.getState().nodes[selectedId as AnyNodeId]
|
||||
if (liveNode?.type !== 'window') return
|
||||
|
||||
@@ -120,12 +121,12 @@ export function WindowPanel() {
|
||||
})
|
||||
if (!hasChange) return
|
||||
|
||||
updateNode(selectedId as AnyNode['id'], updates)
|
||||
useScene.getState().updateNode(selectedId as AnyNode['id'], updates)
|
||||
const scene = useScene.getState()
|
||||
scene.dirtyNodes.add(selectedId as AnyNodeId)
|
||||
if (liveNode.parentId) scene.dirtyNodes.add(liveNode.parentId as AnyNodeId)
|
||||
},
|
||||
[selectedId, node, updateNode],
|
||||
[selectedId],
|
||||
)
|
||||
|
||||
const previewWindowUpdate = useCallback(
|
||||
@@ -169,10 +170,12 @@ export function WindowPanel() {
|
||||
}
|
||||
previewRef.current = null
|
||||
|
||||
updateNode(selectedId as AnyNode['id'], { [key]: value } as Partial<WindowNode>)
|
||||
useScene.getState().updateNode(selectedId as AnyNode['id'], {
|
||||
[key]: value,
|
||||
} as Partial<WindowNode>)
|
||||
scene.dirtyNodes.add(selectedId as AnyNodeId)
|
||||
},
|
||||
[selectedId, updateNode],
|
||||
[selectedId],
|
||||
)
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
|
||||
Reference in New Issue
Block a user