'use client' import { type AnyNode, type AnyNodeId, nodeRegistry, type ParamField, useScene, } from '@pascal-app/core' import { useViewer } from '@pascal-app/viewer' import { Move, Trash2 } from 'lucide-react' import { useCallback } from 'react' import { sfxEmitter } from '../../../lib/sfx-bus' import useEditor from '../../../store/use-editor' import { ActionButton, ActionGroup } from '../controls/action-button' import { PanelSection } from '../controls/panel-section' import { SliderControl } from '../controls/slider-control' import { PanelWrapper } from './panel-wrapper' /** * Auto-derived right-panel inspector for any registry-backed node. * * Reads `definition.parametrics` from the registry and renders one * `` per group, one control per field. Field kinds supported: * - `number` → SliderControl with min/max/step/unit from the descriptor * - `enum` → dark-themed ` onUpdate({ [key]: e.target.value } as Partial)} value={str} > {field.options.map((opt) => ( ))} ) } case 'color': { const str = typeof value === 'string' ? value : '#888888' return (
{prettifyKey(key)}
onUpdate({ [key]: e.target.value } as Partial)} type="color" value={str} /> onUpdate({ [key]: e.target.value } as Partial)} type="text" value={str} />
) } case 'vec3': { const v = Array.isArray(value) && value.length >= 3 ? (value as [number, number, number]) : [0, 0, 0] const axes: Array<{ label: string; index: 0 | 1 | 2 }> = [ { label: 'X', index: 0 }, { label: 'Y', index: 1 }, { label: 'Z', index: 2 }, ] return ( <> {axes.map(({ label, index }) => { // v is a [number, number, number] tuple; the explicit local // resolves TS's noUncheckedIndexedAccess concern that v[index] // could be undefined. const axisValue = v[index] ?? 0 return ( { const updated = [...v] as [number, number, number] updated[index] = next onUpdate({ [key]: updated } as Partial) }} precision={2} step={0.05} unit="m" value={Math.round(axisValue * 100) / 100} /> ) })} ) } default: // material / ref / unrecognized kinds — not implemented in v1. return null } } // ─── helpers ───────────────────────────────────────────────────────── function precisionForStep(step: number): number { if (step <= 0) return 0 return Math.max(0, Math.ceil(-Math.log10(step))) } function prettifyKey(key: string): string { // 'bracketStyle' → 'Bracket style' const spaced = key.replace(/([A-Z])/g, ' $1').toLowerCase() return spaced.charAt(0).toUpperCase() + spaced.slice(1) } function prettifyEnumValue(value: string): string { // 'minimal' → 'Minimal'; 'roof-segment' → 'Roof segment' return value .split(/[-_\s]/) .map((word, i) => i === 0 ? word.charAt(0).toUpperCase() + word.slice(1) : word.toLowerCase(), ) .join(' ') }