fix: guard null selected item during item tool placement (#229)

This commit is contained in:
Pascal
2026-04-14 00:48:59 -04:00
committed by GitHub
parent 3afa1cea8b
commit cf7493985f
2 changed files with 8 additions and 5 deletions
@@ -8,11 +8,11 @@ export const ItemTool: React.FC = () => {
const draftNode = useDraftNode() const draftNode = useDraftNode()
const cursor = usePlacementCoordinator({ const cursor = usePlacementCoordinator({
asset: selectedItem!, asset: selectedItem,
draftNode, draftNode,
initDraft: (gridPosition) => { initDraft: (gridPosition) => {
if (!selectedItem?.attachTo) { if (selectedItem && !selectedItem.attachTo) {
draftNode.create(gridPosition, selectedItem!) draftNode.create(gridPosition, selectedItem)
} }
}, },
onCommitted: () => { onCommitted: () => {
@@ -33,6 +33,7 @@ import { distance, smoothstep, uv, vec2 } from 'three/tsl'
import { LineBasicNodeMaterial, MeshBasicNodeMaterial } from 'three/webgpu' import { LineBasicNodeMaterial, MeshBasicNodeMaterial } from 'three/webgpu'
import { EDITOR_LAYER } from '../../../lib/constants' import { EDITOR_LAYER } from '../../../lib/constants'
import { sfxEmitter } from '../../../lib/sfx-bus' import { sfxEmitter } from '../../../lib/sfx-bus'
import { snapToGrid } from './placement-math'
import { import {
ceilingStrategy, ceilingStrategy,
checkCanPlace, checkCanPlace,
@@ -41,7 +42,6 @@ import {
wallStrategy, wallStrategy,
} from './placement-strategies' } from './placement-strategies'
import type { PlacementState, TransitionResult } from './placement-types' import type { PlacementState, TransitionResult } from './placement-types'
import { snapToGrid } from './placement-math'
import type { DraftNodeHandle } from './use-draft-node' import type { DraftNodeHandle } from './use-draft-node'
const DEFAULT_DIMENSIONS: [number, number, number] = [1, 1, 1] const DEFAULT_DIMENSIONS: [number, number, number] = [1, 1, 1]
@@ -69,7 +69,7 @@ const radialOpacity = smoothstep(0, 0.7, dist).mul(0.6)
basePlaneMaterial.opacityNode = radialOpacity basePlaneMaterial.opacityNode = radialOpacity
export interface PlacementCoordinatorConfig { export interface PlacementCoordinatorConfig {
asset: AssetInput asset: AssetInput | null
draftNode: DraftNodeHandle draftNode: DraftNodeHandle
initDraft: (gridPosition: Vector3) => void initDraft: (gridPosition: Vector3) => void
onCommitted: () => boolean onCommitted: () => boolean
@@ -98,6 +98,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
const { asset, draftNode } = config const { asset, draftNode } = config
useEffect(() => { useEffect(() => {
if (!asset) return
useScene.temporal.getState().pause() useScene.temporal.getState().pause()
const validators = { canPlaceOnFloor, canPlaceOnWall, canPlaceOnCeiling } const validators = { canPlaceOnFloor, canPlaceOnWall, canPlaceOnCeiling }
@@ -873,6 +874,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
// Wall/ceiling items are managed by their own surface entry events (ensureDraft / reparent). // Wall/ceiling items are managed by their own surface entry events (ensureDraft / reparent).
const viewerLevelId = useViewer((s) => s.selection.levelId) const viewerLevelId = useViewer((s) => s.selection.levelId)
useEffect(() => { useEffect(() => {
if (!asset) return
const draft = draftNode.current const draft = draftNode.current
if (!(draft && viewerLevelId) || asset.attachTo) return if (!(draft && viewerLevelId) || asset.attachTo) return
if (draft.parentId === viewerLevelId) return if (draft.parentId === viewerLevelId) return
@@ -881,6 +883,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
}, [viewerLevelId, draftNode, asset]) }, [viewerLevelId, draftNode, asset])
useFrame((_, delta) => { useFrame((_, delta) => {
if (!asset) return
if (!draftNode.current) return if (!draftNode.current) return
const mesh = sceneRegistry.nodes.get(draftNode.current.id) const mesh = sceneRegistry.nodes.get(draftNode.current.id)
if (!mesh) return if (!mesh) return