UNPKG

processcube.apptemplate

Version:

A AppTemplate for a App build with and for the ProcessCube Plattform

576 lines (489 loc) 17.9 kB
/** * Prüft, ob ein Custom Form mit dem angegebenen Namen bereits existiert * @param {string} customForm - Custom Form Name * @returns {object|null} Existierender Node oder null */ function findExistingCustomFormByName(customForm) { if (!customForm) { return null; } // Suche zuerst nach existierender UI-Page Config mit diesem Namen var existingUiPage = null; RED.nodes.eachConfig(function(node) { if (node.type === "ui-page" && node.name === customForm) { existingUiPage = node; } }); // Suche nach ui-page-navigation Nodes var pageNavigationNodes = RED.nodes.filterNodes({type: "ui-page-navigation"}); // Prüfe ob ein Navigation Node existiert, der auf diese UI-Page verweist return pageNavigationNodes.find(function(node) { // Prüfe sowohl auf String-Namen als auch auf UI-Page Config ID if (existingUiPage && node.page === existingUiPage.id) { return true; } // Fallback: Prüfe auf String-Namen (für alte Nodes) return node.page === customForm; }) || null; } /** * Sucht einen Flow anhand seines Namens * @param {string} flowName - Name des zu suchenden Flows * @returns {{flow: object|null, shouldCreate: boolean}} Flow-Objekt und Flag ob ein neuer Flow erstellt werden muss */ function findOrPrepareFlowForCustomForm(flowName) { if (!flowName) { return { flow: null, shouldCreate: false }; } var targetFlow = null; // Suche in Workspaces RED.nodes.eachWorkspace(function(ws) { if (ws.label === flowName) { targetFlow = ws; } }); // Suche in Subflows falls nicht gefunden if (!targetFlow) { RED.nodes.eachSubflow(function(sf) { if (sf.name === flowName) { targetFlow = sf; } }); } return { flow: targetFlow, shouldCreate: !targetFlow }; } /** * Findet die ProcessCube Engine-Konfiguration * @returns {object} Engine-Konfiguration */ function getEngineConfigForCustomForm() { var engineConfigs = []; RED.nodes.eachConfig(function(node) { if (node.type === "processcube-engine-config") { engineConfigs.push(node); } }); return engineConfigs.find(node => node.url === 'ENGINE_URL'); } /** * Findet oder bereitet eine UI-Base Config Node vor * @returns {{existing: object|null, new: object|null}} Existierende oder neue Base Config */ function findOrPrepareUiBase() { var baseName = "ProcessCube® LowCode Portal"; // Suche nach existierender UI-Base var existingBase = null; RED.nodes.eachConfig(function(node) { if (node.type === "ui-base" && node.name === baseName) { existingBase = node; } }); // Falls gefunden, verwende die existierende Base if (existingBase) { console.log("Verwende existierende ui-base:", baseName); return { existing: existingBase, new: null }; } // Prüfe ob ui-base Node-Type existiert var uiBaseType = RED.nodes.getType("ui-base"); if (!uiBaseType) { console.warn("ui-base Node-Type nicht gefunden. Base kann nicht erstellt werden."); return { existing: null, new: null }; } // Erstelle neue UI-Base (wird später importiert) console.log("Erstelle neue ui-base:", baseName); var newBase = { id: RED.nodes.id(), type: "ui-base", name: baseName, path: "/dashboard", appIcon: "http://localhost:1880/LowCode_Icon.ico", includeClientData: true, acceptsClientConfig: [ "ui-control", "ui-notification", "ui-template", "ui-dynamic-form", "ui-dynamic-table", "ui-text", "ui-table" ], showPathInSidebar: false, headerContent: "none", navigationStyle: "default", titleBarStyle: "default", showReconnectNotification: false, notificationDisplayTime: 5, showDisconnectNotification: false, allowInstall: true }; return { existing: null, new: newBase }; } /** * Findet oder bereitet ein UI-Theme Config Node vor * @returns {{existing: object|null, new: object|null}} Existierendes oder neues Theme Config */ function findOrPrepareUiTheme() { var themeName = "ProcessCube-Darkmode"; // Suche nach existierendem Theme var existingTheme = null; RED.nodes.eachConfig(function(node) { if (node.type === "ui-theme" && node.name === themeName) { existingTheme = node; } }); // Falls gefunden, verwende das existierende Theme if (existingTheme) { console.log("Verwende existierendes ui-theme:", themeName); return { existing: existingTheme, new: null }; } // Prüfe ob ui-theme Node-Type existiert var uiThemeType = RED.nodes.getType("ui-theme"); if (!uiThemeType) { console.warn("ui-theme Node-Type nicht gefunden. Theme kann nicht erstellt werden."); return { existing: null, new: null }; } // Erstelle neues UI-Theme (wird später importiert) console.log("Erstelle neues ui-theme:", themeName); var newTheme = { id: RED.nodes.id(), type: "ui-theme", name: themeName, colors: { surface: '#edeaea', primary: '#f2a602', bgPage: '#e5e3e4', groupBg: '#edeaea', groupOutline: '#cacaca' }, sizes: { density: 'compact', pagePadding: '6px', groupGap: '2px', groupBorderRadius: '6px', widgetGap: '2px' } }; return { existing: null, new: newTheme }; } /** * Findet oder bereitet eine UI-Page Config Node vor * @param {string} pageName - Name der Page * @param {string} uiBaseId - UI-Base Config Node ID * @param {string} uiThemeId - UI-Theme Config Node ID * @returns {{existing: object|null, new: object|null}} Existierende oder neue Page Config */ function findOrPrepareUiPageConfig(pageName, uiBaseId, uiThemeId) { if (!pageName) { return { existing: null, new: null }; } // Suche nach existierenden ui-page Config Nodes var existingUiPageConfig = null; RED.nodes.eachConfig(function(node) { if (node.type === "ui-page" && node.name === pageName) { existingUiPageConfig = node; } }); // Falls gefunden, verwende die existierende Config if (existingUiPageConfig) { console.log("Verwende existierende ui-page Config:", pageName); return { existing: existingUiPageConfig, new: null }; } // Prüfe ob ui-page Node-Type existiert var uiPageType = RED.nodes.getType("ui-page"); if (!uiPageType) { console.warn("ui-page Node-Type nicht gefunden. Config Node kann nicht erstellt werden."); return { existing: null, new: null }; } // Erstelle neue ui-page Config Node (wird später importiert) console.log("Erstelle neue ui-page Config:", pageName); // Erstelle URL-sicheren Path aus Page-Namen var urlSafePath = pageName .toLowerCase() .replace(/ä/g, 'ae') .replace(/ö/g, 'oe') .replace(/ü/g, 'ue') .replace(/ß/g, 'ss') .replace(/[^a-z0-9-]/g, '-') // Alle anderen Zeichen durch Bindestrich ersetzen .replace(/-+/g, '-') // Mehrere Bindestriche durch einen ersetzen .replace(/^-|-$/g, ''); // Bindestriche am Anfang/Ende entfernen var newUiPageConfig = { id: RED.nodes.id(), type: "ui-page", name: pageName, ui: uiBaseId, // Verknüpfe mit UI-Base falls vorhanden path: "/" + urlSafePath, // URL-sicherer Path layout: "grid", // Grid Layout theme: uiThemeId // Verknüpfe mit Theme falls vorhanden }; return { existing: null, new: newUiPageConfig }; } /** * Berechnet die Y-Position für eine neue Gruppe im Flow * @param {object} targetFlow - Der Ziel-Flow * @param {boolean} shouldCreateFlow - Flag ob ein neuer Flow erstellt wird * @returns {number} Y-Position für die neue Gruppe */ function calculateGroupYPositionForCustomForm(targetFlow, shouldCreateFlow) { var yPosition = 50; if (targetFlow && !shouldCreateFlow) { var maxY = 0; var groupHeight = 0; RED.nodes.eachGroup(function(group) { if (group.z === targetFlow.id) { if (group.y > maxY) { maxY = group.y; groupHeight = group.h || 0; } } }); yPosition = maxY > 0 ? maxY + groupHeight + 20 : 50; console.log("Berechnete Y-Position für neue Custom Form Gruppe:", yPosition, "MaxY war:", maxY); } return yPosition; } /** * Erstellt die Nodes für ein Custom Form * @param {string} customForm - Custom Form Name * @param {object} engineConfig - Engine-Konfiguration * @param {string} uiPageId - UI-Page Config Node ID * @param {number} yPosition - Y-Position für die Nodes * @returns {{nodes: object[], group: object}} Erstellte Nodes und Gruppe */ function createCustomFormNodes(customForm, engineConfig, uiPageId, yPosition) { // User Task Input Node var userTaskInput = { id: RED.nodes.id(), type: "usertask-input", name: "Aufgabe laden", x: 426, y: yPosition + 50, engine: engineConfig.id }; // UI Page Navigation Node var pageNavigation = { id: RED.nodes.id(), type: "ui-page-navigation", name: "Visit: " + customForm, x: 126, y: yPosition + 50, page: uiPageId || customForm, // Verwende Config Node ID falls vorhanden, sonst Namen event: "$pageview", // Event für Navigation wires: [[userTaskInput.id]] }; // Group var group = { id: RED.nodes.id(), type: "group", name: `Custom Form: ${customForm}`, x: 34, y: yPosition, nodes: [pageNavigation.id, userTaskInput.id], style: { "label": true } }; return { nodes: [pageNavigation, userTaskInput], group: group }; } /** * Setzt die Flow-Zugehörigkeit (z-Property) für alle Nodes * @param {object[]} nodes - Array von Nodes * @param {object} group - Gruppen-Objekt * @param {string} flowId - Flow-ID */ function assignNodesToFlowForCustomForm(nodes, group, flowId) { if (!flowId) { return; } group.z = flowId; nodes.forEach(function(node) { node.z = flowId; }); } /** * Erstellt einen neuen Flow * @param {string} flowName - Name des neuen Flows * @returns {object} Flow-Objekt */ function createNewFlowForCustomForm(flowName) { return { id: RED.nodes.id(), type: "tab", label: flowName, disabled: false, info: "" }; } /** * Erstellt einen neuen Flow und zeigt ihn an * @param {object} flowObject - Das Flow-Objekt * @returns {string} Die ID des erstellten Flows */ function createAndShowFlowForCustomForm(flowObject) { // Importiere nur den Flow RED.view.importNodes([flowObject], { addFlow: true, touchImport: false, applyNodeDefaults: true }); // Wechsle zum neuen Flow RED.workspaces.show(flowObject.id); return flowObject.id; } /** * Importiert Nodes und Config Nodes in einen existierenden Flow * @param {object[]} nodes - Zu importierende Nodes * @param {object} group - Gruppe * @param {object[]} configNodes - Zu importierende Config Nodes (ui-base, ui-theme, ui-page) */ function importNodesIntoFlowForCustomForm(nodes, group, configNodes) { var nodesToImport = [group].concat(nodes); // Füge Config Nodes hinzu falls vorhanden if (configNodes && configNodes.length > 0) { nodesToImport = configNodes.concat(nodesToImport); } var importOptions = { addFlow: false, touchImport: true, applyNodeDefaults: true }; RED.view.importNodes(nodesToImport, importOptions); // Markiere Workspace als dirty, um Deploy-Button zu aktivieren RED.nodes.dirty(true); } /** * Fokussiert einen existierenden Custom Form Node * @param {string} customForm - Custom Form Name */ function focusExistingCustomForm(customForm) { console.log("Custom Form bereits vorhanden, fokussiere:", customForm); // Suche zuerst nach existierender UI-Page Config mit diesem Namen var existingUiPage = null; RED.nodes.eachConfig(function(node) { if (node.type === "ui-page" && node.name === customForm) { existingUiPage = node; } }); // Suche nach ui-page-navigation Nodes var pageNavigationNodes = RED.nodes.filterNodes({type: "ui-page-navigation"}); var matchingNode = pageNavigationNodes.find(function(node) { // Prüfe sowohl auf UI-Page Config ID als auch auf String-Namen if (existingUiPage && node.page === existingUiPage.id) { return true; } // Fallback: Prüfe auf String-Namen return node.page === customForm; }); if (!matchingNode) { console.warn("Custom Form Node nicht gefunden:", customForm); return; } // Wechsle zum richtigen Tab/Flow if (matchingNode.z) { RED.workspaces.show(matchingNode.z); } // Warte kurz, damit der Workspace gewechselt werden kann setTimeout(function() { try { // Selektiere und zeige den Node RED.view.select({nodes: [matchingNode]}); RED.view.reveal(matchingNode.id, false); // Hole Chart-Element var chart = $("#red-ui-workspace-chart"); if (!chart.length) { console.error("Chart-Element nicht gefunden"); return; } var chartWidth = chart.width(); var chartHeight = chart.height(); var scale = RED.view.scale(); // Berechne neue Scroll-Position um Node zu zentrieren var newScrollLeft = (matchingNode.x * scale) - (chartWidth / 2); var newScrollTop = (matchingNode.y * scale) - (chartHeight / 2); // Scrolle zur berechneten Position chart.animate({ scrollLeft: newScrollLeft, scrollTop: newScrollTop }, 400); RED.notify( "Custom Form '" + customForm + "' existiert bereits und wurde fokussiert.", "warning", false, 3000 ); } catch(e) { console.error("Fehler beim Fokussieren:", e); RED.notify("Fehler beim Fokussieren des Nodes", "error", false, 3000); } }, 300); } var addCustomForm = function(params) { 'use strict'; console.log("Füge neues Custom Form hinzu:", params.customForm); // Prüfe, ob Custom Form mit diesem Namen bereits existiert var existingNode = findExistingCustomFormByName(params.customForm); if (existingNode) { focusExistingCustomForm(params.customForm); return; } // Flow-Management var flowInfo = findOrPrepareFlowForCustomForm(params.flowName); var targetFlow = flowInfo.flow; var shouldCreateFlow = flowInfo.shouldCreate; // Engine-Konfiguration laden var engineConfig = getEngineConfigForCustomForm(); // UI-Base finden oder vorbereiten var uiBaseResult = findOrPrepareUiBase(); var uiBaseId = uiBaseResult.existing ? uiBaseResult.existing.id : (uiBaseResult.new ? uiBaseResult.new.id : null); // UI-Theme finden oder vorbereiten var uiThemeResult = findOrPrepareUiTheme(); var uiThemeId = uiThemeResult.existing ? uiThemeResult.existing.id : (uiThemeResult.new ? uiThemeResult.new.id : null); // UI-Page Config Node finden oder vorbereiten (mit Base und Theme verknüpft) var uiPageResult = findOrPrepareUiPageConfig(params.customForm, uiBaseId, uiThemeId); var uiPageId = uiPageResult.existing ? uiPageResult.existing.id : (uiPageResult.new ? uiPageResult.new.id : null); // Sammle Config Nodes, die importiert werden müssen var configNodesToImport = []; if (uiBaseResult.new) configNodesToImport.push(uiBaseResult.new); if (uiThemeResult.new) configNodesToImport.push(uiThemeResult.new); if (uiPageResult.new) configNodesToImport.push(uiPageResult.new); // Y-Position berechnen var yPosition = calculateGroupYPositionForCustomForm(targetFlow, shouldCreateFlow); // Nodes und Gruppe erstellen var customFormSetup = createCustomFormNodes( params.customForm, engineConfig, uiPageId, // UI-Page ID yPosition ); var nodes = customFormSetup.nodes; var group = customFormSetup.group; // Flow-Zuweisung und Import if (shouldCreateFlow) { // 1. Neuen Flow erstellen var newFlow = createNewFlowForCustomForm(params.flowName); // 2. Flow anzeigen (leerer Flow) createAndShowFlowForCustomForm(newFlow); // 3. Nodes dem Flow zuweisen assignNodesToFlowForCustomForm(nodes, group, newFlow.id); // 4. Nodes in den jetzt sichtbaren Flow importieren setTimeout(function() { importNodesIntoFlowForCustomForm(nodes, group, configNodesToImport); }, 100); // Kurze Verzögerung, damit der Flow-Wechsel abgeschlossen ist } else { // In existierenden Flow einfügen var flowId = targetFlow ? targetFlow.id : null; // Zum Ziel-Flow wechseln falls vorhanden if (targetFlow) { RED.workspaces.show(targetFlow.id); } assignNodesToFlowForCustomForm(nodes, group, flowId); setTimeout(function() { importNodesIntoFlowForCustomForm(nodes, group, configNodesToImport); }, 100); } };