Every placement and move path now publishes its ghost pose to a single
`useFacingPose` store, drawn by one editor-side `<FacingPoseIndicator>` overlay,
instead of each path drawing its own triangle (which left the nodes-package and
PlacementBox paths invisible):
- column/shelf presets + all moves (PlacementBox via move-registry, and
DragBoundingBox) now publish the pose, so the triangle finally shows
- stair create + move use a declarative `facingIndicator: { reversed: true }`
(new registry resolver) so the triangle sits before the entry pointing out —
resolved in one place, so create and move match automatically
- stair placement defaults to single and respects the shared `point`
continuation (C) toggle, like the other placement tools
Checkpoint on the placement-interaction epic: also carries the in-flight
continuation-profile extraction (lib/continuation), grid surface (item #8), and
HUD work. Door/window still render their own legacy inline triangle and are
migrated to the overlay next.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
52 lines
1.9 KiB
TypeScript
52 lines
1.9 KiB
TypeScript
'use client'
|
|
|
|
import type { AssetInput } from '@pascal-app/core'
|
|
import { triggerSFX, useDraftNode, useEditor, usePlacementCoordinator } from '@pascal-app/editor'
|
|
|
|
/**
|
|
* Registry-driven item placement tool. Mounted by `ToolManager` when
|
|
* `useEditor.tool === 'item'` (the catalog picker is what selects which
|
|
* asset; this tool handles the cursor follow + click-to-commit flow).
|
|
*
|
|
* Wraps the same `usePlacementCoordinator` + `useDraftNode` primitives
|
|
* the move-tool uses. The placement coordinator runs surface strategies
|
|
* (floor / wall / ceiling / item-surface) so the same cursor logic
|
|
* handles wall-mounted artwork, floor furniture, ceiling fans, and
|
|
* nested items on tables.
|
|
*
|
|
* Replaces the legacy `editor/src/components/tools/item/item-tool.tsx`.
|
|
* The `tools` map in `tool-manager.tsx` no longer needs an `item:` entry
|
|
* — `getRegistryTool('item')` finds this through `def.tool`.
|
|
*/
|
|
function ItemPlacementContent({ selectedItem }: { selectedItem: AssetInput }) {
|
|
const draftNode = useDraftNode()
|
|
|
|
const cursor = usePlacementCoordinator({
|
|
asset: selectedItem,
|
|
draftNode,
|
|
initDraft: (gridPosition) => {
|
|
// Only floor items get a draft on mount; wall / ceiling items are
|
|
// created lazily by the placement coordinator when the cursor
|
|
// enters a surface (so the draft doesn't appear at world origin
|
|
// before the first move event).
|
|
if (selectedItem && !selectedItem.attachTo) {
|
|
draftNode.create(gridPosition, selectedItem)
|
|
}
|
|
},
|
|
onCommitted: () => {
|
|
triggerSFX('sfx:item-place')
|
|
return useEditor.getState().getContinuation('point') === 'repeat'
|
|
},
|
|
})
|
|
|
|
return <>{cursor}</>
|
|
}
|
|
|
|
function ItemTool() {
|
|
const selectedItem = useEditor((state) => state.selectedItem)
|
|
if (!selectedItem) return null
|
|
return <ItemPlacementContent selectedItem={selectedItem} />
|
|
}
|
|
|
|
export default ItemTool
|