213 lines
6.3 KiB
TypeScript
213 lines
6.3 KiB
TypeScript
/**
|
|
* Loop node - Datenquelle für Iteration mit benutzerfreundlichen Labels.
|
|
* Zeigt nur iterierbare Quellen: Arrays und Objekte (Formularfelder → {name, value}).
|
|
*/
|
|
|
|
import React from 'react';
|
|
import { createRef, isRef, type DataRef } from './dataRef';
|
|
import { refToOptionValue, optionValueToRef } from './RefSourceSelect';
|
|
import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
|
|
import styles from '../../editor/Automation2FlowEditor.module.css';
|
|
|
|
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
|
|
|
interface LoopOption {
|
|
ref: DataRef;
|
|
label: string;
|
|
}
|
|
|
|
function getValueAtPath(obj: unknown, path: (string | number)[]): unknown {
|
|
let current: unknown = obj;
|
|
for (const seg of path) {
|
|
if (current == null) return undefined;
|
|
const key = typeof seg === 'number' ? String(seg) : seg;
|
|
if (Array.isArray(current) && /^\d+$/.test(key)) {
|
|
current = current[parseInt(key, 10)];
|
|
} else if (typeof current === 'object' && key in (current as object)) {
|
|
current = (current as Record<string, unknown>)[key];
|
|
} else return undefined;
|
|
}
|
|
return current;
|
|
}
|
|
|
|
/** Build iterable options with friendly labels for Loop node */
|
|
function buildLoopOptions(
|
|
sourceIds: string[],
|
|
nodes: Array<{ id: string; type?: string; title?: string; parameters?: Record<string, unknown> }>,
|
|
nodeOutputsPreview: Record<string, unknown>,
|
|
getNodeLabel: (n: { id: string; type?: string; title?: string }) => string
|
|
): LoopOption[] {
|
|
const options: LoopOption[] = [];
|
|
|
|
for (const nodeId of sourceIds) {
|
|
const node = nodes.find((n) => n.id === nodeId);
|
|
if (node?.type === 'trigger.manual') continue;
|
|
|
|
const nodeLabel = getNodeLabel(node ?? { id: nodeId });
|
|
const preview = nodeOutputsPreview[nodeId];
|
|
|
|
// Special cases with friendly labels
|
|
if (node?.type === 'trigger.form') {
|
|
options.push({
|
|
ref: createRef(nodeId, ['payload']),
|
|
label: `Alle Formularfelder (${nodeLabel})`,
|
|
});
|
|
const filesVal = getValueAtPath(preview, ['files']);
|
|
if (Array.isArray(filesVal)) {
|
|
options.push({
|
|
ref: createRef(nodeId, ['files']),
|
|
label: `Alle Dateien aus Formular (${nodeLabel})`,
|
|
});
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (node?.type === 'input.form') {
|
|
options.push({
|
|
ref: createRef(nodeId, []),
|
|
label: `Alle Formularfelder (${nodeLabel})`,
|
|
});
|
|
continue;
|
|
}
|
|
|
|
if (node?.type === 'input.upload') {
|
|
options.push({
|
|
ref: createRef(nodeId, ['files']),
|
|
label: `Alle hochgeladenen Dateien (${nodeLabel})`,
|
|
});
|
|
options.push({
|
|
ref: createRef(nodeId, ['fileIds']),
|
|
label: `Alle Datei-IDs (${nodeLabel})`,
|
|
});
|
|
continue;
|
|
}
|
|
|
|
if (node?.type === 'flow.loop') {
|
|
options.push({
|
|
ref: createRef(nodeId, ['items']),
|
|
label: `Alle Elemente aus Schleife (${nodeLabel})`,
|
|
});
|
|
continue;
|
|
}
|
|
|
|
if (node?.type === 'email.searchEmail') {
|
|
options.push({
|
|
ref: createRef(nodeId, ['data', 'searchResults', 'results']),
|
|
label: `Alle gefundenen E-Mails (${nodeLabel})`,
|
|
});
|
|
continue;
|
|
}
|
|
|
|
if (node?.type === 'email.checkEmail') {
|
|
options.push({
|
|
ref: createRef(nodeId, ['data', 'emails', 'emails']),
|
|
label: `Alle E-Mails (${nodeLabel})`,
|
|
});
|
|
continue;
|
|
}
|
|
|
|
if (node?.type === 'sharepoint.listFiles') {
|
|
options.push({
|
|
ref: createRef(nodeId, ['files']),
|
|
label: `Alle Dateien (${nodeLabel})`,
|
|
});
|
|
continue;
|
|
}
|
|
|
|
// Generic: find top-level arrays and root object in preview
|
|
if (preview != null && typeof preview === 'object') {
|
|
for (const [k, v] of Object.entries(preview as Record<string, unknown>)) {
|
|
const path: (string | number)[] = [k];
|
|
const pathStr = path.join('.');
|
|
if (Array.isArray(v)) {
|
|
options.push({
|
|
ref: createRef(nodeId, path),
|
|
label: `${nodeLabel}.${pathStr}`,
|
|
});
|
|
} else if (v != null && typeof v === 'object' && !Array.isArray(v)) {
|
|
const inner = v as Record<string, unknown>;
|
|
for (const [k2, v2] of Object.entries(inner)) {
|
|
if (Array.isArray(v2)) {
|
|
options.push({
|
|
ref: createRef(nodeId, [k, k2]),
|
|
label: `${nodeLabel}.${k}.${k2}`,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Deduplicate by ref (path might repeat from different collection)
|
|
const seen = new Set<string>();
|
|
return options.filter((o) => {
|
|
const key = refToOptionValue(o.ref);
|
|
if (seen.has(key)) return false;
|
|
seen.add(key);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
interface LoopItemsSelectProps {
|
|
value: DataRef | { type: 'value'; value: unknown } | null;
|
|
onChange: (ref: DataRef | null) => void;
|
|
placeholder?: string;
|
|
}
|
|
|
|
export const LoopItemsSelect: React.FC<LoopItemsSelectProps> = ({ value,
|
|
onChange,
|
|
placeholder = 'Über was soll iteriert werden?',
|
|
}) => {
|
|
const { t } = useLanguage();
|
|
const dataFlow = useAutomation2DataFlow();
|
|
if (!dataFlow) return null;
|
|
|
|
const sourceIds = dataFlow.getAvailableSourceIds();
|
|
if (sourceIds.length === 0) {
|
|
return (
|
|
<p className={styles.dynamicValueEmptyHint}>
|
|
Keine vorherigen Nodes verbunden. Verbinden Sie zuerst Nodes mit der Schleife.
|
|
</p>
|
|
);
|
|
}
|
|
|
|
const options = buildLoopOptions(
|
|
sourceIds,
|
|
dataFlow.nodes,
|
|
dataFlow.nodeOutputsPreview,
|
|
dataFlow.getNodeLabel
|
|
);
|
|
|
|
const ref = isRef(value) ? value : null;
|
|
const currentValue = ref ? refToOptionValue(ref) : '';
|
|
|
|
return (
|
|
<div className={styles.ifElseConditionRow}>
|
|
<label>{t('loopItemsSelect.datenquelleFuerIteration')}</label>
|
|
<select
|
|
value={currentValue}
|
|
onChange={(e) => {
|
|
const v = e.target.value;
|
|
if (!v) {
|
|
onChange(null);
|
|
return;
|
|
}
|
|
const r = optionValueToRef(v);
|
|
if (r) onChange(r);
|
|
}}
|
|
className={styles.startsInput}
|
|
>
|
|
<option value="">{placeholder}</option>
|
|
{options.map((o) => (
|
|
<option key={refToOptionValue(o.ref)} value={refToOptionValue(o.ref)}>
|
|
{o.label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
<p className={styles.nodeConfigNameHint}>
|
|
Z.B. für jedes Formularfeld, jede Datei aus Upload, jede E-Mail aus Suche.
|
|
</p>
|
|
</div>
|
|
);
|
|
};
|