mirror of
https://github.com/node-red/node-red.git
synced 2025-12-27 07:31:07 +01:00
Merge branch 'dev' into @feature/issue-5029
This commit is contained in:
@@ -111,6 +111,7 @@
|
||||
"userSettings": "User Settings",
|
||||
"nodes": "Nodes",
|
||||
"displayStatus": "Show node status",
|
||||
"displayInfoIcon": "Show node information icon",
|
||||
"displayConfig": "Configuration nodes",
|
||||
"import": "Import",
|
||||
"importExample": "Import example flow",
|
||||
@@ -264,6 +265,8 @@
|
||||
"download": "Download",
|
||||
"importUnrecognised": "Imported unrecognised type:",
|
||||
"importUnrecognised_plural": "Imported unrecognised types:",
|
||||
"importWithModuleInfo": "Required dependencies missing",
|
||||
"importWithModuleInfoDesc": "These nodes are not currently installed in your palette and are required for the imported flow:",
|
||||
"importDuplicate": "Imported duplicate node:",
|
||||
"importDuplicate_plural": "Imported duplicate nodes:",
|
||||
"nodesExported": "Nodes exported to clipboard",
|
||||
|
||||
@@ -1495,7 +1495,12 @@ RED.nodes = (function() {
|
||||
/**
|
||||
* Converts the current node selection to an exportable JSON Object
|
||||
**/
|
||||
function createExportableNodeSet(set, exportedIds, exportedSubflows, exportedConfigNodes) {
|
||||
function createExportableNodeSet(set, {
|
||||
exportedIds,
|
||||
exportedSubflows,
|
||||
exportedConfigNodes,
|
||||
includeModuleConfig = false
|
||||
} = {}) {
|
||||
var nns = [];
|
||||
|
||||
exportedIds = exportedIds || {};
|
||||
@@ -1529,7 +1534,7 @@ RED.nodes = (function() {
|
||||
subflowSet = subflowSet.concat(RED.nodes.junctions(subflowId))
|
||||
subflowSet = subflowSet.concat(RED.nodes.groups(subflowId))
|
||||
|
||||
var exportableSubflow = createExportableNodeSet(subflowSet, exportedIds, exportedSubflows, exportedConfigNodes);
|
||||
var exportableSubflow = createExportableNodeSet(subflowSet, { exportedIds, exportedSubflows, exportedConfigNodes });
|
||||
nns = exportableSubflow.concat(nns);
|
||||
}
|
||||
}
|
||||
@@ -1564,19 +1569,23 @@ RED.nodes = (function() {
|
||||
}
|
||||
nns.push(convertedNode);
|
||||
if (node.type === "group") {
|
||||
nns = nns.concat(createExportableNodeSet(node.nodes, exportedIds, exportedSubflows, exportedConfigNodes));
|
||||
nns = nns.concat(createExportableNodeSet(node.nodes, { exportedIds, exportedSubflows, exportedConfigNodes }));
|
||||
}
|
||||
} else {
|
||||
var convertedSubflow = convertSubflow(node, { credentials: false });
|
||||
nns.push(convertedSubflow);
|
||||
}
|
||||
}
|
||||
if (includeModuleConfig) {
|
||||
updateGlobalConfigModuleList(nns)
|
||||
}
|
||||
return nns;
|
||||
}
|
||||
|
||||
// Create the Flow JSON for the current configuration
|
||||
// opts.credentials (whether to include (known) credentials) - default: true
|
||||
// opts.dimensions (whether to include node dimensions) - default: false
|
||||
// opts.includeModuleConfig (whether to include modules) - default: false
|
||||
function createCompleteNodeSet(opts) {
|
||||
var nns = [];
|
||||
var i;
|
||||
@@ -1608,6 +1617,9 @@ RED.nodes = (function() {
|
||||
RED.nodes.eachNode(function(n) {
|
||||
nns.push(convertNode(n, opts));
|
||||
})
|
||||
if (opts?.includeModuleConfig) {
|
||||
updateGlobalConfigModuleList(nns);
|
||||
}
|
||||
return nns;
|
||||
}
|
||||
|
||||
@@ -1835,6 +1847,7 @@ RED.nodes = (function() {
|
||||
* - id:import - import as-is
|
||||
* - id:copy - import with new id
|
||||
* - id:replace - import over the top of existing
|
||||
* - modules: map of module:version - hints for unknown nodes
|
||||
*/
|
||||
function importNodes(newNodesObj,options) { // createNewIds,createMissingWorkspace) {
|
||||
const defOpts = { generateIds: false, addFlow: false, markChanged: false, reimport: false, importMap: {} }
|
||||
@@ -1970,12 +1983,58 @@ RED.nodes = (function() {
|
||||
|
||||
}
|
||||
if (!isInitialLoad && unknownTypes.length > 0) {
|
||||
var typeList = $("<ul>");
|
||||
unknownTypes.forEach(function(t) {
|
||||
$("<li>").text(t).appendTo(typeList);
|
||||
})
|
||||
typeList = typeList[0].outerHTML;
|
||||
RED.notify("<p>"+RED._("clipboard.importUnrecognised",{count:unknownTypes.length})+"</p>"+typeList,"error",false,10000);
|
||||
const notificationOptions = {
|
||||
type: "error",
|
||||
fixed: false,
|
||||
timeout: 10000,
|
||||
}
|
||||
let unknownNotification
|
||||
let missingModules = []
|
||||
if (options.modules) {
|
||||
missingModules = Object.keys(options.modules).filter(module => !RED.nodes.registry.getModule(module))
|
||||
}
|
||||
if (missingModules.length > 0) {
|
||||
notificationOptions.fixed = true
|
||||
delete notificationOptions.timeout
|
||||
// We have module hint list from imported global-config
|
||||
// Provide option to install missing modules
|
||||
notificationOptions.buttons = [
|
||||
{
|
||||
text: "Manage dependencies",
|
||||
class:"primary",
|
||||
click: function(e) {
|
||||
unknownNotification.close();
|
||||
|
||||
RED.actions.invoke('core:manage-palette', {
|
||||
view: 'install',
|
||||
filter: '"' + missingModules.join('", "') + '"'
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
let moduleList = $("<ul>");
|
||||
missingModules.forEach(function(t) {
|
||||
$("<li>").text(t).appendTo(moduleList);
|
||||
})
|
||||
moduleList = moduleList[0].outerHTML;
|
||||
unknownNotification = RED.notify(
|
||||
"<p>"+RED._("clipboard.importWithModuleInfo")+"</p>"+
|
||||
"<p>"+RED._("clipboard.importWithModuleInfoDesc")+"</p>"+
|
||||
moduleList,
|
||||
notificationOptions
|
||||
);
|
||||
} else {
|
||||
var typeList = $("<ul>");
|
||||
unknownTypes.forEach(function(t) {
|
||||
$("<li>").text(t).appendTo(typeList);
|
||||
})
|
||||
typeList = typeList[0].outerHTML;
|
||||
|
||||
unknownNotification = RED.notify(
|
||||
"<p>"+RED._("clipboard.importUnrecognised",{count:unknownTypes.length})+"</p>"+typeList,
|
||||
notificationOptions
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var activeWorkspace = RED.workspaces.active();
|
||||
@@ -2403,6 +2462,9 @@ RED.nodes = (function() {
|
||||
delete node.z;
|
||||
}
|
||||
}
|
||||
const unknownTypeDef = RED.nodes.getType('unknown')
|
||||
node._def.oneditprepare = unknownTypeDef.oneditprepare
|
||||
|
||||
var orig = {};
|
||||
for (var p in n) {
|
||||
if (n.hasOwnProperty(p) && p!="x" && p!="y" && p!="z" && p!="id" && p!="wires") {
|
||||
@@ -2412,6 +2474,10 @@ RED.nodes = (function() {
|
||||
node._orig = orig;
|
||||
node.name = n.type;
|
||||
node.type = "unknown";
|
||||
if (options.modules) {
|
||||
// We have a module hint list. Attach to the unknown node so we can reference it later
|
||||
node.modules = Object.keys(options.modules)
|
||||
}
|
||||
}
|
||||
if (node._def.category != "config") {
|
||||
if (n.hasOwnProperty('inputs') && node._def.defaults.hasOwnProperty("inputs")) {
|
||||
@@ -3098,7 +3164,33 @@ RED.nodes = (function() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getModuleListForNodes(nodes) {
|
||||
const modules = {}
|
||||
nodes.forEach(n => {
|
||||
const nodeSet = RED.nodes.registry.getNodeSetForType(n.type)
|
||||
if (nodeSet) {
|
||||
modules[nodeSet.module] = nodeSet.version
|
||||
}
|
||||
})
|
||||
return modules
|
||||
}
|
||||
function updateGlobalConfigModuleList(nodes) {
|
||||
const modules = getModuleListForNodes(nodes)
|
||||
delete modules['node-red']
|
||||
const hasModules = (Object.keys(modules).length > 0)
|
||||
let globalConfigNode = nodes.find(n => n.type === 'global-config')
|
||||
if (!globalConfigNode && hasModules) {
|
||||
globalConfigNode = {
|
||||
id: RED.nodes.id(),
|
||||
type: 'global-config',
|
||||
env: [],
|
||||
modules
|
||||
}
|
||||
nodes.push(globalConfigNode)
|
||||
} else if (globalConfigNode) {
|
||||
globalConfigNode.modules = modules
|
||||
}
|
||||
}
|
||||
return {
|
||||
init: function() {
|
||||
RED.events.on("registry:node-type-added",function(type) {
|
||||
|
||||
@@ -730,7 +730,7 @@ RED.clipboard = (function() {
|
||||
nodes = RED.view.selection().nodes||[];
|
||||
}
|
||||
// Don't include the subflow meta-port nodes in the exported selection
|
||||
nodes = RED.nodes.createExportableNodeSet(nodes.filter(function(n) { return n.type !== 'subflow'}));
|
||||
nodes = RED.nodes.createExportableNodeSet(nodes.filter(function(n) { return n.type !== 'subflow'}), { includeModuleConfig: true });
|
||||
} else if (type === 'flow') {
|
||||
var activeWorkspace = RED.workspaces.active();
|
||||
nodes = RED.nodes.groups(activeWorkspace);
|
||||
@@ -745,9 +745,9 @@ RED.clipboard = (function() {
|
||||
});
|
||||
var parentNode = RED.nodes.workspace(activeWorkspace)||RED.nodes.subflow(activeWorkspace);
|
||||
nodes.unshift(parentNode);
|
||||
nodes = RED.nodes.createExportableNodeSet(nodes);
|
||||
nodes = RED.nodes.createExportableNodeSet(nodes, { includeModuleConfig: true });
|
||||
} else if (type === 'full') {
|
||||
nodes = RED.nodes.createCompleteNodeSet({ credentials: false });
|
||||
nodes = RED.nodes.createCompleteNodeSet({ credentials: false, includeModuleConfig: true });
|
||||
}
|
||||
if (nodes !== null) {
|
||||
if (format === "red-ui-clipboard-dialog-export-fmt-full") {
|
||||
@@ -848,7 +848,7 @@ RED.clipboard = (function() {
|
||||
children: []
|
||||
};
|
||||
treeSubflows.push(subflows[node.id])
|
||||
} else {
|
||||
} else if (node.type !== 'global-config') {
|
||||
nodes.push(node);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -33,8 +33,7 @@ RED.envVar = (function() {
|
||||
id: RED.nodes.id(),
|
||||
type: "global-config",
|
||||
env: [],
|
||||
name: "global-config",
|
||||
label: "",
|
||||
modules: {},
|
||||
hasUsers: false,
|
||||
users: [],
|
||||
credentials: cred,
|
||||
|
||||
@@ -34,7 +34,7 @@ RED.palette.editor = (function() {
|
||||
let packageList;
|
||||
|
||||
// Nodes tab - filter
|
||||
let activeFilter = "";
|
||||
let activeFilterTerms = [];
|
||||
// Nodes tab - search input
|
||||
let filterInput;
|
||||
// Install tab - search input
|
||||
@@ -421,7 +421,19 @@ RED.palette.editor = (function() {
|
||||
}
|
||||
|
||||
function filterChange(val) {
|
||||
activeFilter = val.toLowerCase();
|
||||
const activeFilter = val.toLowerCase();
|
||||
activeFilterTerms = []
|
||||
activeFilter.split(',').forEach(term => {
|
||||
term = term.trim()
|
||||
if (term) {
|
||||
const isExact = term[0] === '"' && term[term.length-1] === '"'
|
||||
activeFilterTerms.push({
|
||||
exact: isExact,
|
||||
term: isExact ? term.substring(1,term.length-1) : term
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
var visible = nodeList.editableList('filter');
|
||||
var size = nodeList.editableList('length');
|
||||
if (val === "") {
|
||||
@@ -583,7 +595,9 @@ RED.palette.editor = (function() {
|
||||
packageList.editableList('addItem',{count:loadedList.length})
|
||||
return;
|
||||
}
|
||||
// sort the filtered modules
|
||||
filteredList.sort(activeSort);
|
||||
// render the items in the package list
|
||||
for (var i=0;i<Math.min(10,filteredList.length);i++) {
|
||||
packageList.editableList('addItem',filteredList[i]);
|
||||
}
|
||||
@@ -595,15 +609,26 @@ RED.palette.editor = (function() {
|
||||
}
|
||||
}
|
||||
function sortModulesRelevance(A,B) {
|
||||
var currentFilter = searchInput.searchBox('value').trim();
|
||||
if (currentFilter === "") {
|
||||
return sortModulesAZ(A,B);
|
||||
const defaultSort = sortModulesDownloads(A,B);
|
||||
const currentFilter = searchInput.searchBox('value').trim();
|
||||
if (defaultSort === 0) {
|
||||
// same number of downloads
|
||||
if (currentFilter === "") {
|
||||
return sortModulesAZ(A,B);
|
||||
}
|
||||
var i = A.info.index.indexOf(currentFilter) - B.info.index.indexOf(currentFilter);
|
||||
if (i === 0) {
|
||||
return sortModulesAZ(A,B);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
var i = A.info.index.indexOf(currentFilter) - B.info.index.indexOf(currentFilter);
|
||||
if (i === 0) {
|
||||
return sortModulesAZ(A,B);
|
||||
}
|
||||
return i;
|
||||
return defaultSort;
|
||||
}
|
||||
function sortModulesDownloads(A,B) {
|
||||
// check A has the info.downloads property - if not exising, prioritise it (likely a custom user catalogue)
|
||||
const a = A.info && typeof A.info.downloads !== 'undefined' ? A.info.downloads.week : Number.MAX_SAFE_INTEGER;
|
||||
const b = B.info && typeof B.info.downloads !== 'undefined' ? B.info.downloads.week : Number.MAX_SAFE_INTEGER;
|
||||
return b - a;
|
||||
}
|
||||
function sortModulesAZ(A,B) {
|
||||
return A.info.id.localeCompare(B.info.id);
|
||||
@@ -656,6 +681,9 @@ RED.palette.editor = (function() {
|
||||
title: RED._("palette.editor.palette"),
|
||||
get: getSettingsPane,
|
||||
close: function() {
|
||||
if (filterInput) {
|
||||
filterInput.searchBox('value',"");
|
||||
}
|
||||
settingsPane.detach();
|
||||
},
|
||||
focus: function() {
|
||||
@@ -671,8 +699,20 @@ RED.palette.editor = (function() {
|
||||
// Load catalogues
|
||||
refreshCatalogues()
|
||||
|
||||
RED.actions.add("core:manage-palette",function() {
|
||||
RED.actions.add("core:manage-palette", function(opts) {
|
||||
RED.userSettings.show('palette');
|
||||
if (opts) {
|
||||
if (opts.view) {
|
||||
editorTabs.activateTab(opts.view);
|
||||
}
|
||||
if (opts.filter) {
|
||||
if (opts.view === 'install') {
|
||||
searchInput.searchBox('value', opts.filter)
|
||||
} else if (opts.view === 'nodes') {
|
||||
filterInput.searchBox('value', opts.filter)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
RED.events.on('registry:module-updated', function(ns) {
|
||||
@@ -845,11 +885,26 @@ RED.palette.editor = (function() {
|
||||
return A.info.name.localeCompare(B.info.name);
|
||||
},
|
||||
filter: function(data) {
|
||||
if (activeFilter === "" ) {
|
||||
if (activeFilterTerms.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (activeFilter==="")||(data.index.indexOf(activeFilter) > -1);
|
||||
for (let i = 0; i < activeFilterTerms.length; i++) {
|
||||
const searchTerm = activeFilterTerms[i]
|
||||
const location = data.index.indexOf(searchTerm.term)
|
||||
if (
|
||||
(
|
||||
searchTerm.exact &&
|
||||
(
|
||||
location === 0 && (
|
||||
data.index.length === searchTerm.term.length ||
|
||||
data.index[searchTerm.term.length] === ','
|
||||
)
|
||||
)
|
||||
) ||
|
||||
(!searchTerm.exact && location > -1)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
},
|
||||
addItem: function(container,i,object) {
|
||||
var entry = object.info;
|
||||
@@ -1030,8 +1085,35 @@ RED.palette.editor = (function() {
|
||||
change: function() {
|
||||
var searchTerm = $(this).val().trim().toLowerCase();
|
||||
if (searchTerm.length > 0 || loadedList.length < 20) {
|
||||
const searchTerms = []
|
||||
searchTerm.split(',').forEach(term => {
|
||||
term = term.trim()
|
||||
if (term) {
|
||||
const isExact = term[0] === '"' && term[term.length-1] === '"'
|
||||
searchTerms.push({
|
||||
exact: isExact,
|
||||
term: isExact ? term.substring(1,term.length-1) : term
|
||||
})
|
||||
}
|
||||
})
|
||||
filteredList = loadedList.filter(function(m) {
|
||||
return (m.index.indexOf(searchTerm) > -1);
|
||||
for (let i = 0; i < searchTerms.length; i++) {
|
||||
const location = m.index.indexOf(searchTerms[i].term)
|
||||
if (
|
||||
(
|
||||
searchTerms[i].exact &&
|
||||
(
|
||||
location === 0 && (
|
||||
m.index.length === searchTerms[i].term.length ||
|
||||
m.index[searchTerms[i].term.length] === ','
|
||||
)
|
||||
)
|
||||
) ||
|
||||
(!searchTerms[i].exact && location > -1)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}).map(function(f) { return {info:f}});
|
||||
refreshFilteredItems();
|
||||
searchInput.searchBox('count',filteredList.length+" / "+loadedList.length);
|
||||
@@ -1120,8 +1202,11 @@ RED.palette.editor = (function() {
|
||||
var descRow = $('<div class="red-ui-palette-module-meta"></div>').appendTo(headerRow);
|
||||
$('<div>',{class:"red-ui-palette-module-description"}).text(entry.description).appendTo(descRow);
|
||||
var metaRow = $('<div class="red-ui-palette-module-meta"></div>').appendTo(headerRow);
|
||||
$('<span class="red-ui-palette-module-version"><i class="fa fa-tag"></i> '+entry.version+'</span>').appendTo(metaRow);
|
||||
$('<span class="red-ui-palette-module-updated"><i class="fa fa-calendar"></i> '+formatUpdatedAt(entry.updated_at)+'</span>').appendTo(metaRow);
|
||||
$('<span class="red-ui-palette-module-version" title="Latest Version"><i class="fa fa-tag"></i> '+entry.version+'</span>').appendTo(metaRow);
|
||||
$('<span class="red-ui-palette-module-updated" title="Last Updated"><i class="fa fa-calendar"></i> '+formatUpdatedAt(entry.updated_at)+'</span>').appendTo(metaRow);
|
||||
if (entry.downloads?.week !== undefined) {
|
||||
$('<span class="red-ui-palette-module-downloads" title="Downloads - Last 7 Days"><i class="fa fa-download"></i> '+(new Intl.NumberFormat().format(entry.downloads.week))+'</span>').appendTo(metaRow);
|
||||
}
|
||||
if (loadedCatalogs.length > 1) {
|
||||
$('<span class="red-ui-palette-module-updated"><i class="fa fa-cubes"></i>' + (entry.catalog.name || entry.catalog.url) + '</span>').appendTo(metaRow);
|
||||
}
|
||||
|
||||
@@ -140,6 +140,7 @@ RED.userSettings = (function() {
|
||||
title: "menu.label.nodes",
|
||||
options: [
|
||||
{setting:"view-node-status",oldSetting:"menu-menu-item-status",label:"menu.label.displayStatus",default: true, toggle:true,onchange:"core:toggle-status"},
|
||||
{setting:"view-node-info-icon", label:"menu.label.displayInfoIcon", default: true, toggle:true,onchange:"core:toggle-node-info-icon"},
|
||||
{setting:"view-node-show-label",label:"menu.label.showNodeLabelDefault",default: true, toggle:true}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -128,10 +128,25 @@ RED.utils = (function() {
|
||||
function formatString(str) {
|
||||
return str.replace(/\r?\n/g,"↵").replace(/\t/g,"→");
|
||||
}
|
||||
|
||||
function sanitize(m) {
|
||||
return m.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncates a string to a specified maximum length, adding ellipsis
|
||||
* if truncated.
|
||||
*
|
||||
* @param {string} str The string to be truncated.
|
||||
* @param {number} [maxLength = 120] The maximum length of the truncated
|
||||
* string. Default `120`.
|
||||
* @returns {string} The truncated string with ellipsis if it exceeds the
|
||||
* maximum length.
|
||||
*/
|
||||
function truncateString(str, maxLength = 120) {
|
||||
return str.length > maxLength ? str.slice(0, maxLength) + "..." : str;
|
||||
}
|
||||
|
||||
function buildMessageSummaryValue(value) {
|
||||
var result;
|
||||
if (Array.isArray(value)) {
|
||||
@@ -379,6 +394,9 @@ RED.utils = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
// Max string length before truncating
|
||||
const MAX_STRING_LENGTH = 80;
|
||||
|
||||
/**
|
||||
* Create a DOM element representation of obj - as used by Debug sidebar etc
|
||||
*
|
||||
@@ -474,7 +492,7 @@ RED.utils = (function() {
|
||||
} else if (typeHint === "internal" || (obj.__enc__ && obj.type === 'internal')) {
|
||||
e = $('<span class="red-ui-debug-msg-type-meta red-ui-debug-msg-object-header"></span>').text("[internal]").appendTo(entryObj);
|
||||
} else if (typeof obj === 'string') {
|
||||
if (/[\t\n\r]/.test(obj)) {
|
||||
if (/[\t\n\r]/.test(obj) || obj.length > MAX_STRING_LENGTH) {
|
||||
element.addClass('collapsed');
|
||||
$('<i class="fa fa-caret-right red-ui-debug-msg-object-handle"></i> ').prependTo(header);
|
||||
makeExpandable(header, function() {
|
||||
@@ -483,7 +501,7 @@ RED.utils = (function() {
|
||||
$('<pre class="red-ui-debug-msg-type-string"></pre>').text(obj).appendTo(row);
|
||||
},function(state) {if (ontoggle) { ontoggle(path,state);}}, checkExpanded(strippedKey, expandPaths, { expandLeafNodes }));
|
||||
}
|
||||
e = $('<span class="red-ui-debug-msg-type-string red-ui-debug-msg-object-header"></span>').html('"'+formatString(sanitize(obj))+'"').appendTo(entryObj);
|
||||
e = $('<span class="red-ui-debug-msg-type-string red-ui-debug-msg-object-header"></span>').html('"'+formatString(sanitize(truncateString(obj, MAX_STRING_LENGTH)))+'"').appendTo(entryObj);
|
||||
if (/^#[0-9a-f]{6}$/i.test(obj)) {
|
||||
$('<span class="red-ui-debug-msg-type-string-swatch"></span>').css('backgroundColor',obj).appendTo(e);
|
||||
}
|
||||
@@ -1513,6 +1531,7 @@ RED.utils = (function() {
|
||||
parseContextKey: parseContextKey,
|
||||
createIconElement: createIconElement,
|
||||
sanitize: sanitize,
|
||||
truncateString: truncateString,
|
||||
renderMarkdown: renderMarkdown,
|
||||
createNodeIcon: createNodeIcon,
|
||||
getDarkerColor: getDarkerColor,
|
||||
|
||||
@@ -82,6 +82,7 @@ RED.view = (function() {
|
||||
var slicePathLast = null;
|
||||
var ghostNode = null;
|
||||
var showStatus = false;
|
||||
let showNodeInfo = true;
|
||||
var lastClickNode = null;
|
||||
var dblClickPrimed = null;
|
||||
var clickTime = 0;
|
||||
@@ -860,11 +861,51 @@ RED.view = (function() {
|
||||
toggleStatus(state);
|
||||
}
|
||||
});
|
||||
RED.actions.add("core:toggle-node-info-icon", function (state) {
|
||||
if (state === undefined) {
|
||||
RED.userSettings.toggle("view-node-info-icon");
|
||||
} else {
|
||||
toggleNodeInfo(state)
|
||||
}
|
||||
})
|
||||
|
||||
RED.view.annotations.init();
|
||||
RED.view.navigator.init();
|
||||
RED.view.tools.init();
|
||||
|
||||
RED.view.annotations.register("red-ui-flow-node-docs",{
|
||||
type: "badge",
|
||||
class: "red-ui-flow-node-docs",
|
||||
element: function(node) {
|
||||
|
||||
const docsBadge = document.createElementNS("http://www.w3.org/2000/svg","g")
|
||||
|
||||
const pageOutline = document.createElementNS("http://www.w3.org/2000/svg","rect");
|
||||
pageOutline.setAttribute("x",0);
|
||||
pageOutline.setAttribute("y",0);
|
||||
pageOutline.setAttribute("rx",2);
|
||||
pageOutline.setAttribute("width",7);
|
||||
pageOutline.setAttribute("height",10);
|
||||
docsBadge.appendChild(pageOutline)
|
||||
|
||||
const pageLines = document.createElementNS("http://www.w3.org/2000/svg","path");
|
||||
pageLines.setAttribute("d", "M 7 3 h -3 v -3 M 2 8 h 3 M 2 6 h 3 M 2 4 h 2")
|
||||
docsBadge.appendChild(pageLines)
|
||||
|
||||
$(docsBadge).on('click', function (evt) {
|
||||
if (node.type === "subflow") {
|
||||
RED.editor.editSubflow(activeSubflow, 'editor-tab-description');
|
||||
} else if (node.type === "group") {
|
||||
RED.editor.editGroup(node, 'editor-tab-description');
|
||||
} else {
|
||||
RED.editor.edit(node, 'editor-tab-description');
|
||||
}
|
||||
})
|
||||
|
||||
return docsBadge;
|
||||
},
|
||||
show: function(n) { return showNodeInfo && !!n.info }
|
||||
})
|
||||
|
||||
RED.view.annotations.register("red-ui-flow-node-changed",{
|
||||
type: "badge",
|
||||
@@ -5695,37 +5736,62 @@ RED.view = (function() {
|
||||
activeSubflowChanged = activeSubflow.changed;
|
||||
}
|
||||
var filteredNodesToImport = nodesToImport;
|
||||
var globalConfig = null;
|
||||
var gconf = null;
|
||||
var importedGlobalConfig = null;
|
||||
var existingGlobalConfig = null;
|
||||
|
||||
RED.nodes.eachConfig(function (conf) {
|
||||
if (conf.type === "global-config") {
|
||||
gconf = conf;
|
||||
existingGlobalConfig = conf;
|
||||
}
|
||||
});
|
||||
if (gconf) {
|
||||
if (existingGlobalConfig) {
|
||||
filteredNodesToImport = nodesToImport.filter(function (n) {
|
||||
return (n.type !== "global-config");
|
||||
if (n.type === "global-config") {
|
||||
importedGlobalConfig = n
|
||||
// Do not import it if one exists
|
||||
// The properties of this one will be merged after import
|
||||
return false
|
||||
}
|
||||
return true
|
||||
});
|
||||
globalConfig = nodesToImport.find(function (n) {
|
||||
return (n.type === "global-config");
|
||||
} else {
|
||||
filteredNodesToImport = nodesToImport.filter(function (n) {
|
||||
if (n.type === "global-config") {
|
||||
importedGlobalConfig = n
|
||||
if (n.env && n.env.length) {
|
||||
// No existing global-config
|
||||
// Contains env and maybe modules, so import it
|
||||
return true
|
||||
}
|
||||
// Contains modules only - do not import it
|
||||
return false
|
||||
}
|
||||
return true
|
||||
});
|
||||
}
|
||||
var result = RED.nodes.import(filteredNodesToImport,{
|
||||
|
||||
const modules = importedGlobalConfig?.modules || {}
|
||||
// Ensure do not import modules - since it can contain it
|
||||
if (importedGlobalConfig?.modules) {
|
||||
delete importedGlobalConfig.modules;
|
||||
}
|
||||
|
||||
const importResult = RED.nodes.import(filteredNodesToImport,{
|
||||
generateIds: options.generateIds,
|
||||
addFlow: addNewFlow,
|
||||
importMap: options.importMap,
|
||||
markChanged: true
|
||||
markChanged: true,
|
||||
modules: modules
|
||||
});
|
||||
if (result) {
|
||||
var new_nodes = result.nodes;
|
||||
var new_links = result.links;
|
||||
var new_groups = result.groups;
|
||||
var new_junctions = result.junctions;
|
||||
var new_workspaces = result.workspaces;
|
||||
var new_subflows = result.subflows;
|
||||
var removedNodes = result.removedNodes;
|
||||
var new_default_workspace = result.missingWorkspace;
|
||||
if (importResult) {
|
||||
var new_nodes = importResult.nodes;
|
||||
var new_links = importResult.links;
|
||||
var new_groups = importResult.groups;
|
||||
var new_junctions = importResult.junctions;
|
||||
var new_workspaces = importResult.workspaces;
|
||||
var new_subflows = importResult.subflows;
|
||||
var removedNodes = importResult.removedNodes;
|
||||
var new_default_workspace = importResult.missingWorkspace;
|
||||
if (addNewFlow && new_default_workspace) {
|
||||
RED.workspaces.show(new_default_workspace.id);
|
||||
}
|
||||
@@ -5839,38 +5905,38 @@ RED.view = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
if (globalConfig) {
|
||||
if (importedGlobalConfig && existingGlobalConfig) {
|
||||
// merge global env to existing global-config
|
||||
var env0 = gconf.env;
|
||||
var env1 = globalConfig.env;
|
||||
var newEnv = Array.from(env0);
|
||||
var existingEnv = existingGlobalConfig.env || [];
|
||||
var importedEnv = importedGlobalConfig.env || []
|
||||
var newEnv = Array.from(existingEnv);
|
||||
var changed = false;
|
||||
|
||||
env1.forEach(function (item1) {
|
||||
var index = newEnv.findIndex(function (item0) {
|
||||
return (item0.name === item1.name);
|
||||
importedEnv.forEach(function (importedItem) {
|
||||
var index = newEnv.findIndex(function (existingItem) {
|
||||
return (existingItem.name === importedItem.name);
|
||||
});
|
||||
if (index >= 0) {
|
||||
var item0 = newEnv[index];
|
||||
if ((item0.type !== item1.type) ||
|
||||
(item0.value !== item1.value)) {
|
||||
newEnv[index] = item1;
|
||||
const existingItem = newEnv[index];
|
||||
if ((existingItem.type !== importedItem.type) ||
|
||||
(existingItem.value !== importedItem.value)) {
|
||||
newEnv[index] = importedItem;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
newEnv.push(item1);
|
||||
newEnv.push(importedItem);
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
if(changed) {
|
||||
gconf.env = newEnv;
|
||||
if (changed) {
|
||||
existingGlobalConfig.env = newEnv;
|
||||
var replaceEvent = {
|
||||
t: "edit",
|
||||
node: gconf,
|
||||
node: existingGlobalConfig,
|
||||
changed: true,
|
||||
changes: {
|
||||
env: env0
|
||||
env: existingEnv
|
||||
}
|
||||
};
|
||||
historyEvent = {
|
||||
@@ -6007,6 +6073,11 @@ RED.view = (function() {
|
||||
//TODO: subscribe/unsubscribe here
|
||||
redraw();
|
||||
}
|
||||
function toggleNodeInfo(s) {
|
||||
showNodeInfo = s
|
||||
RED.nodes.eachNode(function(n) { n.dirty = true;});
|
||||
redraw();
|
||||
}
|
||||
function setSelectedNodeState(isDisabled) {
|
||||
if (mouse_mode === RED.state.SELECTING_NODE) {
|
||||
return;
|
||||
|
||||
@@ -244,6 +244,18 @@ svg:not(.red-ui-workspace-lasso-active) {
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
.red-ui-flow-node-docs {
|
||||
stroke-width: 1px;
|
||||
stroke-linejoin: round;
|
||||
stroke-linecap: round;
|
||||
stroke: var(--red-ui-node-border);
|
||||
fill: none;
|
||||
cursor: pointer;
|
||||
rect {
|
||||
fill: white;
|
||||
}
|
||||
}
|
||||
|
||||
g.red-ui-flow-node-selected {
|
||||
.red-ui-workspace-select-mode & {
|
||||
opacity: 1;
|
||||
|
||||
@@ -119,6 +119,9 @@
|
||||
.red-ui-palette-module-updated {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.red-ui-palette-module-downloads {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.red-ui-palette-module-link {
|
||||
margin-left: 5px;
|
||||
}
|
||||
@@ -230,7 +233,7 @@
|
||||
white-space: nowrap;
|
||||
@include mixins.enable-selection;
|
||||
}
|
||||
.red-ui-palette-module-version, .red-ui-palette-module-updated, .red-ui-palette-module-link {
|
||||
.red-ui-palette-module-version, .red-ui-palette-module-updated, .red-ui-palette-module-link, .red-ui-palette-module-downloads {
|
||||
font-style:italic;
|
||||
font-size: 0.8em;
|
||||
@include mixins.enable-selection;
|
||||
|
||||
@@ -11,12 +11,13 @@
|
||||
RED.nodes.registerType('global-config',{
|
||||
category: 'config',
|
||||
defaults: {
|
||||
name: { value: "" },
|
||||
env: { value: [] },
|
||||
modules: { value: {} }
|
||||
},
|
||||
credentials: {
|
||||
map: { type: "map" }
|
||||
},
|
||||
label: 'global-config',
|
||||
oneditprepare: function() {
|
||||
$('#node-input-edit-env-var').on('click', function(evt) {
|
||||
RED.actions.invoke('core:show-user-settings', 'envvar')
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
|
||||
<script type="text/html" data-template-name="unknown">
|
||||
<div class="form-tips"><span data-i18n="[html]unknown.tip"></span></div>
|
||||
<div class="form-tips">
|
||||
<span data-i18n="[html]unknown.tip"></span>
|
||||
<p id="unknown-module-known">
|
||||
<button id="unknown-manage-dependencies" class="red-ui-button">Manage dependencies</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('unknown',{
|
||||
RED.nodes.registerType('unknown', {
|
||||
category: 'unknown',
|
||||
color:"#fff0f0",
|
||||
color:"#fff000",
|
||||
defaults: {
|
||||
name: {value:""}
|
||||
name: {value:""},
|
||||
modules: { value: [] }
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
@@ -18,6 +26,20 @@
|
||||
},
|
||||
labelStyle: function() {
|
||||
return "node_label_unknown";
|
||||
},
|
||||
oneditprepare: function () {
|
||||
const node = this
|
||||
if (this.modules && this.modules.length > 0) {
|
||||
$('#unknown-manage-dependencies').on('click', function () {
|
||||
RED.actions.invoke('core:cancel-edit-tray')
|
||||
RED.actions.invoke('core:manage-palette', {
|
||||
view: 'install',
|
||||
filter: '"' + node.modules.join('", "') + '"'
|
||||
})
|
||||
})
|
||||
} else {
|
||||
$('#unknown-module-known').hide()
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user