frontend_nyla/src/components/Automation2FlowEditor/nodes/shared/DataPicker.tsx

126 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Automation2 Flow Editor - Data Picker for selecting node output references.
*/
import React, { useState } from 'react';
import { createRef, type DataRef } from './dataRef';
import styles from '../../editor/Automation2FlowEditor.module.css';
interface DataPickerProps {
open: boolean;
onClose: () => void;
onPick: (ref: DataRef) => void;
availableSourceIds: string[];
nodes: Array<{ id: string; title?: string; type?: string }>;
nodeOutputsPreview: Record<string, unknown>;
getNodeLabel: (node: { id: string; title?: string }) => string;
}
/** Collect all pickable paths (each leads to a value the user can reference) */
function buildPickablePaths(obj: unknown, basePath: (string | number)[] = []): Array<{ path: (string | number)[]; label: string }> {
const pathLabel = basePath.length ? basePath.map(String).join(' → ') : '(ganze Ausgabe)';
if (obj == null || typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean') {
return [{ path: [...basePath], label: pathLabel }];
}
if (Array.isArray(obj)) {
const result: Array<{ path: (string | number)[]; label: string }> = [{ path: [...basePath], label: pathLabel }];
for (let i = 0; i < Math.min(obj.length, 10); i++) {
result.push(...buildPickablePaths(obj[i], [...basePath, i]));
}
return result;
}
if (typeof obj === 'object') {
const result: Array<{ path: (string | number)[]; label: string }> = [{ path: [...basePath], label: pathLabel }];
for (const [k, v] of Object.entries(obj as Record<string, unknown>)) {
result.push(...buildPickablePaths(v, [...basePath, k]));
}
return result;
}
return [{ path: [...basePath], label: pathLabel }];
}
export const DataPicker: React.FC<DataPickerProps> = ({
open,
onClose,
onPick,
availableSourceIds,
nodes,
nodeOutputsPreview,
getNodeLabel,
}) => {
const [expandedNodes, setExpandedNodes] = useState<Set<string>>(new Set());
if (!open) return null;
const toggleExpand = (nodeId: string) => {
setExpandedNodes((prev) => {
const next = new Set(prev);
if (next.has(nodeId)) next.delete(nodeId);
else next.add(nodeId);
return next;
});
};
const handlePick = (nodeId: string, path: (string | number)[]) => {
onPick(createRef(nodeId, path));
onClose();
};
return (
<div className={styles.dataPickerOverlay} onClick={onClose}>
<div className={styles.dataPickerModal} onClick={(e) => e.stopPropagation()}>
<div className={styles.dataPickerHeader}>
<h4 className={styles.dataPickerTitle}>Datenquelle wählen</h4>
<button type="button" className={styles.dataPickerClose} onClick={onClose} aria-label="Schließen">
×
</button>
</div>
<div className={styles.dataPickerBody}>
{(() => {
const filteredIds = availableSourceIds.filter((nodeId) => {
const node = nodes.find((n) => n.id === nodeId);
return node?.type !== 'trigger.manual';
});
if (filteredIds.length === 0) {
return <p className={styles.dataPickerEmpty}>Keine vorherigen Nodes verfügbar.</p>;
}
return filteredIds.map((nodeId) => {
const node = nodes.find((n) => n.id === nodeId);
const preview = nodeOutputsPreview[nodeId];
const label = node ? getNodeLabel(node) : nodeId;
const paths = buildPickablePaths(preview);
const isExpanded = expandedNodes.has(nodeId);
return (
<div key={nodeId} className={styles.dataPickerNodeSection}>
<button
type="button"
className={styles.dataPickerNodeHeader}
onClick={() => toggleExpand(nodeId)}
>
<span className={styles.dataPickerExpandIcon}>{isExpanded ? '▼' : '▶'}</span>
<span className={styles.dataPickerNodeLabel}>{label}</span>
</button>
{isExpanded && (
<div className={styles.dataPickerTree}>
{paths.map((p, i) => (
<button
key={`${p.path.join('.')}-${i}`}
type="button"
className={styles.dataPickerLeaf}
onClick={() => handlePick(nodeId, p.path)}
>
{p.label}
</button>
))}
</div>
)}
</div>
);
});
})()}
</div>
</div>
</div>
);
};