processcube.apptemplate
Version:
A AppTemplate for a App build with and for the ProcessCube Plattform
576 lines (489 loc) • 17.9 kB
JavaScript
/**
* 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);
}
};