shortcuts and deletion
This commit is contained in:
@@ -9,6 +9,7 @@ import { MathUtils, type Mesh } from 'three'
|
||||
|
||||
import { color, float, fract, fwidth, mix, positionLocal } from 'three/tsl'
|
||||
import { MeshBasicNodeMaterial } from 'three/webgpu'
|
||||
import { useKeyboard } from '@/hooks/use-keyboard'
|
||||
import { ToolManager } from '../tools/tool-manager'
|
||||
import { ActionMenu } from '../ui/action-menu'
|
||||
import { SidebarProvider } from '../ui/primitives/sidebar'
|
||||
@@ -20,6 +21,8 @@ initSpatialGridSync()
|
||||
useScene.getState().loadScene()
|
||||
|
||||
export default function Editor() {
|
||||
useKeyboard()
|
||||
|
||||
return (
|
||||
<div className="w-full h-full">
|
||||
{/* <LevelModeSwitcher /> */}
|
||||
|
||||
@@ -243,6 +243,36 @@ export const ItemTool: React.FC = () => {
|
||||
emitter.on('wall:click', onWallClick)
|
||||
emitter.on('wall:leave', onWallLeave)
|
||||
|
||||
// Keyboard rotation handlers
|
||||
const ROTATION_STEP = Math.PI / 2 // 90 degrees
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (!draftItem.current) return
|
||||
|
||||
let rotationDelta = 0
|
||||
if (event.key === 'r' || event.key === 'R') {
|
||||
rotationDelta = ROTATION_STEP // Counter-clockwise
|
||||
} else if (event.key === 't' || event.key === 'T') {
|
||||
rotationDelta = -ROTATION_STEP // Clockwise
|
||||
}
|
||||
|
||||
if (rotationDelta !== 0) {
|
||||
event.preventDefault()
|
||||
const currentRotation = draftItem.current.rotation
|
||||
const newRotationY = (currentRotation[1] ?? 0) + rotationDelta
|
||||
draftItem.current.rotation = [currentRotation[0], newRotationY, currentRotation[2]]
|
||||
|
||||
useScene.getState().updateNode(draftItem.current.id, {
|
||||
rotation: draftItem.current.rotation,
|
||||
})
|
||||
|
||||
// Update cursor rotation to match
|
||||
cursorRef.current.rotation.y = newRotationY
|
||||
|
||||
checkCanPlace()
|
||||
}
|
||||
}
|
||||
window.addEventListener('keydown', onKeyDown)
|
||||
|
||||
const setupBoundingBox = () => {
|
||||
const boxGeometry = new BoxGeometry(
|
||||
selectedItem.dimensions[0],
|
||||
@@ -265,6 +295,7 @@ export const ItemTool: React.FC = () => {
|
||||
emitter.off('wall:leave', onWallLeave)
|
||||
emitter.off('wall:click', onWallClick)
|
||||
emitter.off('wall:move', onWallMove)
|
||||
window.removeEventListener('keydown', onKeyDown)
|
||||
}
|
||||
}, [selectedItem, canPlaceOnFloor, canPlaceOnWall])
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { useScene } from '@pascal-app/core'
|
||||
import { useViewer } from '@pascal-app/viewer'
|
||||
import { useEffect } from 'react'
|
||||
import useEditor from '@/store/use-editor'
|
||||
|
||||
export const useKeyboard = () => {
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
// Don't handle shortcuts if user is typing in an input
|
||||
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
|
||||
return
|
||||
}
|
||||
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault()
|
||||
// Emit tool:cancel event - each tool handles its own cancellation logic
|
||||
// if (useEditor.getState().controlMode === 'building') {
|
||||
// emitter.emit('tool:cancel', undefined)
|
||||
// }
|
||||
// if (selectedNodeIds.length > 0) {
|
||||
// handleClear()
|
||||
// }
|
||||
} else if (e.key === '1' && !e.metaKey && !e.ctrlKey) {
|
||||
e.preventDefault()
|
||||
useEditor.getState().setPhase('site')
|
||||
useEditor.getState().setMode('select')
|
||||
} else if (e.key === '2' && !e.metaKey && !e.ctrlKey) {
|
||||
e.preventDefault()
|
||||
useEditor.getState().setPhase('structure')
|
||||
useEditor.getState().setMode('select')
|
||||
} else if (e.key === '3' && !e.metaKey && !e.ctrlKey) {
|
||||
e.preventDefault()
|
||||
useEditor.getState().setPhase('furnish')
|
||||
useEditor.getState().setMode('select')
|
||||
} if (e.key === 'v' && !e.metaKey && !e.ctrlKey) {
|
||||
e.preventDefault()
|
||||
useEditor.getState().setMode('select')
|
||||
} else if (e.key === 'z' && (e.metaKey || e.ctrlKey)) {
|
||||
e.preventDefault()
|
||||
useScene.temporal.getState().undo()
|
||||
} else if (e.key === 'Z' && e.shiftKey && (e.metaKey || e.ctrlKey)) {
|
||||
e.preventDefault()
|
||||
useScene.temporal.getState().redo()
|
||||
} else if (e.key === 'Delete' || e.key === 'Backspace') {
|
||||
e.preventDefault()
|
||||
|
||||
const selectedNodeIds = useViewer.getState().selection.selectedIds
|
||||
|
||||
if (selectedNodeIds.length > 0) {
|
||||
useScene.getState().deleteNodes(selectedNodeIds)
|
||||
}
|
||||
}
|
||||
}
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
return () => window.removeEventListener('keydown', handleKeyDown)
|
||||
}, [])
|
||||
|
||||
return null
|
||||
}
|
||||
Reference in New Issue
Block a user