'use client' import type { AnyNodeId } from '@pascal-app/core' import { LevelNode, useScene } from '@pascal-app/core' import { useViewer } from '@pascal-app/viewer' import { AppWindow, ArrowRight, Box, Building2, Camera, Copy, DoorOpen, Eye, EyeOff, FileJson, Grid3X3, Hexagon, Layers, Map, Maximize2, Minimize2, Moon, MousePointer2, Package, PaintBucket, PencilLine, Plus, Redo2, Square, SquareStack, Sun, Trash2, Undo2, Video, } from 'lucide-react' import { useEffect } from 'react' import { deleteLevelWithFallbackSelection } from '../../../lib/level-selection' import { runRedo, runUndo } from '../../../lib/history' import { useCommandRegistry } from '../../../store/use-command-registry' import type { StructureTool } from '../../../store/use-editor' import useEditor from '../../../store/use-editor' import { useCommandPalette } from './index' export function EditorCommands() { const register = useCommandRegistry((s) => s.register) const { navigateTo, setInputValue, setOpen } = useCommandPalette() const setPhase = useEditor((s) => s.setPhase) const setMode = useEditor((s) => s.setMode) const setTool = useEditor((s) => s.setTool) const setStructureLayer = useEditor((s) => s.setStructureLayer) const primeMaterialPaintFromSelection = useEditor((s) => s.primeMaterialPaintFromSelection) const isPreviewMode = useEditor((s) => s.isPreviewMode) const setPreviewMode = useEditor((s) => s.setPreviewMode) const exportScene = useViewer((s) => s.exportScene) // Re-register when exportScene availability changes (it's a conditional action) useEffect(() => { const run = (fn: () => void) => { fn() setOpen(false) } const activateTool = (tool: StructureTool) => { run(() => { setPhase('structure') setMode('build') if (tool === 'zone') setStructureLayer('zones') setTool(tool) }) } return register([ // ── Scene ──────────────────────────────────────────────────────────── { id: 'editor.tool.wall', label: 'Wall Tool', group: 'Scene', icon: , keywords: ['draw', 'build', 'structure'], execute: () => activateTool('wall'), }, { id: 'editor.tool.slab', label: 'Slab Tool', group: 'Scene', icon: , keywords: ['floor', 'build'], execute: () => activateTool('slab'), }, { id: 'editor.tool.ceiling', label: 'Ceiling Tool', group: 'Scene', icon: , keywords: ['top', 'build'], execute: () => activateTool('ceiling'), }, { id: 'editor.tool.door', label: 'Door Tool', group: 'Scene', icon: , keywords: ['opening', 'entrance'], execute: () => activateTool('door'), }, { id: 'editor.tool.window', label: 'Window Tool', group: 'Scene', icon: , keywords: ['opening', 'glass'], execute: () => activateTool('window'), }, { id: 'editor.tool.item', label: 'Item Tool', group: 'Scene', icon: , keywords: ['furniture', 'object', 'asset', 'furnish'], execute: () => activateTool('item'), }, { id: 'editor.tool.stair', label: 'Stair Tool', group: 'Scene', icon: , keywords: ['stairs', 'staircase', 'flight', 'landing', 'steps'], execute: () => activateTool('stair'), }, { id: 'editor.tool.zone', label: 'Zone Tool', group: 'Scene', icon: , keywords: ['area', 'room', 'space'], execute: () => activateTool('zone'), }, { id: 'editor.delete-selection', label: 'Delete Selection', group: 'Scene', icon: , keywords: ['remove', 'erase'], shortcut: ['⌫'], when: () => useViewer.getState().selection.selectedIds.length > 0, execute: () => run(() => { const { selectedIds } = useViewer.getState().selection useScene.getState().deleteNodes(selectedIds as any[]) }), }, { id: 'editor.mode.material-paint', label: 'Material Paint', group: 'Scene', icon: , keywords: ['paint', 'material', 'texture', 'bucket', 'surface'], shortcut: ['P'], execute: () => run(() => { primeMaterialPaintFromSelection() setPhase('structure') setStructureLayer('elements') setMode('material-paint') }), }, // ── Levels ─────────────────────────────────────────────────────────── { id: 'editor.level.goto', label: 'Go to Level', group: 'Levels', icon: , keywords: ['level', 'floor', 'go', 'navigate', 'switch', 'select'], navigate: true, when: () => Object.values(useScene.getState().nodes).some((n) => n.type === 'level'), execute: () => navigateTo('goto-level'), }, { id: 'editor.level.add', label: 'Add Level', group: 'Levels', icon: , keywords: ['level', 'floor', 'add', 'create', 'new'], execute: () => run(() => { const { nodes } = useScene.getState() const building = Object.values(nodes).find((n) => n.type === 'building') if (!building) return const newLevel = LevelNode.parse({ level: building.children.length, children: [], parentId: building.id, }) useScene.getState().createNode(newLevel, building.id) useViewer.getState().setSelection({ levelId: newLevel.id }) }), }, { id: 'editor.level.rename', label: 'Rename Level', group: 'Levels', icon: , keywords: ['level', 'floor', 'rename', 'name'], navigate: true, when: () => !!useViewer.getState().selection.levelId, execute: () => { const activeLevelId = useViewer.getState().selection.levelId if (!activeLevelId) return const level = useScene.getState().nodes[activeLevelId as AnyNodeId] as LevelNode setInputValue(level?.name ?? '') navigateTo('rename-level') }, }, { id: 'editor.level.delete', label: 'Delete Level', group: 'Levels', icon: , keywords: ['level', 'floor', 'delete', 'remove'], when: () => { const levelId = useViewer.getState().selection.levelId if (!levelId) return false const node = useScene.getState().nodes[levelId as AnyNodeId] as LevelNode return node?.type === 'level' && node.level !== 0 }, execute: () => run(() => { const activeLevelId = useViewer.getState().selection.levelId if (!activeLevelId) return deleteLevelWithFallbackSelection(activeLevelId as AnyNodeId) }), }, // ── Viewer Controls ────────────────────────────────────────────────── { id: 'editor.viewer.wall-mode', label: 'Wall Mode', group: 'Viewer Controls', icon: , keywords: ['wall', 'cutaway', 'up', 'down', 'view'], badge: () => { const mode = useViewer.getState().wallMode return { cutaway: 'Cutaway', up: 'Up', down: 'Down' }[mode] }, navigate: true, execute: () => navigateTo('wall-mode'), }, { id: 'editor.viewer.level-mode', label: 'Level Mode', group: 'Viewer Controls', icon: , keywords: ['level', 'floor', 'exploded', 'stacked', 'solo'], badge: () => { const mode = useViewer.getState().levelMode return { manual: 'Manual', stacked: 'Stacked', exploded: 'Exploded', solo: 'Solo' }[mode] }, navigate: true, execute: () => navigateTo('level-mode'), }, { id: 'editor.viewer.camera-mode', label: () => { const mode = useViewer.getState().cameraMode return `Camera: Switch to ${mode === 'perspective' ? 'Orthographic' : 'Perspective'}` }, group: 'Viewer Controls', icon: