From 3dbcf2cd675172420cb4588c2eca665f47412e42 Mon Sep 17 00:00:00 2001 From: Mel Massadian <7041726+melMass@users.noreply.github.com> Date: Mon, 19 Jan 2026 17:34:25 +0000 Subject: [PATCH] feat(mtb-api): add drag-drop reordering for api inputs - implement drag-drop interface in TabUI with svelte-dnd-action - add notifyOrderChanged event to persist input order to node properties - update getAPIInputs to sort by order field and assign sequential ids - add order? optional field to APIInputSettings for tracking display order - listen for MTB_API_ORDER_CHANGED_EVENT in APIPanel and apply reordering via applyInputOrder --- web_source/src/lib/TabUI.svelte | 11 ++++ web_source/src/mtb_api/panel.svelte.ts | 77 ++++++++++++++++++++------ web_source/src/mtb_api/types.ts | 2 + 3 files changed, 72 insertions(+), 18 deletions(-) diff --git a/web_source/src/lib/TabUI.svelte b/web_source/src/lib/TabUI.svelte index 023fc40..38c1eef 100644 --- a/web_source/src/lib/TabUI.svelte +++ b/web_source/src/lib/TabUI.svelte @@ -2,9 +2,12 @@ import { dndzone } from 'svelte-dnd-action' import InspectorItem from './InspectorItem.svelte' import { flip } from 'svelte/animate' + import { notifyOrderChanged } from '../mtb_api/panel.svelte' interface InputItem { id: number + node_id?: number + original_name?: string [key: string]: unknown } @@ -16,8 +19,16 @@ function handleDndConsider(e: CustomEvent<{ items: InputItem[] }>) { inputs = e.detail.items } + function handleDndFinalize(e: CustomEvent<{ items: InputItem[] }>) { inputs = e.detail.items + // Persist the new order back to the nodes + const orderedInputs = inputs.map((item, index) => ({ + node_id: item.node_id as number, + original_name: item.original_name as string, + order: index, + })) + notifyOrderChanged(orderedInputs) } diff --git a/web_source/src/mtb_api/panel.svelte.ts b/web_source/src/mtb_api/panel.svelte.ts index e6e3a46..8dd8558 100644 --- a/web_source/src/mtb_api/panel.svelte.ts +++ b/web_source/src/mtb_api/panel.svelte.ts @@ -33,11 +33,19 @@ function createPanelProps() { /** Custom event for API changes */ export const MTB_API_CHANGED_EVENT = 'mtb:api:changed' +/** Custom event for order changes from drag-drop */ +export const MTB_API_ORDER_CHANGED_EVENT = 'mtb:api:order-changed' + /** Dispatch event to notify panel of changes */ export function notifyAPIChanged(): void { window.dispatchEvent(new CustomEvent(MTB_API_CHANGED_EVENT)) } +/** Dispatch event with new order after drag-drop */ +export function notifyOrderChanged(orderedInputs: { node_id: number; original_name: string; order: number }[]): void { + window.dispatchEvent(new CustomEvent(MTB_API_ORDER_CHANGED_EVENT, { detail: orderedInputs })) +} + /** * Controls the API panel UI for managing exposed workflow inputs */ @@ -65,6 +73,29 @@ export class APIPanel { this.updateContent() }) }) + + // Listen for order changes from drag-drop + window.addEventListener(MTB_API_ORDER_CHANGED_EVENT, ((e: CustomEvent<{ node_id: number; original_name: string; order: number }[]>) => { + this.applyInputOrder(e.detail) + }) as EventListener) + } + + /** + * Applies new order to node properties after drag-drop reorder + */ + private applyInputOrder(orderedInputs: { node_id: number; original_name: string; order: number }[]): void { + for (const node of shared.getNodes(true) as MTBNode[]) { + const nodeInputs = orderedInputs.filter(i => i.node_id === node.id) + if (nodeInputs.length === 0) continue + + for (const input of nodeInputs) { + if (node.properties.mtb_api?.inputs?.[input.original_name]) { + node.properties.mtb_api.inputs[input.original_name].order = input.order + } + } + // Trigger property update + node.setProperty('mtb_api', node.properties.mtb_api) + } } /** @@ -133,8 +164,7 @@ export class APIPanel { * Collects all API inputs from marked nodes in the graph */ getAPIInputs(): Record { - const inputs: Record = {} - let counter = 1 + const inputsList: (APIInput & { original_name: string })[] = [] for (const node of shared.getNodes(true) as MTBNode[]) { const widgets = node.widgets @@ -148,28 +178,39 @@ export class APIPanel { const widget = widgets?.find((w) => w.name === currentName) if (!widget) continue - if (!(inputName in inputs)) { - inputs[inputName] = { - ...current, - id: counter, - name: inputName, - type: current.type, - node_id: node.id, - widgets: [], - // Extract current value from widget - value: widget.value, - // For COMBO types, extract options - options: (widget.options as { values?: string[] })?.values, - } - } - inputs[inputName].widgets.push(widget) - counter++ + inputsList.push({ + ...current, + id: 0, // Will be assigned after sorting + name: inputName, + original_name: currentName, + type: current.type, + node_id: node.id, + widgets: [widget], + // Extract current value from widget + value: widget.value, + // For COMBO types, extract options + options: (widget.options as { values?: string[] })?.values, + }) } } } } } + // Sort by order (undefined order goes to end) + inputsList.sort((a, b) => { + const orderA = a.order ?? Number.MAX_SAFE_INTEGER + const orderB = b.order ?? Number.MAX_SAFE_INTEGER + return orderA - orderB + }) + + // Convert to Record and assign sequential IDs + const inputs: Record = {} + inputsList.forEach((input, index) => { + input.id = index + 1 + inputs[input.name] = input + }) + return inputs } diff --git a/web_source/src/mtb_api/types.ts b/web_source/src/mtb_api/types.ts index 0e99c31..e6ffa36 100644 --- a/web_source/src/mtb_api/types.ts +++ b/web_source/src/mtb_api/types.ts @@ -20,6 +20,8 @@ export interface APIInputSettings { enabled: boolean type: APIInputType name: string + /** Display order (lower = first) */ + order?: number // NUMBER config min?: number