ai-chatbot/apps/editor/store/use-editor.tsx
Aymeric Rabot 6c99851006 Refactor asset types and improve UI primitives
Replaces usage of the Asset type with AssetInput across editor components and hooks for consistency with core types. Adds default dimensions fallback for asset placement logic. Refactors UI primitives (button, sidebar, opacity control) to support forwarding refs and asChild prop, and updates imports for primitives. Updates Radix UI dependencies in package.json and bun.lock.
2026-01-30 22:01:25 -05:00

180 lines
5.0 KiB
TypeScript

'use client'
import { type BuildingNode, type ItemNode, type LevelNode, useScene } from '@pascal-app/core'
import { useViewer } from '@pascal-app/viewer'
import { create } from 'zustand'
import type { AssetInput } from '@pascal-app/core'
export type Phase = 'site' | 'structure' | 'furnish'
export type Mode = 'select' | 'edit' | 'delete' | 'build'
// Structure mode tools (building elements)
export type StructureTool =
| 'wall'
| 'room'
| 'custom-room'
| 'slab'
| 'ceiling'
| 'roof'
| 'column'
| 'stair'
| 'item'
| 'zone'
// Furnish mode tools (items and decoration)
export type FurnishTool = 'item'
// Site mode tools
export type SiteTool = 'property-line'
// Catalog categories for furnish mode items
export type CatalogCategory =
| 'furniture'
| 'appliance'
| 'bathroom'
| 'kitchen'
| 'outdoor'
| 'window'
| 'door'
export type StructureLayer = 'zones' | 'elements'
// Combined tool type
export type Tool = SiteTool | StructureTool | FurnishTool
type EditorState = {
phase: Phase
setPhase: (phase: Phase) => void
mode: Mode
setMode: (mode: Mode) => void
tool: Tool | null
setTool: (tool: Tool | null) => void
structureLayer: StructureLayer
setStructureLayer: (layer: StructureLayer) => void
catalogCategory: CatalogCategory | null
setCatalogCategory: (category: CatalogCategory | null) => void
selectedItem: AssetInput | null
setSelectedItem: (item: AssetInput) => void
movingNode: ItemNode | null
setMovingNode: (node: ItemNode | null) => void
selectedReferenceId: string | null
setSelectedReferenceId: (id: string | null) => void
}
const useEditor = create<EditorState>()((set, get) => ({
phase: 'site',
setPhase: (phase) => {
const currentPhase = get().phase
if (currentPhase === phase) return
set({ phase })
// Clear tool and catalog when switching phases
set({ tool: null, catalogCategory: null })
const viewer = useViewer.getState()
const scene = useScene.getState()
// Helper to find building and level 0
const selectBuildingAndLevel0 = () => {
let buildingId = viewer.selection.buildingId
// If no building selected, find the first one
if (!buildingId) {
const firstBuildingId = scene.rootNodeIds.find((id) => {
const node = scene.nodes[id]
return node?.type === 'building'
})
if (firstBuildingId) {
buildingId = firstBuildingId as BuildingNode['id']
viewer.setSelection({ buildingId })
}
}
// If no level selected, find level 0 in the building
if (buildingId && !viewer.selection.levelId) {
const buildingNode = scene.nodes[buildingId] as BuildingNode
const level0Id = buildingNode.children.find((childId) => {
const levelNode = scene.nodes[childId] as LevelNode
return levelNode?.type === 'level' && levelNode.level === 0
})
if (level0Id) {
viewer.setSelection({ levelId: level0Id as LevelNode['id'] })
} else if (buildingNode.children[0]) {
// Fallback to first level if level 0 doesn't exist
viewer.setSelection({ levelId: buildingNode.children[0] as LevelNode['id'] })
}
}
}
switch (phase) {
case 'site':
// In Site mode, we zoom out and deselect specific levels/buildings
viewer.resetSelection()
break
case 'structure':
selectBuildingAndLevel0()
break
case 'furnish':
selectBuildingAndLevel0()
// Furnish mode only supports elements layer, not zones
set({ structureLayer: 'elements' })
break
}
},
mode: 'select',
setMode: (mode) => {
set({ mode })
const { phase, structureLayer, tool } = get()
// When entering build mode in structure phase with zones layer, activate zone tool
if (mode === 'build' && phase === 'structure' && structureLayer === 'zones') {
set({ tool: 'zone' })
}
// When leaving build mode, clear tool
else if (mode !== 'build' && tool) {
set({ tool: null })
}
},
tool: null,
setTool: (tool) => set({ tool }),
structureLayer: 'elements',
setStructureLayer: (layer) => {
const { mode, tool } = get()
set({ structureLayer: layer })
const viewer = useViewer.getState()
viewer.setSelection({
selectedIds: [],
zoneId: null,
})
// Handle tool changes based on layer
if (layer === 'zones') {
// In zones layer with build mode, activate zone tool
if (mode === 'build') {
set({ tool: 'zone' })
}
} else {
// In elements layer, clear zone tool if it was active
if (tool === 'zone') {
set({ tool: null })
}
}
},
catalogCategory: null,
setCatalogCategory: (category) => set({ catalogCategory: category }),
selectedItem: null,
setSelectedItem: (item) => set({ selectedItem: item }),
movingNode: null,
setMovingNode: (node) => set({ movingNode: node }),
selectedReferenceId: null,
setSelectedReferenceId: (id) => set({ selectedReferenceId: id }),
}))
export default useEditor