Adding the lab project
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
import React from 'react'
|
||||
import type { PresetNode } from './types.js'
|
||||
|
||||
interface PresetAssetTreeProps {
|
||||
assignedNodeId?: string
|
||||
nodes: PresetNode[]
|
||||
selectedNodeId?: string
|
||||
onSelect: (node: PresetNode) => void
|
||||
}
|
||||
|
||||
interface TreeItem {
|
||||
node: PresetNode
|
||||
children: TreeItem[]
|
||||
}
|
||||
|
||||
function compareNodes(left: PresetNode, right: PresetNode) {
|
||||
return left.sort_order - right.sort_order || left.name.localeCompare(right.name)
|
||||
}
|
||||
|
||||
function buildTree(nodes: PresetNode[]) {
|
||||
const byParent = new Map<string | null, PresetNode[]>()
|
||||
for (const node of nodes) {
|
||||
const siblings = byParent.get(node.parent_id) ?? []
|
||||
siblings.push(node)
|
||||
byParent.set(node.parent_id, siblings)
|
||||
}
|
||||
|
||||
function build(parentId: string | null): TreeItem[] {
|
||||
return (byParent.get(parentId) ?? [])
|
||||
.sort(compareNodes)
|
||||
.map((node) => ({ node, children: build(node.id) }))
|
||||
}
|
||||
|
||||
return build(null)
|
||||
}
|
||||
|
||||
export function PresetAssetTree({
|
||||
assignedNodeId,
|
||||
nodes,
|
||||
selectedNodeId,
|
||||
onSelect
|
||||
}: PresetAssetTreeProps) {
|
||||
const tree = React.useMemo(() => buildTree(nodes), [nodes])
|
||||
|
||||
if (tree.length === 0) return <p className="empty">This CA library has no presets.</p>
|
||||
|
||||
return (
|
||||
<div className="asset-tree">
|
||||
{tree.map((item) => (
|
||||
<AssetTreeItem
|
||||
assignedNodeId={assignedNodeId}
|
||||
item={item}
|
||||
key={item.node.id}
|
||||
selectedNodeId={selectedNodeId}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function AssetTreeItem({
|
||||
assignedNodeId,
|
||||
item,
|
||||
selectedNodeId,
|
||||
onSelect
|
||||
}: {
|
||||
assignedNodeId?: string
|
||||
item: TreeItem
|
||||
selectedNodeId?: string
|
||||
onSelect: (node: PresetNode) => void
|
||||
}) {
|
||||
const [open, setOpen] = React.useState(true)
|
||||
const selected = item.node.id === selectedNodeId
|
||||
const assigned = item.node.id === assignedNodeId
|
||||
|
||||
return (
|
||||
<div className="asset-tree-item">
|
||||
<div className={`asset-tree-row${selected ? ' selected' : ''}${assigned ? ' assigned' : ''}`}>
|
||||
<button
|
||||
aria-label={item.children.length ? `${open ? 'Collapse' : 'Expand'} ${item.node.name}` : `${item.node.name} has no children`}
|
||||
className="asset-tree-arrow"
|
||||
disabled={!item.children.length}
|
||||
type="button"
|
||||
onClick={() => setOpen((current) => !current)}
|
||||
>
|
||||
{item.children.length ? (open ? '▾' : '▸') : '·'}
|
||||
</button>
|
||||
<button className="asset-tree-select" type="button" onClick={() => onSelect(item.node)}>
|
||||
<strong>{item.node.name}</strong>
|
||||
<small>
|
||||
{item.node.kind}
|
||||
{assigned ? ' · current' : ''}
|
||||
</small>
|
||||
{item.node.description ? <span>{item.node.description}</span> : null}
|
||||
</button>
|
||||
</div>
|
||||
{open && item.children.length ? (
|
||||
<div className="asset-tree-children">
|
||||
{item.children.map((child) => (
|
||||
<AssetTreeItem
|
||||
assignedNodeId={assignedNodeId}
|
||||
item={child}
|
||||
key={child.node.id}
|
||||
selectedNodeId={selectedNodeId}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user