Files
editor/packages/nodes/src/slab/hole-editor.tsx
T
Wassim SAMADandClaude Opus 4.7 b2e5d84986 Phase 5 Stage D slab: port placement + move + boundary/hole editors
Replicates the fence Stage D recipe for slab — three drag affordances
+ one placement tool, all routed through the registry:

  affordanceTools:
    'boundary-edit' → thin <PolygonEditor> wrapper (vertex/edge drag)
    'hole-edit'     → same for a single hole polygon
    move            → DragAction with single-undo dance
  tool: () => placement (multi-click polygon with axis/45° snap)

`PolygonEditor` + `PolygonEditorProps` exported from
`@pascal-app/editor` as Stage D transitional surface (Stage F cleanup
moves them into `@pascal-app/nodes`).

ToolManager mount sites for SlabBoundaryEditor / SlabHoleEditor now
route through `getRegistryAffordanceTool` with legacy fallback.

Slab move action does not use the live-drag exception (polygon CSG
rebuild every tick) — matches legacy behavior; optimization is a
separate task once we measure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 18:27:31 -04:00

54 lines
1.6 KiB
TypeScript

'use client'
import { resolveLevelId, type SlabNode, useScene } from '@pascal-app/core'
import { PolygonEditor } from '@pascal-app/editor'
import { useViewer } from '@pascal-app/viewer'
import { useCallback } from 'react'
/**
* Phase 5 Stage D — slab hole editor (registry-driven).
*
* Edits a specific hole polygon inside a slab. Mounted by ToolManager
* via `def.affordanceTools['hole-edit']` when `useEditor.editingHole`
* is set on the selected slab.
*/
export const SlabHoleEditor: React.FC<{ slabId: SlabNode['id']; holeIndex: number }> = ({
slabId,
holeIndex,
}) => {
const slabNode = useScene((s) => s.nodes[slabId])
const updateNode = useScene((s) => s.updateNode)
const setSelection = useViewer((s) => s.setSelection)
const slab = slabNode?.type === 'slab' ? (slabNode as SlabNode) : null
const holes = slab?.holes || []
const hole = holes[holeIndex]
const handlePolygonChange = useCallback(
(newPolygon: Array<[number, number]>) => {
const updatedHoles = [...holes]
updatedHoles[holeIndex] = newPolygon
updateNode(slabId, { holes: updatedHoles })
setSelection({ selectedIds: [slabId] })
},
[slabId, holeIndex, holes, updateNode, setSelection],
)
if (!(slab && hole) || hole.length < 3) return null
return (
<PolygonEditor
allowEdgeMove
allowPolygonMove
color="#ef4444"
levelId={resolveLevelId(slab, useScene.getState().nodes)}
minVertices={3}
onPolygonChange={handlePolygonChange}
polygon={hole}
surfaceHeight={slab.elevation ?? 0.05}
/>
)
}
export default SlabHoleEditor