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.
117 lines
4.6 KiB
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}
|