6 var VALIDATION_META = {};
10 function t(key, fallback) {
11 return Object.prototype.hasOwnProperty.call(TEXT, key) ? String(TEXT[key]) : String(fallback || key);
14 function tf(key, replacements, fallback) {
15 var message = t(key, fallback);
16 Object.keys(replacements || {}).forEach(function (name) {
17 message = message.split("{" + name + "}").join(String(replacements[name]));
22 function applyMessages(root) {
24 TEXT = JSON.parse(root.getAttribute("data-workflow-messages") || "{}");
30 input: { label: t("kind_input", "Eingabe"), icon: "bi-input-cursor-text", action: "form", defaultLabel: t("js_new_input", "Neue Eingabe"), defaultEvent: t("js_input_complete", "Eingabe vollständig") },
31 action: { label: t("kind_action", "Aktion"), icon: "bi-lightning-charge", action: "module", defaultLabel: t("js_new_action", "Neue Aktion"), defaultEvent: t("js_action_complete", "Aktion ausgeführt") },
32 check: { label: t("kind_check", "Prüfung"), icon: "bi-shield-check", action: "select", defaultLabel: t("js_new_check", "Neue Prüfung"), defaultEvent: t("js_check_complete", "Prüfung abgeschlossen") },
33 decision: { label: t("kind_decision", "Entscheidung"), icon: "bi-signpost-split", action: "select", defaultLabel: t("js_new_decision", "Neue Entscheidung"), defaultEvent: t("js_decision_complete", "Entscheidung getroffen") }
37 exactly_one: t("validation_exactly_one", "Genau ein Wert"),
38 at_least_one: t("validation_at_least_one", "Mindestens ein Wert"),
39 not_empty: t("validation_not_empty", "Nicht leer"),
40 positive_integer: t("validation_positive_integer", "Ganzzahl größer 0"),
41 confirmed: t("validation_confirmed", "Ausdrücklich bestätigt")
45 form: t("resolver_form", "Eingabeformular öffnen"),
46 select: t("resolver_select", "Auswahlformular öffnen"),
47 create: t("resolver_create", "Erfassungsformular öffnen"),
48 module: t("resolver_module", "Modulformular öffnen")
54 label: t("js_payment_label", "Zahlungsbedingung"),
56 event: t("js_payment_event", "Zahlungsbedingung ausgewählt"),
59 validation: "exactly_one",
60 question: t("js_payment_question", "Welche Zahlungsbedingung ist ausgewählt?"),
61 missing: t("js_payment_missing", "Keine Zahlungsbedingung ausgewählt – der Workflow ist unvollständig."),
62 resolver: t("js_payment_resolver", "Zahlungsbedingung auswählen"),
63 hint: t("js_payment_hint", "Wähle die Zahlungsfrist, die für diesen Vorgang gelten soll."),
64 options: t("js_payment_options", "sofort=Sofort fällig")
69 function visibleRows(root) {
70 return Array.prototype.filter.call(root.querySelectorAll("[data-workflow-step-row]"), function (row) {
71 return !row.classList.contains("dbx-workflow-step-row-empty") && row.style.display !== "none";
75 function field(row, namePart) {
76 return row.querySelector('[name^="' + namePart + '["]');
79 function value(row, namePart) {
80 var el = field(row, namePart);
81 return el ? String(el.value || "").trim() : "";
84 function actionChoices(row) {
85 return Array.prototype.slice.call(row.querySelectorAll("[data-workflow-action-choice]"));
88 function selectedActions(row) {
89 return actionChoices(row).filter(function (choice) { return choice.checked; }).map(function (choice) { return choice.value; });
92 function setActionSelected(row, action, checked) {
93 actionChoices(row).forEach(function (choice) {
94 if (choice.value === action) choice.checked = checked;
98 function syncActionControls(row, requestedPreferred) {
99 var preferred = field(row, "workflow_step_action");
100 if (requestedPreferred) setActionSelected(row, requestedPreferred, true);
101 var actions = selectedActions(row);
102 if (!actions.length) {
103 var fallback = requestedPreferred || (preferred && preferred.value) || "form";
104 setActionSelected(row, fallback, true);
105 actions = selectedActions(row);
108 var preferredValue = requestedPreferred || preferred.value;
109 if (actions.indexOf(preferredValue) < 0) preferredValue = actions[0];
110 Array.prototype.forEach.call(preferred.options, function (option) {
111 option.disabled = actions.indexOf(option.value) < 0;
113 preferred.value = preferredValue;
115 var moduleSetting = row.querySelector("[data-workflow-module-setting]");
116 if (moduleSetting) moduleSetting.hidden = actions.indexOf("module") < 0;
119 function slug(text) {
120 return String(text || "")
122 .replace(/[ä]/g, "ae")
123 .replace(/[ö]/g, "oe")
124 .replace(/[ü]/g, "ue")
125 .replace(/[ß]/g, "ss")
126 .replace(/[^a-z0-9]+/g, "_")
127 .replace(/^_+|_+$/g, "");
130 function updateRowSummary(row) {
131 var kind = value(row, "workflow_step_kind") || "input";
132 var meta = KIND_META[kind] || KIND_META.input;
133 var label = value(row, "workflow_step_label") || t("new_task", "Neue Aufgabe");
134 var keyInput = field(row, "workflow_step_key");
135 if (keyInput && !keyInput.value.trim()) keyInput.value = slug(label);
136 if (!row.dataset.workflowLastKey && keyInput && keyInput.value.trim()) row.dataset.workflowLastKey = keyInput.value.trim();
138 row.dataset.stepKind = kind;
139 var icon = row.querySelector("[data-workflow-kind-icon]");
140 var kindLabel = row.querySelector("[data-workflow-kind-label]");
141 var title = row.querySelector("[data-workflow-step-title]");
142 var result = row.querySelector("[data-workflow-step-result]");
143 var automation = value(row, "workflow_step_automation");
144 var autoBadge = row.querySelector("[data-workflow-auto-badge]");
145 var question = field(row, "workflow_step_question");
146 var missing = field(row, "workflow_step_missing_message");
147 if (question && row.dataset.questionAuto === "1") question.value = tf("default_question", { label: label }, "Ist „{label}“ vollständig und richtig?");
148 if (missing && row.dataset.missingAuto === "1") missing.value = tf("default_missing_message", { label: label }, "{label} fehlt oder ist nicht vollständig – der Workflow ist unvollständig.");
149 if (icon) icon.className = "bi " + meta.icon;
150 if (kindLabel) kindLabel.textContent = meta.label;
151 if (title) title.textContent = label;
152 if (result) result.textContent = value(row, "workflow_step_event") || t("no_result", "Noch kein Ergebnistext");
153 if (autoBadge) autoBadge.hidden = automation !== "observe";
156 function renderChecks(root) {
157 var rows = visibleRows(root);
158 var list = root.querySelector("[data-workflow-check-list]");
159 var count = root.querySelector("[data-workflow-check-count]");
160 if (count) count.textContent = rows.length + " " + (rows.length === 1 ? t("js_requirement_singular", "Anforderung") : t("js_requirement_plural", "Anforderungen"));
162 list.innerHTML = rows.map(function (row) {
163 var label = value(row, "workflow_step_label") || value(row, "workflow_step_key") || t("new_task", "Neue Aufgabe");
164 var question = value(row, "workflow_step_question") || tf("default_question", { label: label }, "Ist „{label}“ vollständig und richtig?");
165 var validation = value(row, "workflow_step_validation") || "exactly_one";
166 var required = value(row, "workflow_step_required") !== "0";
167 var action = value(row, "workflow_step_action") || "form";
168 var resolver = value(row, "workflow_step_resolver_label") || ACTION_META[action] || ACTION_META.form;
169 return '<article class="dbx-workflow-check-preview-item">' +
170 '<i class="bi bi-shield-check"></i><div><strong>' + escapeHtml(question) + '</strong><small>' + escapeHtml(label) + '</small></div>' +
171 '<span class="dbx-workflow-check-rule">' + escapeHtml(VALIDATION_META[validation] || validation) + '</span>' +
172 '<span class="dbx-workflow-check-required">' + (required ? t("preview_required", "Pflicht") : t("preview_optional", "Optional")) + '</span>' +
173 '<span class="dbx-workflow-check-resolver"><i class="bi bi-box-arrow-in-right"></i> ' + escapeHtml(resolver) + '</span></article>';
177 function updateDependencyOptions(root) {
178 var rows = visibleRows(root);
179 rows.forEach(function (row) {
180 var select = field(row, "workflow_step_depends_on");
182 var selected = select.dataset.workflowPendingDependency || select.value;
183 var ownKey = value(row, "workflow_step_key");
184 var html = '<option value="">' + escapeHtml(t("js_direct_after_previous", "Direkt nach vorherigem Schritt")) + '</option>';
185 rows.forEach(function (candidate) {
186 var key = value(candidate, "workflow_step_key");
187 if (!key || key === ownKey) return;
188 var label = value(candidate, "workflow_step_label") || key;
189 html += '<option value="' + escapeHtml(key) + '">' + escapeHtml(label) + "</option>";
191 select.innerHTML = html;
192 select.value = selected;
193 if (selected && select.value !== selected) select.dataset.workflowPendingDependency = selected;
194 else delete select.dataset.workflowPendingDependency;
198 function migrateDependencyKey(root, row) {
199 var oldKey = row.dataset.workflowLastKey || "";
200 var newKey = value(row, "workflow_step_key");
201 if (oldKey && newKey && oldKey !== newKey) {
202 visibleRows(root).forEach(function (candidate) {
203 var select = field(candidate, "workflow_step_depends_on");
205 var dependency = select.dataset.workflowPendingDependency || select.value;
206 if (dependency === oldKey) select.dataset.workflowPendingDependency = newKey;
209 if (newKey) row.dataset.workflowLastKey = newKey;
212 function escapeHtml(text) {
213 var div = document.createElement("div");
214 div.textContent = String(text || "");
215 return div.innerHTML;
218 function renumber(root) {
219 visibleRows(root).forEach(function (row, index) {
220 var number = row.querySelector("[data-workflow-step-number]");
221 if (number) number.textContent = String(index + 1).padStart(2, "0");
222 updateRowSummary(row);
224 updateDependencyOptions(root);
230 function setStatus(root, type, title, items) {
231 var box = root.querySelector("[data-workflow-validation]");
233 box.className = "dbx-workflow-validation is-" + type;
234 var icon = type === "error" ? "bi-x-octagon" : (type === "warning" ? "bi-exclamation-triangle" : "bi-check2-circle");
235 box.innerHTML = '<i class="bi ' + icon + '"></i><div><strong>' + escapeHtml(title) + "</strong>" +
236 (items.length ? "<ul><li>" + items.map(escapeHtml).join("</li><li>") + "</li></ul>" : "") + "</div>";
239 function validate(root) {
240 var rows = visibleRows(root);
246 rows.forEach(function (row, index) {
247 var label = value(row, "workflow_step_label");
248 var key = value(row, "workflow_step_key");
249 if (!label) errors.push(tf("js_error_label_missing", { step: index + 1 }, "Schritt {step} braucht eine Bezeichnung."));
250 if (!key) errors.push(tf("js_error_key_missing", { step: index + 1 }, "Schritt {step} braucht einen internen Schlüssel."));
251 if (key && byKey[key]) errors.push(tf("js_error_key_duplicate", { key: key }, "Der Schlüssel „{key}“ wird mehrfach verwendet."));
254 positions[key] = index;
256 var kind = value(row, "workflow_step_kind");
257 var question = value(row, "workflow_step_question");
258 if (!question) errors.push(tf("js_error_question_missing", { label: label || key }, "Für „{label}“ fehlt die automatisch verwendete Prüffrage."));
259 var options = value(row, "workflow_step_options").split(/\r?\n|,/).filter(function (item) { return item.trim(); });
260 if (kind === "decision" && options.length < 2) {
261 errors.push(tf("js_error_decision_options", { label: label || key }, "Die Entscheidung „{label}“ braucht mindestens zwei Ergebnisse."));
263 if (value(row, "workflow_step_automation") === "observe" && kind !== "check" && kind !== "decision") {
264 warnings.push(tf("js_warning_automation", { label: label || key }, "Automatik bei „{label}“ ist nur sinnvoll als Prüfung oder Entscheidung."));
266 var actions = selectedActions(row);
267 if (actions.indexOf("select") >= 0 && !options.length && !value(row, "workflow_step_source")) {
268 warnings.push(tf("js_warning_select_source", { label: label || key }, "Das Auswahlformular für „{label}“ hat noch keine Auswahlwerte oder Datenquelle."));
270 if (actions.indexOf("module") >= 0 && !value(row, "workflow_step_module_links")) {
271 warnings.push(tf("js_warning_module_link", { label: label || key }, "Für „{label}“ ist noch kein Modulformular hinterlegt."));
275 rows.forEach(function (row) {
276 var key = value(row, "workflow_step_key");
277 var depends = value(row, "workflow_step_depends_on");
278 if (!depends) return;
279 if (!byKey[depends]) errors.push(tf("js_error_dependency_missing", { dependency: depends, key: key }, "Die Abhängigkeit „{dependency}“ von „{key}“ existiert nicht."));
280 else if (positions[depends] >= positions[key]) errors.push(tf("js_error_dependency_order", { dependency: depends, key: key }, "„{key}“ muss hinter seiner Voraussetzung „{dependency}“ liegen."));
283 if (!rows.length) errors.push(t("validation_minimum_step", "Mindestens ein Schritt ist erforderlich."));
284 if (errors.length) setStatus(root, "error", t("js_status_invalid", "Workflow noch nicht speicherbar"), errors);
285 else if (warnings.length) setStatus(root, "warning", t("js_status_warning", "Workflow ist speicherbar – Hinweise prüfen"), warnings);
286 else setStatus(root, "ok", t("js_status_valid", "Prüfung wurde automatisch abgeleitet"), [
287 tf("js_status_valid_detail", { steps: rows.length, requirements: rows.length }, "{steps} Schritte liefern {requirements} Prüfanforderungen samt passendem Bearbeitungsweg.")
290 var form = root.closest("form");
291 if (form) form.dataset.workflowValid = errors.length ? "0" : "1";
292 return errors.length === 0;
295 function point(canvas, element, side) {
296 var base = canvas.getBoundingClientRect();
297 var rect = element.getBoundingClientRect();
299 x: rect.left - base.left + rect.width / 2,
300 y: (side === "top" ? rect.top : rect.bottom) - base.top
304 function pathBetween(a, b) {
305 var bend = Math.max(28, Math.abs(b.y - a.y) * 0.42);
306 return "M " + a.x + " " + a.y + " C " + a.x + " " + (a.y + bend) + ", " + b.x + " " + (b.y - bend) + ", " + b.x + " " + b.y;
309 function draw(root) {
310 window.requestAnimationFrame(function () {
311 var canvas = root.querySelector("[data-workflow-canvas]");
312 var svg = root.querySelector("[data-workflow-lines]");
313 var start = root.querySelector("[data-workflow-start]");
314 var finish = root.querySelector("[data-workflow-finish]");
315 var rows = visibleRows(root);
316 if (!canvas || !svg || !start || !finish) return;
318 var width = Math.max(1, canvas.clientWidth);
319 var height = Math.max(1, canvas.scrollHeight);
320 svg.setAttribute("viewBox", "0 0 " + width + " " + height);
321 svg.setAttribute("width", width);
322 svg.setAttribute("height", height);
325 rows.forEach(function (row) { byKey[value(row, "workflow_step_key")] = row; });
326 var markup = '<defs><marker id="dbxWorkflowArrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z"></path></marker></defs>';
327 rows.forEach(function (row, index) {
328 var depends = value(row, "workflow_step_depends_on");
329 var source = (depends && byKey[depends]) ? byKey[depends] : (index ? rows[index - 1] : start);
330 var a = point(canvas, source, "bottom");
331 var b = point(canvas, row, "top");
332 var branch = value(row, "workflow_step_depends_value");
333 var sourceKind = source.dataset ? source.dataset.stepKind : "start";
334 markup += '<path class="dbx-workflow-flow-line is-' + escapeHtml(sourceKind || "input") + '" d="' + pathBetween(a, b) + '" marker-end="url(#dbxWorkflowArrow)"></path>';
336 markup += '<text class="dbx-workflow-flow-label" x="' + ((a.x + b.x) / 2 + 9) + '" y="' + ((a.y + b.y) / 2) + '">' + escapeHtml(branch) + "</text>";
339 var last = rows.length ? rows[rows.length - 1] : start;
340 markup += '<path class="dbx-workflow-flow-line is-finish" d="' + pathBetween(point(canvas, last, "bottom"), point(canvas, finish, "top")) + '" marker-end="url(#dbxWorkflowArrow)"></path>';
341 svg.innerHTML = markup;
345 function setField(row, namePart, fieldValue) {
346 var el = field(row, namePart);
347 if (el) el.value = fieldValue;
350 function resetRow(row, kind, presetName) {
351 Array.prototype.forEach.call(row.querySelectorAll("input, textarea, select"), function (el) {
352 if (el.name && (el.name.indexOf("workflow_step_present") >= 0 || el.name.indexOf("workflow_step_action_contract") >= 0)) return;
353 if (el.type === "checkbox") el.checked = el.name.indexOf("workflow_step_active") >= 0;
356 var kindField = field(row, "workflow_step_kind");
357 var actionField = field(row, "workflow_step_action");
358 var requiredField = field(row, "workflow_step_required");
359 var modeField = field(row, "workflow_step_mode");
360 var automationField = field(row, "workflow_step_automation");
361 var labelField = field(row, "workflow_step_label");
362 var eventField = field(row, "workflow_step_event");
363 var optionsField = field(row, "workflow_step_options");
364 var meta = KIND_META[kind] || KIND_META.input;
365 if (kindField) kindField.value = kind;
366 if (actionField) actionField.value = meta.action;
367 syncActionControls(row, meta.action);
368 if (requiredField) requiredField.value = "1";
369 if (modeField) modeField.value = "single";
370 if (automationField) automationField.value = (kind === "check") ? "observe" : "manual";
371 if (labelField) labelField.value = meta.defaultLabel;
372 if (eventField) eventField.value = meta.defaultEvent;
373 if (optionsField && kind === "decision") optionsField.value = "yes=" + t("js_yes", "Ja") + "\nno=" + t("js_no", "Nein");
374 setField(row, "workflow_step_validation", "exactly_one");
375 setField(row, "workflow_step_question", tf("default_question", { label: meta.defaultLabel }, "Ist „{label}“ vollständig und richtig?"));
376 setField(row, "workflow_step_missing_message", tf("default_missing_message", { label: meta.defaultLabel }, "{label} fehlt oder ist nicht vollständig – der Workflow ist unvollständig."));
377 setField(row, "workflow_step_resolver_label", ACTION_META[meta.action] || ACTION_META.form);
378 row.dataset.questionAuto = "1";
379 row.dataset.missingAuto = "1";
380 delete row.dataset.workflowLastKey;
382 var preset = PRESETS[presetName] || null;
384 kind = preset.kind || kind;
385 actionChoices(row).forEach(function (choice) { choice.checked = false; });
386 setField(row, "workflow_step_kind", kind);
387 setField(row, "workflow_step_label", preset.label || meta.defaultLabel);
388 setField(row, "workflow_step_key", preset.key || slug(preset.label));
389 setField(row, "workflow_step_event", preset.event || t("js_default_event", "Angabe vollständig"));
390 setField(row, "workflow_step_action", preset.action || meta.action);
391 syncActionControls(row, preset.action || meta.action);
392 setField(row, "workflow_step_mode", preset.mode || "single");
393 setField(row, "workflow_step_validation", preset.validation || "exactly_one");
394 setField(row, "workflow_step_question", preset.question || "");
395 setField(row, "workflow_step_missing_message", preset.missing || "");
396 setField(row, "workflow_step_resolver_label", preset.resolver || ACTION_META[preset.action] || ACTION_META.form);
397 setField(row, "workflow_step_hint", preset.hint || "");
398 setField(row, "workflow_step_options", preset.options || "");
399 row.dataset.questionAuto = "0";
400 row.dataset.missingAuto = "0";
402 row.classList.remove("dbx-workflow-step-row-empty");
403 row.style.display = "";
404 row.setAttribute("draggable", "true");
405 var details = row.querySelector("details");
406 if (details) details.open = true;
409 function addRow(root, kind, beforeRow, presetName) {
410 var row = root.querySelector(".dbx-workflow-step-row-empty");
412 setStatus(root, "error", t("js_no_free_step", "Kein freier Schritt mehr"), [
413 t("js_no_free_step_detail", "Workflow speichern und erneut öffnen, um weitere Schritte anzulegen.")
417 resetRow(row, kind || "input", presetName || "");
418 if (beforeRow && beforeRow.parentNode === row.parentNode) row.parentNode.insertBefore(row, beforeRow);
420 var label = field(row, "workflow_step_label");
421 if (label) label.focus();
425 function removeRow(root, row) {
426 var active = field(row, "workflow_step_active");
427 var label = field(row, "workflow_step_label");
428 var key = field(row, "workflow_step_key");
429 if (active) active.checked = false;
430 if (label) label.value = "";
431 if (key) key.value = "";
432 row.classList.add("dbx-workflow-step-row-empty");
433 row.style.display = "none";
434 var list = root.querySelector("[data-workflow-step-list]");
435 if (list) list.appendChild(row);
439 function nearestRow(list, y) {
440 var rows = Array.prototype.filter.call(list.querySelectorAll("[data-workflow-step-row]"), function (row) {
441 return !row.classList.contains("dbx-workflow-step-row-empty") && !row.classList.contains("is-dragging");
443 var closest = { offset: Number.NEGATIVE_INFINITY, row: null };
444 rows.forEach(function (row) {
445 var box = row.getBoundingClientRect();
446 var offset = y - box.top - box.height / 2;
447 if (offset < 0 && offset > closest.offset) closest = { offset: offset, row: row };
452 function init(root) {
453 if (!root || root.dataset.workflowDesignerReady === "1") return;
455 root.dataset.workflowDesignerReady = "1";
456 var list = root.querySelector("[data-workflow-step-list]");
459 root.addEventListener("input", function (event) {
460 var row = event.target.closest("[data-workflow-step-row]");
462 if (event.target === field(row, "workflow_step_key")) migrateDependencyKey(root, row);
463 if (event.target.name && event.target.name.indexOf("workflow_step_question") >= 0) row.dataset.questionAuto = "0";
464 if (event.target.name && event.target.name.indexOf("workflow_step_missing_message") >= 0) row.dataset.missingAuto = "0";
465 updateRowSummary(row);
467 updateDependencyOptions(root);
473 root.addEventListener("change", function (event) {
474 var row = event.target.closest("[data-workflow-step-row]");
476 if (event.target === field(row, "workflow_step_depends_on")) delete event.target.dataset.workflowPendingDependency;
477 if (event.target.matches("[data-workflow-action-choice]")) {
478 syncActionControls(row, "");
480 if (event.target.name && event.target.name.indexOf("workflow_step_kind") >= 0) {
481 var meta = KIND_META[event.target.value] || KIND_META.input;
482 var action = field(row, "workflow_step_action");
483 if (action) action.value = meta.action;
484 syncActionControls(row, meta.action);
485 } else if (event.target === field(row, "workflow_step_action")) {
486 syncActionControls(row, event.target.value);
488 updateRowSummary(row);
492 root.addEventListener("click", function (event) {
493 var add = event.target.closest("[data-workflow-add-kind]");
495 event.preventDefault();
496 addRow(root, add.dataset.workflowAddKind || "input", null, add.dataset.workflowAddPreset || "");
499 var remove = event.target.closest("[data-workflow-remove-step]");
501 event.preventDefault();
502 removeRow(root, remove.closest("[data-workflow-step-row]"));
506 root.addEventListener("pointerdown", function (event) {
507 var handle = event.target.closest("[data-workflow-drag-handle]");
509 var row = handle.closest("[data-workflow-step-row]");
510 if (row) row.dataset.dragReady = "1";
514 root.addEventListener("pointerup", function () {
515 visibleRows(root).forEach(function (row) { delete row.dataset.dragReady; });
518 root.addEventListener("dragstart", function (event) {
519 var palette = event.target.closest("[data-workflow-add-kind]");
521 event.dataTransfer.setData("text/x-dbx-workflow-kind", palette.dataset.workflowAddKind || "input");
522 event.dataTransfer.setData("text/x-dbx-workflow-preset", palette.dataset.workflowAddPreset || "");
523 event.dataTransfer.effectAllowed = "copy";
526 var row = event.target.closest("[data-workflow-step-row]");
527 var handle = event.target.closest("[data-workflow-drag-handle]");
528 if (!row || (!handle && row.dataset.dragReady !== "1")) {
529 event.preventDefault();
532 row.classList.add("is-dragging");
533 event.dataTransfer.effectAllowed = "move";
534 event.dataTransfer.setData("text/x-dbx-workflow-row", "1");
537 list.addEventListener("dragover", function (event) {
538 event.preventDefault();
539 var dragging = root.querySelector(".dbx-workflow-step-row.is-dragging");
541 var before = nearestRow(list, event.clientY);
542 if (before) list.insertBefore(dragging, before);
544 var firstEmpty = list.querySelector(".dbx-workflow-step-row-empty");
545 list.insertBefore(dragging, firstEmpty || null);
549 event.dataTransfer.dropEffect = dragging ? "move" : "copy";
552 list.addEventListener("drop", function (event) {
553 event.preventDefault();
554 var kind = event.dataTransfer.getData("text/x-dbx-workflow-kind");
555 var preset = event.dataTransfer.getData("text/x-dbx-workflow-preset");
556 if (kind) addRow(root, kind, nearestRow(list, event.clientY), preset);
557 var dragging = root.querySelector(".dbx-workflow-step-row.is-dragging");
558 if (dragging) dragging.classList.remove("is-dragging");
562 root.addEventListener("dragend", function () {
563 visibleRows(root).forEach(function (row) {
564 row.classList.remove("is-dragging");
565 delete row.dataset.dragReady;
570 var form = root.closest("form");
572 form.addEventListener("submit", function (event) {
573 if (!validate(root)) {
574 event.preventDefault();
575 event.stopImmediatePropagation();
576 var box = root.querySelector("[data-workflow-validation]");
577 if (box) box.scrollIntoView({ behavior: "smooth", block: "center" });
582 if (window.ResizeObserver) {
583 var observer = new ResizeObserver(function () { draw(root); });
584 observer.observe(root.querySelector("[data-workflow-canvas]"));
586 window.addEventListener("resize", function () { draw(root); });
588 visibleRows(root).forEach(function (row) {
589 row.dataset.workflowLastKey = value(row, "workflow_step_key");
590 syncActionControls(row, "");
596 Array.prototype.forEach.call(document.querySelectorAll("[data-workflow-designer]"), init);
599 if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", initAll);
602 document.addEventListener("dbx:content:loaded", initAll);
603 window.setTimeout(initAll, 0);