You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
v/src/vcen-edit/comps/EditorTreeNodes.svelte

117 lines
4.6 KiB

<script lang="ts">
import type { Metadata, MetadataField } from '@/mtas/lib'
import type { GlobState } from '@/glob/adapters/svelte'
import { validateField } from '@/mtas/lib'
import TypeBadge from '@/mtas/svelte/components/MetaTypeBadge.svelte'
import EditorTreeNodesRecursive from './EditorTreeNodes.svelte'
let {
meta,
order,
path,
depth,
selectedPath,
glob,
onSelect,
onMove,
}: {
meta: Metadata
order: string[]
path: string[]
depth: number
selectedPath: string[]
glob: GlobState
onSelect: (path: string[]) => void
onMove: (path: string[], dir: -1 | 1) => void
} = $props()
let openGroups = $state<Set<string>>(new Set(order))
function nodePath(key: string): string[] { return [...path, key] }
function isSelected(key: string): boolean { return selectedPath.join('.') === nodePath(key).join('.') }
function isAncestor(key: string): boolean { return selectedPath.join('.').startsWith(nodePath(key).join('.') + '.') }
function toggleGroup(key: string): void {
const next = new Set(openGroups)
next.has(key) ? next.delete(key) : next.add(key)
openGroups = next
}
function asField(v: unknown): MetadataField { return v as MetadataField }
function groupValue(f: MetadataField): Metadata { return f.type === 'group' ? f.value : {} }
</script>
{#each order as key, i (key)}
{@const field = asField(meta[key])}
{@const np = nodePath(key)}
{@const vResult = validateField(key, field)}
{@const sel = isSelected(key)}
{@const anc = isAncestor(key)}
{@const isGroup = field.type === 'group'}
{@const groupOpen = openGroups.has(key)}
{@const childKeys = isGroup ? Object.keys(field.value) : []}
<div
class="group/node flex items-center gap-1 px-2 py-1.5 cursor-pointer select-none transition-colors border-l-2
{sel ? 'bg-indigo-950/50 border-indigo-500' : anc ? 'bg-gray-800/50 border-transparent' : 'hover:bg-gray-800/40 border-transparent'}"
style="padding-left: {depth * 12 + 8}px"
role="treeitem"
tabindex="0"
aria-selected={sel}
onclick={() => onSelect(np)}
onkeydown={e => { if (e.key === 'Enter') onSelect(np) }}
>
{#if isGroup}
<button type="button"
onclick={e => { e.stopPropagation(); toggleGroup(key) }}
class="flex items-center justify-center w-4 h-4 rounded text-gray-600 hover:text-gray-400 hover:bg-gray-700 transition-colors shrink-0 p-0">
<svg class="h-3 w-3 transition-transform {groupOpen ? 'rotate-90' : ''}"
viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2">
<path d="M4 2l4 4-4 4"/>
</svg>
</button>
{:else}
<span class="w-4 shrink-0"></span>
{/if}
<div class="h-1.5 w-1.5 shrink-0 rounded-full {vResult.valid ? 'bg-green-500/60' : 'bg-red-500'}"></div>
<TypeBadge type={field.type} showLabel={false} {glob} />
<span class="text-xs font-mono truncate flex-1 {sel ? 'text-indigo-400 font-semibold' : 'text-gray-300'}">
{key}
</span>
<div class="items-center gap-0.5 shrink-0 {sel ? 'flex' : 'hidden group-hover/node:flex'}">
<button type="button"
onclick={e => { e.stopPropagation(); onMove(np, -1) }}
disabled={i === 0}
class="flex items-center justify-center w-5 h-5 rounded text-xs text-gray-500 hover:text-gray-300 hover:bg-gray-700 transition-colors disabled:opacity-20"
title={glob.t('vcen.editor.mtas.field.moveUp')}>↑</button>
<button type="button"
onclick={e => { e.stopPropagation(); onMove(np, 1) }}
disabled={i === order.length - 1}
class="flex items-center justify-center w-5 h-5 rounded text-xs text-gray-500 hover:text-gray-300 hover:bg-gray-700 transition-colors disabled:opacity-20"
title={glob.t('vcen.editor.mtas.field.moveDown')}>↓</button>
</div>
</div>
{#if isGroup && groupOpen && childKeys.length > 0}
<EditorTreeNodesRecursive
meta={groupValue(field)}
order={childKeys}
path={np}
depth={depth + 1}
{selectedPath} {glob}
{onSelect} {onMove}
/>
{/if}
{#if isGroup && groupOpen && childKeys.length === 0}
<div class="text-xs text-gray-600 italic py-1.5"
style="padding-left: {(depth + 1) * 12 + 8}px">
{glob.t('vcen.editor.mtas.field.emptyGroup')}
</div>
{/if}
{/each}

Powered by TurnKey Linux.