'use client' import { useScene } from '@pascal-app/core' import { SegmentedControl, SliderControl, ToggleControl, useEditor } from '@pascal-app/editor' import { FLOWER_PRESET_LIST } from './flower-presets' import type { FlowerPreset } from './flower-schema' import { GRASS_PRESET_LIST } from './grass-presets' import type { GrassPreset } from './grass-schema' import { TREE_PRESET_LIST } from './presets' import type { TreePreset } from './schema' import { type TreesPanelMode as Mode, useTreesStore } from './store' const KIND: Record = { trees: 'trees:tree', flowers: 'trees:flower', grass: 'trees:grass', } const NOUN: Record = { trees: 'tree', flowers: 'flower', grass: 'grass' } /** * The plugin's left-rail panel. A Trees / Flowers / Grass segmented control * switches the brush; picking a preset arms placement for that kind * (`setTool('trees:*')` + build mode). The count chip reads the scene reactively, * closing the triangle: panel → store → tool → scene → panel. It composes the * host's exported controls (`SegmentedControl`/`SliderControl`/`ToggleControl`) * so the brush matches the right-hand inspector pixel-for-pixel. */ export default function TreesPanel() { // Section lives in the plugin store (not local state) so "find in catalog" // can point the panel at the found node's section — see find-sync.ts. const mode = useTreesStore((s) => s.mode) const setMode = useTreesStore((s) => s.setMode) const activeTool = useEditor((s) => s.tool) const count = useScene( (s) => Object.values(s.nodes).filter((n) => (n.type as string) === KIND[mode]).length, ) const arming = activeTool === KIND[mode] return (

自然资产

{count} planted

{arming ? 'Click the ground to plant. Press Esc to stop.' : `Pick ${mode === 'grass' ? 'a grass' : `a ${NOUN[mode]}`}, then click the ground.`}

{mode === 'trees' && } {mode === 'flowers' && } {mode === 'grass' && }
) } function TreesSection({ arming }: { arming: boolean }) { const selected = useTreesStore((s) => s.preset) const size = useTreesStore((s) => s.size) const height = useTreesStore((s) => s.height) const foliageDensity = useTreesStore((s) => s.foliageDensity) const trunkThickness = useTreesStore((s) => s.trunkThickness) const leafless = useTreesStore((s) => s.leafless) const activate = (preset: TreePreset) => { useTreesStore.getState().setPreset(preset) useEditor.getState().setTool('trees:tree') useEditor.getState().setMode('build') } return ( <> {selected !== 'trellis' && (
)}
{!leafless && ( )}
) } function FlowersSection({ arming }: { arming: boolean }) { const selected = useTreesStore((s) => s.flowerPreset) const height = useTreesStore((s) => s.flowerHeight) const activate = (preset: FlowerPreset) => { useTreesStore.getState().setFlowerPreset(preset) useEditor.getState().setTool('trees:flower') useEditor.getState().setMode('build') } return ( <> ) } function GrassSection({ arming }: { arming: boolean }) { const selected = useTreesStore((s) => s.grassPreset) const height = useTreesStore((s) => s.grassHeight) const activate = (preset: GrassPreset) => { useTreesStore.getState().setGrassPreset(preset) useEditor.getState().setTool('trees:grass') useEditor.getState().setMode('build') } return ( <> ) } function PresetGrid({ items, selected, onPick, }: { items: ReadonlyArray<{ id: T; label: string; thumbnail: string }> selected: T | null onPick: (id: T) => void }) { return (
{items.map((item) => { const isSelected = selected === item.id return ( ) })}
) }