mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Merge branch 'dev' into pr_4387
This commit is contained in:
@@ -819,7 +819,7 @@ RED.clipboard = (function() {
|
||||
flow.forEach(function(node) {
|
||||
if (node.type === "tab") {
|
||||
flows[node.id] = {
|
||||
element: getFlowLabel(node,false),
|
||||
element: getFlowLabel(node),
|
||||
deferBuild: type !== "flow",
|
||||
expanded: type === "flow",
|
||||
children: []
|
||||
@@ -1000,7 +1000,6 @@ RED.clipboard = (function() {
|
||||
try {
|
||||
RED.view.importNodes(newNodes, importOptions);
|
||||
} catch(error) {
|
||||
console.log(error.importConfig)
|
||||
// Thrown for import_conflict
|
||||
confirmImport(error.importConfig, newNodes, importOptions);
|
||||
}
|
||||
@@ -1170,9 +1169,9 @@ RED.clipboard = (function() {
|
||||
function getNodeElement(n, isConflicted, isSelected, parent) {
|
||||
var element;
|
||||
if (n.type === "tab") {
|
||||
element = getFlowLabel(n, isSelected);
|
||||
element = getFlowLabel(n, isConflicted);
|
||||
} else {
|
||||
element = getNodeLabel(n, isConflicted, isSelected);
|
||||
element = getNodeLabel(n, isConflicted, isSelected, parent);
|
||||
}
|
||||
var controls = $('<div>',{class:"red-ui-clipboard-dialog-import-conflicts-controls"}).appendTo(element);
|
||||
controls.on("click", function(evt) { evt.stopPropagation(); });
|
||||
@@ -1222,14 +1221,14 @@ RED.clipboard = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
function getFlowLabel(n) {
|
||||
function getFlowLabel(n, isConflicted) {
|
||||
n = JSON.parse(JSON.stringify(n));
|
||||
n._def = RED.nodes.getType(n.type) || {};
|
||||
if (n._def) {
|
||||
n._ = n._def._;
|
||||
}
|
||||
|
||||
var div = $('<div>',{class:"red-ui-info-outline-item red-ui-info-outline-item-flow"});
|
||||
var div = $('<div>',{class:"red-ui-info-outline-item red-ui-info-outline-item-flow red-ui-node-list-item"});
|
||||
var contentDiv = $('<div>',{class:"red-ui-search-result-description red-ui-info-outline-item-label"}).appendTo(div);
|
||||
var label = (typeof n === "string")? n : n.label;
|
||||
var newlineIndex = label.indexOf("\\n");
|
||||
@@ -1237,11 +1236,17 @@ RED.clipboard = (function() {
|
||||
label = label.substring(0,newlineIndex)+"...";
|
||||
}
|
||||
contentDiv.text(label);
|
||||
|
||||
if (!!isConflicted) {
|
||||
const conflictIcon = $('<span style="padding: 0 10px;"><i class="fa fa-exclamation-circle"></span>').appendTo(div)
|
||||
RED.popover.tooltip(conflictIcon, RED._('clipboard.import.alreadyExists'))
|
||||
}
|
||||
|
||||
// A conflicted flow should not be imported by default.
|
||||
return div;
|
||||
}
|
||||
|
||||
function getNodeLabel(n, isConflicted) {
|
||||
function getNodeLabel(n, isConflicted, isSelected, parent) {
|
||||
n = JSON.parse(JSON.stringify(n));
|
||||
n._def = RED.nodes.getType(n.type) || {};
|
||||
if (n._def) {
|
||||
@@ -1249,6 +1254,11 @@ RED.clipboard = (function() {
|
||||
}
|
||||
var div = $('<div>',{class:"red-ui-node-list-item"});
|
||||
RED.utils.createNodeIcon(n,true).appendTo(div);
|
||||
|
||||
if (!parent && !!isConflicted) {
|
||||
const conflictIcon = $('<span style="padding: 0 10px;"><i class="fa fa-exclamation-circle"></span>').appendTo(div)
|
||||
RED.popover.tooltip(conflictIcon, RED._('clipboard.import.alreadyExists'))
|
||||
}
|
||||
return div;
|
||||
}
|
||||
|
||||
|
@@ -174,12 +174,24 @@
|
||||
this.uiContainer.width(m[1]);
|
||||
}
|
||||
if (this.options.sortable) {
|
||||
var isCanceled = false; // Flag to track if an item has been canceled from being dropped into a different list
|
||||
var noDrop = false; // Flag to track if an item is being dragged into a different list
|
||||
var handle = (typeof this.options.sortable === 'string')?
|
||||
this.options.sortable :
|
||||
".red-ui-editableList-item-handle";
|
||||
var sortOptions = {
|
||||
axis: "y",
|
||||
update: function( event, ui ) {
|
||||
// dont trigger update if the item is being canceled
|
||||
const targetList = $(event.target);
|
||||
const draggedItem = ui.item;
|
||||
const draggedItemParent = draggedItem.parent();
|
||||
if (!targetList.is(draggedItemParent) && draggedItem.hasClass("red-ui-editableList-item-constrained")) {
|
||||
noDrop = true;
|
||||
}
|
||||
if (isCanceled || noDrop) {
|
||||
return;
|
||||
}
|
||||
if (that.options.sortItems) {
|
||||
that.options.sortItems(that.items());
|
||||
}
|
||||
@@ -189,8 +201,32 @@
|
||||
tolerance: "pointer",
|
||||
forcePlaceholderSize:true,
|
||||
placeholder: "red-ui-editabelList-item-placeholder",
|
||||
start: function(e, ui){
|
||||
ui.placeholder.height(ui.item.height()-4);
|
||||
start: function (event, ui) {
|
||||
isCanceled = false;
|
||||
ui.placeholder.height(ui.item.height() - 4);
|
||||
ui.item.css('cursor', 'grabbing'); // TODO: this doesn't seem to work, use a class instead?
|
||||
},
|
||||
stop: function (event, ui) {
|
||||
ui.item.css('cursor', 'auto');
|
||||
},
|
||||
receive: function (event, ui) {
|
||||
if (ui.item.hasClass("red-ui-editableList-item-constrained")) {
|
||||
isCanceled = true;
|
||||
$(ui.sender).sortable('cancel');
|
||||
}
|
||||
},
|
||||
over: function (event, ui) {
|
||||
// if the dragged item is constrained, prevent it from being dropped into a different list
|
||||
const targetList = $(event.target);
|
||||
const draggedItem = ui.item;
|
||||
const draggedItemParent = draggedItem.parent();
|
||||
if (!targetList.is(draggedItemParent) && draggedItem.hasClass("red-ui-editableList-item-constrained")) {
|
||||
noDrop = true;
|
||||
draggedItem.css('cursor', 'no-drop'); // TODO: this doesn't seem to work, use a class instead?
|
||||
} else {
|
||||
noDrop = false;
|
||||
draggedItem.css('cursor', 'grabbing'); // TODO: this doesn't seem to work, use a class instead?
|
||||
}
|
||||
}
|
||||
};
|
||||
if (this.options.connectWith) {
|
||||
|
@@ -54,25 +54,26 @@
|
||||
return icon;
|
||||
}
|
||||
|
||||
var autoComplete = function(options) {
|
||||
function getMatch(value, searchValue) {
|
||||
const idx = value.toLowerCase().indexOf(searchValue.toLowerCase());
|
||||
const len = idx > -1 ? searchValue.length : 0;
|
||||
return {
|
||||
index: idx,
|
||||
found: idx > -1,
|
||||
pre: value.substring(0,idx),
|
||||
match: value.substring(idx,idx+len),
|
||||
post: value.substring(idx+len),
|
||||
}
|
||||
}
|
||||
function generateSpans(match) {
|
||||
const els = [];
|
||||
if(match.pre) { els.push($('<span/>').text(match.pre)); }
|
||||
if(match.match) { els.push($('<span/>',{style:"font-weight: bold; color: var(--red-ui-text-color-link);"}).text(match.match)); }
|
||||
if(match.post) { els.push($('<span/>').text(match.post)); }
|
||||
return els;
|
||||
function getMatch(value, searchValue) {
|
||||
const idx = value.toLowerCase().indexOf(searchValue.toLowerCase());
|
||||
const len = idx > -1 ? searchValue.length : 0;
|
||||
return {
|
||||
index: idx,
|
||||
found: idx > -1,
|
||||
pre: value.substring(0,idx),
|
||||
match: value.substring(idx,idx+len),
|
||||
post: value.substring(idx+len),
|
||||
}
|
||||
}
|
||||
function generateSpans(match) {
|
||||
const els = [];
|
||||
if(match.pre) { els.push($('<span/>').text(match.pre)); }
|
||||
if(match.match) { els.push($('<span/>',{style:"font-weight: bold; color: var(--red-ui-text-color-link);"}).text(match.match)); }
|
||||
if(match.post) { els.push($('<span/>').text(match.post)); }
|
||||
return els;
|
||||
}
|
||||
|
||||
const msgAutoComplete = function(options) {
|
||||
return function(val) {
|
||||
var matches = [];
|
||||
options.forEach(opt => {
|
||||
@@ -102,6 +103,197 @@
|
||||
}
|
||||
}
|
||||
|
||||
function getEnvVars (obj, envVars = {}) {
|
||||
contextKnownKeys.env = contextKnownKeys.env || {}
|
||||
if (contextKnownKeys.env[obj.id]) {
|
||||
return contextKnownKeys.env[obj.id]
|
||||
}
|
||||
let parent
|
||||
if (obj.type === 'tab' || obj.type === 'subflow') {
|
||||
RED.nodes.eachConfig(function (conf) {
|
||||
if (conf.type === "global-config") {
|
||||
parent = conf;
|
||||
}
|
||||
})
|
||||
} else if (obj.g) {
|
||||
parent = RED.nodes.group(obj.g)
|
||||
} else if (obj.z) {
|
||||
parent = RED.nodes.workspace(obj.z) || RED.nodes.subflow(obj.z)
|
||||
}
|
||||
if (parent) {
|
||||
getEnvVars(parent, envVars)
|
||||
}
|
||||
if (obj.env) {
|
||||
obj.env.forEach(env => {
|
||||
envVars[env.name] = obj
|
||||
})
|
||||
}
|
||||
contextKnownKeys.env[obj.id] = envVars
|
||||
return envVars
|
||||
}
|
||||
|
||||
const envAutoComplete = function (val) {
|
||||
const editStack = RED.editor.getEditStack()
|
||||
if (editStack.length === 0) {
|
||||
done([])
|
||||
return
|
||||
}
|
||||
const editingNode = editStack.pop()
|
||||
if (!editingNode) {
|
||||
return []
|
||||
}
|
||||
const envVarsMap = getEnvVars(editingNode)
|
||||
const envVars = Object.keys(envVarsMap)
|
||||
const matches = []
|
||||
const i = val.lastIndexOf('${')
|
||||
let searchKey = val
|
||||
let isSubkey = false
|
||||
if (i > -1) {
|
||||
if (val.lastIndexOf('}') < i) {
|
||||
searchKey = val.substring(i+2)
|
||||
isSubkey = true
|
||||
}
|
||||
}
|
||||
envVars.forEach(v => {
|
||||
let valMatch = getMatch(v, searchKey);
|
||||
if (valMatch.found) {
|
||||
const optSrc = envVarsMap[v]
|
||||
const element = $('<div>',{style: "display: flex"});
|
||||
const valEl = $('<div/>',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"});
|
||||
valEl.append(generateSpans(valMatch))
|
||||
valEl.appendTo(element)
|
||||
|
||||
if (optSrc) {
|
||||
const optEl = $('<div>').css({ "font-size": "0.8em" });
|
||||
let label
|
||||
if (optSrc.type === 'global-config') {
|
||||
label = RED._('sidebar.context.global')
|
||||
} else if (optSrc.type === 'group') {
|
||||
label = RED.utils.getNodeLabel(optSrc) || (RED._('sidebar.info.group') + ': '+optSrc.id)
|
||||
} else {
|
||||
label = RED.utils.getNodeLabel(optSrc) || optSrc.id
|
||||
}
|
||||
|
||||
optEl.append(generateSpans({ match: label }));
|
||||
optEl.appendTo(element);
|
||||
}
|
||||
matches.push({
|
||||
value: isSubkey ? val + v + '}' : v,
|
||||
label: element,
|
||||
i: valMatch.index
|
||||
});
|
||||
}
|
||||
})
|
||||
matches.sort(function(A,B){return A.i-B.i})
|
||||
return matches
|
||||
}
|
||||
|
||||
let contextKnownKeys = {}
|
||||
let contextCache = {}
|
||||
if (RED.events) {
|
||||
RED.events.on("editor:close", function () {
|
||||
contextCache = {}
|
||||
contextKnownKeys = {}
|
||||
});
|
||||
}
|
||||
|
||||
const contextAutoComplete = function() {
|
||||
const that = this
|
||||
const getContextKeysFromRuntime = function(scope, store, searchKey, done) {
|
||||
contextKnownKeys[scope] = contextKnownKeys[scope] || {}
|
||||
contextKnownKeys[scope][store] = contextKnownKeys[scope][store] || new Set()
|
||||
if (searchKey.length > 0) {
|
||||
try {
|
||||
RED.utils.normalisePropertyExpression(searchKey)
|
||||
} catch (err) {
|
||||
// Not a valid context key, so don't try looking up
|
||||
done()
|
||||
return
|
||||
}
|
||||
}
|
||||
const url = `context/${scope}/${encodeURIComponent(searchKey)}?store=${store}&keysOnly`
|
||||
if (contextCache[url]) {
|
||||
// console.log('CACHED', url)
|
||||
done()
|
||||
} else {
|
||||
// console.log('GET', url)
|
||||
$.getJSON(url, function(data) {
|
||||
// console.log(data)
|
||||
contextCache[url] = true
|
||||
const result = data[store] || {}
|
||||
const keys = result.keys || []
|
||||
const keyPrefix = searchKey + (searchKey.length > 0 ? '.' : '')
|
||||
keys.forEach(key => {
|
||||
if (/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(key)) {
|
||||
contextKnownKeys[scope][store].add(keyPrefix + key)
|
||||
} else {
|
||||
contextKnownKeys[scope][store].add(searchKey + "[\""+key.replace(/"/,"\\\"")+"\"]")
|
||||
}
|
||||
})
|
||||
done()
|
||||
})
|
||||
}
|
||||
}
|
||||
const getContextKeys = function(key, done) {
|
||||
const keyParts = key.split('.')
|
||||
const partialKey = keyParts.pop()
|
||||
let scope = that.propertyType
|
||||
if (scope === 'flow') {
|
||||
// Get the flow id of the node we're editing
|
||||
const editStack = RED.editor.getEditStack()
|
||||
if (editStack.length === 0) {
|
||||
done([])
|
||||
return
|
||||
}
|
||||
const editingNode = editStack.pop()
|
||||
if (editingNode.z) {
|
||||
scope = `${scope}/${editingNode.z}`
|
||||
} else {
|
||||
done([])
|
||||
return
|
||||
}
|
||||
}
|
||||
const store = (contextStoreOptions.length === 1) ? contextStoreOptions[0].value : that.optionValue
|
||||
const searchKey = keyParts.join('.')
|
||||
|
||||
getContextKeysFromRuntime(scope, store, searchKey, function() {
|
||||
if (contextKnownKeys[scope][store].has(key) || key.endsWith(']')) {
|
||||
getContextKeysFromRuntime(scope, store, key, function() {
|
||||
done(contextKnownKeys[scope][store])
|
||||
})
|
||||
}
|
||||
done(contextKnownKeys[scope][store])
|
||||
})
|
||||
}
|
||||
|
||||
return function(val, done) {
|
||||
getContextKeys(val, function (keys) {
|
||||
const matches = []
|
||||
keys.forEach(v => {
|
||||
let optVal = v
|
||||
let valMatch = getMatch(optVal, val);
|
||||
if (!valMatch.found && val.length > 0 && val.endsWith('.')) {
|
||||
// Search key ends in '.' - but doesn't match. Check again
|
||||
// with [" at the end instead so we match bracket notation
|
||||
valMatch = getMatch(optVal, val.substring(0, val.length - 1) + '["')
|
||||
}
|
||||
if (valMatch.found) {
|
||||
const element = $('<div>',{style: "display: flex"});
|
||||
const valEl = $('<div/>',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"});
|
||||
valEl.append(generateSpans(valMatch))
|
||||
valEl.appendTo(element)
|
||||
matches.push({
|
||||
value: optVal,
|
||||
label: element,
|
||||
});
|
||||
}
|
||||
})
|
||||
matches.sort(function(a, b) { return a.value.localeCompare(b.value) });
|
||||
done(matches);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// This is a hand-generated list of completions for the core nodes (based on the node help html).
|
||||
var msgCompletions = [
|
||||
{ value: "payload" },
|
||||
@@ -166,23 +358,27 @@
|
||||
{ value: "_session", source: ["websocket out","tcp out"] },
|
||||
]
|
||||
var allOptions = {
|
||||
msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression, autoComplete: autoComplete(msgCompletions)},
|
||||
msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression, autoComplete: msgAutoComplete(msgCompletions)},
|
||||
flow: {value:"flow",label:"flow.",hasValue:true,
|
||||
options:[],
|
||||
validate:RED.utils.validatePropertyExpression,
|
||||
parse: contextParse,
|
||||
export: contextExport,
|
||||
valueLabel: contextLabel
|
||||
valueLabel: contextLabel,
|
||||
autoComplete: contextAutoComplete
|
||||
},
|
||||
global: {value:"global",label:"global.",hasValue:true,
|
||||
options:[],
|
||||
validate:RED.utils.validatePropertyExpression,
|
||||
parse: contextParse,
|
||||
export: contextExport,
|
||||
valueLabel: contextLabel
|
||||
valueLabel: contextLabel,
|
||||
autoComplete: contextAutoComplete
|
||||
},
|
||||
str: {value:"str",label:"string",icon:"red/images/typedInput/az.svg"},
|
||||
num: {value:"num",label:"number",icon:"red/images/typedInput/09.svg",validate:/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/},
|
||||
num: {value:"num",label:"number",icon:"red/images/typedInput/09.svg",validate: function(v) {
|
||||
return (true === RED.utils.validateTypedProperty(v, "num"));
|
||||
} },
|
||||
bool: {value:"bool",label:"boolean",icon:"red/images/typedInput/bool.svg",options:["true","false"]},
|
||||
json: {
|
||||
value:"json",
|
||||
@@ -212,7 +408,25 @@
|
||||
}
|
||||
},
|
||||
re: {value:"re",label:"regular expression",icon:"red/images/typedInput/re.svg"},
|
||||
date: {value:"date",label:"timestamp",icon:"fa fa-clock-o",hasValue:false},
|
||||
date: {
|
||||
value:"date",
|
||||
label:"timestamp",
|
||||
icon:"fa fa-clock-o",
|
||||
options:[
|
||||
{
|
||||
label: 'milliseconds since epoch',
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
label: 'YYYY-MM-DDTHH:mm:ss.sssZ',
|
||||
value: 'iso'
|
||||
},
|
||||
{
|
||||
label: 'JavaScript Date Object',
|
||||
value: 'object'
|
||||
}
|
||||
]
|
||||
},
|
||||
jsonata: {
|
||||
value: "jsonata",
|
||||
label: "expression",
|
||||
@@ -249,7 +463,8 @@
|
||||
env: {
|
||||
value: "env",
|
||||
label: "env variable",
|
||||
icon: "red/images/typedInput/env.svg"
|
||||
icon: "red/images/typedInput/env.svg",
|
||||
autoComplete: envAutoComplete
|
||||
},
|
||||
node: {
|
||||
value: "node",
|
||||
@@ -381,18 +596,75 @@
|
||||
eyeButton.show();
|
||||
}
|
||||
}
|
||||
},
|
||||
'conf-types': {
|
||||
value: "conf-types",
|
||||
label: "config",
|
||||
icon: "fa fa-cog",
|
||||
// hasValue: false,
|
||||
valueLabel: function (container, value) {
|
||||
// get the selected option (for access to the "name" and "module" properties)
|
||||
const _options = this._optionsCache || this.typeList.find(opt => opt.value === value)?.options || []
|
||||
const selectedOption = _options.find(opt => opt.value === value) || {
|
||||
title: '',
|
||||
name: '',
|
||||
module: ''
|
||||
}
|
||||
container.attr("title", selectedOption.title) // set tooltip to the full path/id of the module/node
|
||||
container.text(selectedOption.name) // apply the "name" of the selected option
|
||||
// set "line-height" such as to make the "name" appear further up, giving room for the "module" to be displayed below the value
|
||||
container.css("line-height", "1.4em")
|
||||
// add the module name in smaller, lighter font below the value
|
||||
$('<div></div>').text(selectedOption.module).css({
|
||||
// "font-family": "var(--red-ui-monospace-font)",
|
||||
color: "var(--red-ui-tertiary-text-color)",
|
||||
"font-size": "0.8em",
|
||||
"line-height": "1em",
|
||||
opacity: 0.8
|
||||
}).appendTo(container);
|
||||
},
|
||||
// hasValue: false,
|
||||
options: function () {
|
||||
if (this._optionsCache) {
|
||||
return this._optionsCache
|
||||
}
|
||||
const configNodes = RED.nodes.registry.getNodeDefinitions({configOnly: true, filter: (def) => def.type !== "global-config"}).map((def) => {
|
||||
// create a container with with 2 rows (row 1 for the name, row 2 for the module name in smaller, lighter font)
|
||||
const container = $('<div style="display: flex; flex-direction: column; justify-content: space-between; row-gap: 1px;">')
|
||||
const row1Name = $('<div>').text(def.type)
|
||||
const row2Module = $('<div style="font-size: 0.8em; color: var(--red-ui-tertiary-text-color);">').text(def.set.module)
|
||||
container.append(row1Name, row2Module)
|
||||
|
||||
return {
|
||||
value: def.type,
|
||||
name: def.type,
|
||||
enabled: def.set.enabled ?? true,
|
||||
local: def.set.local,
|
||||
title: def.set.id, // tooltip e.g. "node-red-contrib-foo/bar"
|
||||
module: def.set.module,
|
||||
icon: container[0].outerHTML.trim(), // the typeInput will interpret this as html text and render it in the anchor
|
||||
}
|
||||
})
|
||||
this._optionsCache = configNodes
|
||||
return configNodes
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// For a type with options, check value is a valid selection
|
||||
// If !opt.multiple, returns the valid option object
|
||||
// if opt.multiple, returns an array of valid option objects
|
||||
// If not valid, returns null;
|
||||
|
||||
function isOptionValueValid(opt, currentVal) {
|
||||
let _options = opt.options
|
||||
if (typeof _options === "function") {
|
||||
_options = _options.call(this)
|
||||
}
|
||||
if (!opt.multiple) {
|
||||
for (var i=0;i<opt.options.length;i++) {
|
||||
op = opt.options[i];
|
||||
for (var i=0;i<_options.length;i++) {
|
||||
op = _options[i];
|
||||
if (typeof op === "string" && op === currentVal) {
|
||||
return {value:currentVal}
|
||||
} else if (op.value === currentVal) {
|
||||
@@ -409,8 +681,8 @@
|
||||
currentValues[v] = true;
|
||||
}
|
||||
});
|
||||
for (var i=0;i<opt.options.length;i++) {
|
||||
op = opt.options[i];
|
||||
for (var i=0;i<_options.length;i++) {
|
||||
op = _options[i];
|
||||
var val = typeof op === "string" ? op : op.value;
|
||||
if (currentValues.hasOwnProperty(val)) {
|
||||
delete currentValues[val];
|
||||
@@ -425,6 +697,7 @@
|
||||
}
|
||||
|
||||
var nlsd = false;
|
||||
let contextStoreOptions;
|
||||
|
||||
$.widget( "nodered.typedInput", {
|
||||
_create: function() {
|
||||
@@ -436,7 +709,7 @@
|
||||
}
|
||||
}
|
||||
var contextStores = RED.settings.context.stores;
|
||||
var contextOptions = contextStores.map(function(store) {
|
||||
contextStoreOptions = contextStores.map(function(store) {
|
||||
return {value:store,label: store, icon:'<i class="red-ui-typedInput-icon fa fa-database"></i>'}
|
||||
}).sort(function(A,B) {
|
||||
if (A.value === RED.settings.context.default) {
|
||||
@@ -447,13 +720,17 @@
|
||||
return A.value.localeCompare(B.value);
|
||||
}
|
||||
})
|
||||
if (contextOptions.length < 2) {
|
||||
if (contextStoreOptions.length < 2) {
|
||||
allOptions.flow.options = [];
|
||||
allOptions.global.options = [];
|
||||
} else {
|
||||
allOptions.flow.options = contextOptions;
|
||||
allOptions.global.options = contextOptions;
|
||||
allOptions.flow.options = contextStoreOptions;
|
||||
allOptions.global.options = contextStoreOptions;
|
||||
}
|
||||
// Translate timestamp options
|
||||
allOptions.date.options.forEach(opt => {
|
||||
opt.label = RED._("typedInput.date.format." + (opt.value || 'timestamp'), {defaultValue: opt.label})
|
||||
})
|
||||
}
|
||||
nlsd = true;
|
||||
var that = this;
|
||||
@@ -542,7 +819,7 @@
|
||||
that.element.trigger('paste',evt);
|
||||
});
|
||||
this.input.on('keydown', function(evt) {
|
||||
if (that.typeMap[that.propertyType].autoComplete) {
|
||||
if (that.typeMap[that.propertyType].autoComplete || that.input.hasClass('red-ui-autoComplete')) {
|
||||
return
|
||||
}
|
||||
if (evt.keyCode >= 37 && evt.keyCode <= 40) {
|
||||
@@ -836,7 +1113,9 @@
|
||||
if (this.optionMenu) {
|
||||
this.optionMenu.remove();
|
||||
}
|
||||
this.menu.remove();
|
||||
if (this.menu) {
|
||||
this.menu.remove();
|
||||
}
|
||||
this.uiSelect.remove();
|
||||
},
|
||||
types: function(types) {
|
||||
@@ -869,7 +1148,7 @@
|
||||
this.menu = this._createMenu(this.typeList,{},function(v) { that.type(v) });
|
||||
if (currentType && !this.typeMap.hasOwnProperty(currentType)) {
|
||||
if (!firstCall) {
|
||||
this.type(this.typeList[0].value);
|
||||
this.type(this.typeList[0]?.value || ""); // permit empty typeList
|
||||
}
|
||||
} else {
|
||||
this.propertyType = null;
|
||||
@@ -906,6 +1185,11 @@
|
||||
var selectedOption = [];
|
||||
var valueToCheck = value;
|
||||
if (opt.options) {
|
||||
let _options = opt.options
|
||||
if (typeof opt.options === "function") {
|
||||
_options = opt.options.call(this)
|
||||
}
|
||||
|
||||
if (opt.hasValue && opt.parse) {
|
||||
var parts = opt.parse(value);
|
||||
if (this.options.debug) { console.log(this.identifier,"new parse",parts) }
|
||||
@@ -919,8 +1203,8 @@
|
||||
checkValues = valueToCheck.split(",");
|
||||
}
|
||||
checkValues.forEach(function(valueToCheck) {
|
||||
for (var i=0;i<opt.options.length;i++) {
|
||||
var op = opt.options[i];
|
||||
for (var i=0;i<_options.length;i++) {
|
||||
var op = _options[i];
|
||||
if (typeof op === "string") {
|
||||
if (op === valueToCheck || op === ""+valueToCheck) {
|
||||
selectedOption.push(that.activeOptions[op]);
|
||||
@@ -955,7 +1239,7 @@
|
||||
},
|
||||
type: function(type) {
|
||||
if (!arguments.length) {
|
||||
return this.propertyType;
|
||||
return this.propertyType || this.options?.default || '';
|
||||
} else {
|
||||
var that = this;
|
||||
if (this.options.debug) { console.log(this.identifier,"----- SET TYPE -----",type) }
|
||||
@@ -965,6 +1249,9 @@
|
||||
// If previousType is !null, then this is a change of the type, rather than the initialisation
|
||||
var previousType = this.typeMap[this.propertyType];
|
||||
previousValue = this.input.val();
|
||||
if (this.input.hasClass('red-ui-autoComplete')) {
|
||||
this.input.autoComplete("destroy");
|
||||
}
|
||||
|
||||
if (previousType && this.typeChanged) {
|
||||
if (this.options.debug) { console.log(this.identifier,"typeChanged",{previousType,previousValue}) }
|
||||
@@ -1011,7 +1298,9 @@
|
||||
this.input.val(this.oldValues.hasOwnProperty("_")?this.oldValues["_"]:(opt.default||""))
|
||||
}
|
||||
if (previousType.autoComplete) {
|
||||
this.input.autoComplete("destroy");
|
||||
if (this.input.hasClass('red-ui-autoComplete')) {
|
||||
this.input.autoComplete("destroy");
|
||||
}
|
||||
}
|
||||
}
|
||||
this.propertyType = type;
|
||||
@@ -1051,6 +1340,10 @@
|
||||
this.optionMenu = null;
|
||||
}
|
||||
if (opt.options) {
|
||||
let _options = opt.options
|
||||
if (typeof _options === "function") {
|
||||
_options = opt.options.call(this);
|
||||
}
|
||||
if (this.optionExpandButton) {
|
||||
this.optionExpandButton.hide();
|
||||
this.optionExpandButton.shown = false;
|
||||
@@ -1067,7 +1360,7 @@
|
||||
this.valueLabelContainer.hide();
|
||||
}
|
||||
this.activeOptions = {};
|
||||
opt.options.forEach(function(o) {
|
||||
_options.forEach(function(o) {
|
||||
if (typeof o === 'string') {
|
||||
that.activeOptions[o] = {label:o,value:o};
|
||||
} else {
|
||||
@@ -1087,7 +1380,7 @@
|
||||
if (validValues) {
|
||||
that._updateOptionSelectLabel(validValues)
|
||||
} else {
|
||||
op = opt.options[0];
|
||||
op = _options[0] || {value:""}; // permit zero options
|
||||
if (typeof op === "string") {
|
||||
this.value(op);
|
||||
that._updateOptionSelectLabel({value:op});
|
||||
@@ -1106,7 +1399,7 @@
|
||||
that._updateOptionSelectLabel(validValues);
|
||||
}
|
||||
} else {
|
||||
var selectedOption = this.optionValue||opt.options[0];
|
||||
var selectedOption = this.optionValue||_options[0];
|
||||
if (opt.parse) {
|
||||
var selectedOptionObj = typeof selectedOption === "string"?{value:selectedOption}:selectedOption
|
||||
var parts = opt.parse(this.input.val(),selectedOptionObj);
|
||||
@@ -1139,8 +1432,18 @@
|
||||
} else {
|
||||
this.optionSelectTrigger.hide();
|
||||
}
|
||||
if (opt.autoComplete) {
|
||||
let searchFunction = opt.autoComplete
|
||||
if (searchFunction.length === 0) {
|
||||
searchFunction = opt.autoComplete.call(this)
|
||||
}
|
||||
this.input.autoComplete({
|
||||
search: searchFunction,
|
||||
minLength: 0
|
||||
})
|
||||
}
|
||||
}
|
||||
this.optionMenu = this._createMenu(opt.options,opt,function(v){
|
||||
this.optionMenu = this._createMenu(_options,opt,function(v){
|
||||
if (!opt.multiple) {
|
||||
that._updateOptionSelectLabel(that.activeOptions[v]);
|
||||
if (!opt.hasValue) {
|
||||
@@ -1181,8 +1484,12 @@
|
||||
this.valueLabelContainer.hide();
|
||||
this.elementDiv.show();
|
||||
if (opt.autoComplete) {
|
||||
let searchFunction = opt.autoComplete
|
||||
if (searchFunction.length === 0) {
|
||||
searchFunction = opt.autoComplete.call(this)
|
||||
}
|
||||
this.input.autoComplete({
|
||||
search: opt.autoComplete,
|
||||
search: searchFunction,
|
||||
minLength: 0
|
||||
})
|
||||
}
|
||||
|
@@ -30,8 +30,26 @@ RED.contextMenu = (function () {
|
||||
const isGroup = hasSelection && selection.nodes.length === 1 && selection.nodes[0].type === 'group'
|
||||
const canEdit = !RED.workspaces.isLocked()
|
||||
const canRemoveFromGroup = hasSelection && !!selection.nodes[0].g
|
||||
const isAllGroups = hasSelection && selection.nodes.filter(n => n.type !== 'group').length === 0
|
||||
const hasGroup = hasSelection && selection.nodes.filter(n => n.type === 'group' ).length > 0
|
||||
let hasGroup, isAllGroups = true, hasDisabledNode, hasEnabledNode, hasLabeledNode, hasUnlabeledNode;
|
||||
if (hasSelection) {
|
||||
selection.nodes.forEach(n => {
|
||||
if (n.type === 'group') {
|
||||
hasGroup = true;
|
||||
} else {
|
||||
isAllGroups = false;
|
||||
}
|
||||
if (n.d) {
|
||||
hasDisabledNode = true;
|
||||
} else {
|
||||
hasEnabledNode = true;
|
||||
}
|
||||
if (n.l === undefined || n.l) {
|
||||
hasLabeledNode = true;
|
||||
} else {
|
||||
hasUnlabeledNode = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
const offset = $("#red-ui-workspace-chart").offset()
|
||||
|
||||
let addX = options.x - offset.left + $("#red-ui-workspace-chart").scrollLeft()
|
||||
@@ -44,7 +62,7 @@ RED.contextMenu = (function () {
|
||||
}
|
||||
|
||||
menuItems.push(
|
||||
{ onselect: 'core:show-action-list', onpostselect: function () { } }
|
||||
{ onselect: 'core:show-action-list', label: RED._("contextMenu.showActionList"), onpostselect: function () { } }
|
||||
)
|
||||
|
||||
const insertOptions = []
|
||||
@@ -55,7 +73,7 @@ RED.contextMenu = (function () {
|
||||
onselect: function () {
|
||||
RED.view.showQuickAddDialog({
|
||||
position: [addX, addY],
|
||||
touchTrigger: true,
|
||||
touchTrigger: 'ontouchstart' in window,
|
||||
splice: isSingleLink ? selection.links[0] : undefined,
|
||||
// spliceMultiple: isMultipleLinks
|
||||
})
|
||||
@@ -108,16 +126,16 @@ RED.contextMenu = (function () {
|
||||
const nodeOptions = []
|
||||
if (!hasMultipleSelection && !isGroup) {
|
||||
nodeOptions.push(
|
||||
{ onselect: 'core:show-node-help' },
|
||||
{ onselect: 'core:show-node-help', label: RED._('menu.label.showNodeHelp') },
|
||||
null
|
||||
)
|
||||
}
|
||||
nodeOptions.push(
|
||||
{ onselect: 'core:enable-selected-nodes' },
|
||||
{ onselect: 'core:disable-selected-nodes' },
|
||||
{ onselect: 'core:enable-selected-nodes', label: RED._('menu.label.enableSelectedNodes'), disabled: !hasDisabledNode },
|
||||
{ onselect: 'core:disable-selected-nodes', label: RED._('menu.label.disableSelectedNodes'), disabled: !hasEnabledNode },
|
||||
null,
|
||||
{ onselect: 'core:show-selected-node-labels' },
|
||||
{ onselect: 'core:hide-selected-node-labels' }
|
||||
{ onselect: 'core:show-selected-node-labels', label: RED._('menu.label.showSelectedNodeLabels'), disabled: !hasUnlabeledNode },
|
||||
{ onselect: 'core:hide-selected-node-labels', label: RED._('menu.label.hideSelectedNodeLabels'), disabled: !hasLabeledNode }
|
||||
)
|
||||
menuItems.push({
|
||||
label: RED._('sidebar.info.node'),
|
||||
@@ -126,8 +144,8 @@ RED.contextMenu = (function () {
|
||||
menuItems.push({
|
||||
label: RED._('sidebar.info.group'),
|
||||
options: [
|
||||
{ onselect: 'core:group-selection' },
|
||||
{ onselect: 'core:ungroup-selection', disabled: !hasGroup },
|
||||
{ onselect: 'core:group-selection', label: RED._("menu.label.groupSelection") },
|
||||
{ onselect: 'core:ungroup-selection', label: RED._("menu.label.ungroupSelection"), disabled: !hasGroup },
|
||||
]
|
||||
})
|
||||
if (hasGroup) {
|
||||
@@ -143,8 +161,8 @@ RED.contextMenu = (function () {
|
||||
}
|
||||
menuItems[menuItems.length - 1].options.push(
|
||||
null,
|
||||
{ onselect: 'core:copy-group-style', disabled: !hasGroup },
|
||||
{ onselect: 'core:paste-group-style', disabled: !hasGroup}
|
||||
{ onselect: 'core:copy-group-style', label: RED._("keyboard.copyGroupStyle"), disabled: !hasGroup },
|
||||
{ onselect: 'core:paste-group-style', label: RED._("keyboard.pasteGroupStyle"), disabled: !hasGroup}
|
||||
)
|
||||
}
|
||||
if (canEdit && hasMultipleSelection) {
|
||||
@@ -168,16 +186,16 @@ RED.contextMenu = (function () {
|
||||
|
||||
menuItems.push(
|
||||
null,
|
||||
{ onselect: 'core:undo', disabled: RED.history.list().length === 0 },
|
||||
{ onselect: 'core:redo', disabled: RED.history.listRedo().length === 0 },
|
||||
{ onselect: 'core:undo', label: RED._("keyboard.undoChange"), disabled: RED.history.list().length === 0 },
|
||||
{ onselect: 'core:redo', label: RED._("keyboard.redoChange"), disabled: RED.history.listRedo().length === 0 },
|
||||
null,
|
||||
{ onselect: 'core:cut-selection-to-internal-clipboard', label: RED._("keyboard.cutNode"), disabled: !canEdit || !hasSelection },
|
||||
{ onselect: 'core:copy-selection-to-internal-clipboard', label: RED._("keyboard.copyNode"), disabled: !hasSelection },
|
||||
{ onselect: 'core:paste-from-internal-clipboard', label: RED._("keyboard.pasteNode"), disabled: !canEdit || !RED.view.clipboard() },
|
||||
{ onselect: 'core:delete-selection', disabled: !canEdit || !canDelete },
|
||||
{ onselect: 'core:delete-selection', label: RED._('keyboard.deleteSelected'), disabled: !canEdit || !canDelete },
|
||||
{ onselect: 'core:delete-selection-and-reconnect', label: RED._('keyboard.deleteReconnect'), disabled: !canEdit || !canDelete },
|
||||
{ onselect: 'core:show-export-dialog', label: RED._("menu.label.export") },
|
||||
{ onselect: 'core:select-all-nodes' },
|
||||
{ onselect: 'core:select-all-nodes', label: RED._("keyboard.selectAll") },
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -989,9 +989,10 @@ RED.diff = (function() {
|
||||
}
|
||||
if (localNode && remoteNode && typeof localNode[d] === "string") {
|
||||
if (/\n/.test(localNode[d]) || /\n/.test(remoteNode[d])) {
|
||||
$('<button class="red-ui-button red-ui-button-small red-ui-diff-text-diff-button"><i class="fa fa-file-o"> <i class="fa fa-caret-left"></i> <i class="fa fa-caret-right"></i> <i class="fa fa-file-o"></i></button>').on("click", function() {
|
||||
var textDiff = $('<button class="red-ui-button red-ui-button-small red-ui-diff-text-diff-button"><i class="fa fa-file-o"> <i class="fa fa-caret-left"></i> <i class="fa fa-caret-right"></i> <i class="fa fa-file-o"></i></button>').on("click", function() {
|
||||
showTextDiff(localNode[d],remoteNode[d]);
|
||||
}).appendTo(propertyNameCell);
|
||||
RED.popover.tooltip(textDiff, RED._("diff.compareChanges"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -115,8 +115,9 @@ RED.editor = (function() {
|
||||
var valid = validateNodeProperty(node, definition, prop, properties[prop]);
|
||||
if ((typeof valid) === "string") {
|
||||
result.push(valid);
|
||||
}
|
||||
else if(!valid) {
|
||||
} else if (Array.isArray(valid)) {
|
||||
result = result.concat(valid)
|
||||
} else if(!valid) {
|
||||
result.push(prop);
|
||||
}
|
||||
}
|
||||
@@ -165,7 +166,7 @@ RED.editor = (function() {
|
||||
// If the validator takes two arguments, it is a 3.x validator that
|
||||
// can return a String to mean 'invalid' and provide a reason
|
||||
if ((definition[property].validate.length === 2) &&
|
||||
((typeof valid) === "string")) {
|
||||
((typeof valid) === "string") || Array.isArray(valid)) {
|
||||
return valid;
|
||||
} else {
|
||||
// Otherwise, a 2.x returns a truth-like/false-like value that
|
||||
@@ -181,6 +182,17 @@ RED.editor = (function() {
|
||||
error: err.message
|
||||
});
|
||||
}
|
||||
} else if (valid) {
|
||||
// If the validator is not provided in node property => Check if the input has a validator
|
||||
if ("category" in node._def) {
|
||||
const isConfig = node._def.category === "config";
|
||||
const prefix = isConfig ? "node-config-input" : "node-input";
|
||||
const input = $("#"+prefix+"-"+property);
|
||||
const isTypedInput = input.length > 0 && input.next(".red-ui-typedInput-container").length > 0;
|
||||
if (isTypedInput) {
|
||||
valid = input.typedInput("validate");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (valid && definition[property].type && RED.nodes.getType(definition[property].type) && !("validate" in definition[property])) {
|
||||
if (!value || value == "_ADD_") {
|
||||
@@ -314,47 +326,78 @@ RED.editor = (function() {
|
||||
|
||||
/**
|
||||
* Create a config-node select box for this property
|
||||
* @param node - the node being edited
|
||||
* @param property - the name of the field
|
||||
* @param type - the type of the config-node
|
||||
* @param {Object} node - the node being edited
|
||||
* @param {String} property - the name of the node property
|
||||
* @param {String} type - the type of the config-node
|
||||
* @param {"node-config-input"|"node-input"|"node-input-subflow-env"} prefix - the prefix to use in the input element ids
|
||||
* @param {Function} [filter] - a function to filter the list of config nodes
|
||||
* @param {Object} [env] - the environment variable object (only used for subflow env vars)
|
||||
*/
|
||||
function prepareConfigNodeSelect(node,property,type,prefix,filter) {
|
||||
var input = $("#"+prefix+"-"+property);
|
||||
if (input.length === 0 ) {
|
||||
function prepareConfigNodeSelect(node, property, type, prefix, filter, env) {
|
||||
let nodeValue
|
||||
if (prefix === 'node-input-subflow-env') {
|
||||
nodeValue = env?.value
|
||||
} else {
|
||||
nodeValue = node[property]
|
||||
}
|
||||
|
||||
const buttonId = `${prefix}-lookup-${property}`
|
||||
const selectId = prefix + '-' + property
|
||||
const input = $(`#${selectId}`);
|
||||
if (input.length === 0) {
|
||||
return;
|
||||
}
|
||||
var newWidth = input.width();
|
||||
var attrStyle = input.attr('style');
|
||||
var m;
|
||||
const attrStyle = input.attr('style');
|
||||
let newWidth;
|
||||
let m;
|
||||
if ((m = /(^|\s|;)width\s*:\s*([^;]+)/i.exec(attrStyle)) !== null) {
|
||||
newWidth = m[2].trim();
|
||||
} else {
|
||||
newWidth = "70%";
|
||||
}
|
||||
var outerWrap = $("<div></div>").css({
|
||||
const outerWrap = $("<div></div>").css({
|
||||
width: newWidth,
|
||||
display:'inline-flex'
|
||||
display: 'inline-flex'
|
||||
});
|
||||
var select = $('<select id="'+prefix+'-'+property+'"></select>').appendTo(outerWrap);
|
||||
const select = $('<select id="' + selectId + '"></select>').appendTo(outerWrap);
|
||||
input.replaceWith(outerWrap);
|
||||
// set the style attr directly - using width() on FF causes a value of 114%...
|
||||
select.css({
|
||||
'flex-grow': 1
|
||||
});
|
||||
updateConfigNodeSelect(property,type,node[property],prefix,filter);
|
||||
$('<a id="'+prefix+'-lookup-'+property+'" class="red-ui-button"><i class="fa fa-pencil"></i></a>')
|
||||
.css({"margin-left":"10px"})
|
||||
updateConfigNodeSelect(property, type, nodeValue, prefix, filter);
|
||||
const disableButton = function(disabled) {
|
||||
btn.prop( "disabled", !!disabled)
|
||||
btn.toggleClass("disabled", !!disabled)
|
||||
}
|
||||
// create the edit button
|
||||
const btn = $('<a id="' + buttonId + '" class="red-ui-button"><i class="fa fa-pencil"></i></a>')
|
||||
.css({ "margin-left": "10px" })
|
||||
.appendTo(outerWrap);
|
||||
$('#'+prefix+'-lookup-'+property).on("click", function(e) {
|
||||
showEditConfigNodeDialog(property,type,select.find(":selected").val(),prefix,node);
|
||||
|
||||
// add the click handler
|
||||
btn.on("click", function (e) {
|
||||
const selectedOpt = select.find(":selected")
|
||||
if (selectedOpt.data('env')) { return } // don't show the dialog for env vars items (MVP. Future enhancement: lookup the env, if present, show the associated edit dialog)
|
||||
if (btn.prop("disabled")) { return }
|
||||
showEditConfigNodeDialog(property, type, selectedOpt.val(), prefix, node);
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
// dont permit the user to click the button if the selected option is an env var
|
||||
select.on("change", function () {
|
||||
const selectedOpt = select.find(":selected")
|
||||
if (selectedOpt?.data('env')) {
|
||||
disableButton(true)
|
||||
} else {
|
||||
disableButton(false)
|
||||
}
|
||||
});
|
||||
var label = "";
|
||||
var configNode = RED.nodes.node(node[property]);
|
||||
var node_def = RED.nodes.getType(type);
|
||||
var configNode = RED.nodes.node(nodeValue);
|
||||
|
||||
if (configNode) {
|
||||
label = RED.utils.getNodeLabel(configNode,configNode.id);
|
||||
label = RED.utils.getNodeLabel(configNode, configNode.id);
|
||||
}
|
||||
input.val(label);
|
||||
}
|
||||
@@ -756,12 +799,9 @@ RED.editor = (function() {
|
||||
}
|
||||
|
||||
function defaultConfigNodeSort(A,B) {
|
||||
if (A.__label__ < B.__label__) {
|
||||
return -1;
|
||||
} else if (A.__label__ > B.__label__) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
// sort case insensitive so that `[env] node-name` items are at the top and
|
||||
// not mixed inbetween the the lower and upper case items
|
||||
return (A.__label__ || '').localeCompare((B.__label__ || ''), undefined, {sensitivity: 'base'})
|
||||
}
|
||||
|
||||
function updateConfigNodeSelect(name,type,value,prefix,filter) {
|
||||
@@ -776,7 +816,7 @@ RED.editor = (function() {
|
||||
}
|
||||
$("#"+prefix+"-"+name).val(value);
|
||||
} else {
|
||||
|
||||
let inclSubflowEnvvars = false
|
||||
var select = $("#"+prefix+"-"+name);
|
||||
var node_def = RED.nodes.getType(type);
|
||||
select.children().remove();
|
||||
@@ -784,6 +824,7 @@ RED.editor = (function() {
|
||||
var activeWorkspace = RED.nodes.workspace(RED.workspaces.active());
|
||||
if (!activeWorkspace) {
|
||||
activeWorkspace = RED.nodes.subflow(RED.workspaces.active());
|
||||
inclSubflowEnvvars = true
|
||||
}
|
||||
|
||||
var configNodes = [];
|
||||
@@ -799,6 +840,31 @@ RED.editor = (function() {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// as includeSubflowEnvvars is true, this is a subflow.
|
||||
// include any 'conf-types' env vars as a list of avaiable configs
|
||||
// in the config dropdown as `[env] node-name`
|
||||
if (inclSubflowEnvvars && activeWorkspace.env) {
|
||||
const parentEnv = activeWorkspace.env.filter(env => env.ui?.type === 'conf-types' && env.type === type)
|
||||
if (parentEnv && parentEnv.length > 0) {
|
||||
const locale = RED.i18n.lang()
|
||||
for (let i = 0; i < parentEnv.length; i++) {
|
||||
const tenv = parentEnv[i]
|
||||
const ui = tenv.ui || {}
|
||||
const labels = ui.label || {}
|
||||
const labelText = RED.editor.envVarList.lookupLabel(labels, labels["en-US"] || tenv.name, locale)
|
||||
const config = {
|
||||
env: tenv,
|
||||
id: '${' + parentEnv[0].name + '}',
|
||||
type: type,
|
||||
label: labelText,
|
||||
__label__: `[env] ${labelText}`
|
||||
}
|
||||
configNodes.push(config)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var configSortFn = defaultConfigNodeSort;
|
||||
if (typeof node_def.sort == "function") {
|
||||
configSortFn = node_def.sort;
|
||||
@@ -810,7 +876,10 @@ RED.editor = (function() {
|
||||
}
|
||||
|
||||
configNodes.forEach(function(cn) {
|
||||
$('<option value="'+cn.id+'"'+(value==cn.id?" selected":"")+'></option>').text(RED.text.bidi.enforceTextDirectionWithUCC(cn.__label__)).appendTo(select);
|
||||
const option = $('<option value="'+cn.id+'"'+(value==cn.id?" selected":"")+'></option>').text(RED.text.bidi.enforceTextDirectionWithUCC(cn.__label__)).appendTo(select);
|
||||
if (cn.env) {
|
||||
option.data('env', cn.env) // set a data attribute to indicate this is an env var (to inhibit the edit button)
|
||||
}
|
||||
delete cn.__label__;
|
||||
});
|
||||
|
||||
@@ -1219,7 +1288,11 @@ RED.editor = (function() {
|
||||
})
|
||||
|
||||
if (node_def.hasUsers !== false) {
|
||||
$('<span><i class="fa fa-info-circle"></i> <span id="red-ui-editor-config-user-count"></span></span>').css("margin-left", "10px").appendTo(trayFooterLeft);
|
||||
// $('<span><i class="fa fa-info-circle"></i> <span id="red-ui-editor-config-user-count"></span></span>').css("margin-left", "10px").appendTo(trayFooterLeft);
|
||||
$('<button type="button" class="red-ui-button"><i class="fa fa-user"></i><span id="red-ui-editor-config-user-count"></span></button>').on('click', function() {
|
||||
RED.sidebar.info.outliner.search('uses:'+editing_config_node.id)
|
||||
RED.sidebar.info.show()
|
||||
}).appendTo(trayFooterLeft);
|
||||
}
|
||||
trayFooter.append('<span class="red-ui-tray-footer-right"><span id="red-ui-editor-config-scope-warning" data-i18n="[title]editor.errors.scopeChange"><i class="fa fa-warning"></i></span><select id="red-ui-editor-config-scope"></select></span>');
|
||||
|
||||
@@ -1277,7 +1350,8 @@ RED.editor = (function() {
|
||||
});
|
||||
}
|
||||
if (node_def.hasUsers !== false) {
|
||||
$("#red-ui-editor-config-user-count").text(RED._("editor.nodesUse", {count:editing_config_node.users.length})).parent().show();
|
||||
$("#red-ui-editor-config-user-count").text(editing_config_node.users.length).parent().show();
|
||||
RED.popover.tooltip($("#red-ui-editor-config-user-count").parent(), function() { return RED._('editor.nodesUse',{count:editing_config_node.users.length})});
|
||||
}
|
||||
trayBody.i18n();
|
||||
trayFooter.i18n();
|
||||
@@ -1466,9 +1540,16 @@ RED.editor = (function() {
|
||||
}
|
||||
RED.tray.close(function() {
|
||||
var filter = null;
|
||||
if (editContext && typeof editContext._def.defaults[configProperty].filter === 'function') {
|
||||
filter = function(n) {
|
||||
return editContext._def.defaults[configProperty].filter.call(editContext,n);
|
||||
// when editing a config via subflow edit panel, the `configProperty` will not
|
||||
// necessarily be a property of the editContext._def.defaults object
|
||||
// Also, when editing via dashboard sidebar, editContext can be null
|
||||
// so we need to guard both scenarios
|
||||
if (editContext?._def) {
|
||||
const isSubflow = (editContext._def.type === 'subflow' || /subflow:.*/.test(editContext._def.type))
|
||||
if (editContext && !isSubflow && typeof editContext._def.defaults?.[configProperty]?.filter === 'function') {
|
||||
filter = function(n) {
|
||||
return editContext._def.defaults[configProperty].filter.call(editContext,n);
|
||||
}
|
||||
}
|
||||
}
|
||||
updateConfigNodeSelect(configProperty,configType,editing_config_node.id,prefix,filter);
|
||||
@@ -1529,7 +1610,7 @@ RED.editor = (function() {
|
||||
RED.history.push(historyEvent);
|
||||
RED.tray.close(function() {
|
||||
var filter = null;
|
||||
if (editContext && typeof editContext._def.defaults[configProperty].filter === 'function') {
|
||||
if (editContext && typeof editContext._def.defaults[configProperty]?.filter === 'function') {
|
||||
filter = function(n) {
|
||||
return editContext._def.defaults[configProperty].filter.call(editContext,n);
|
||||
}
|
||||
@@ -2070,6 +2151,7 @@ RED.editor = (function() {
|
||||
}
|
||||
},
|
||||
editBuffer: function(options) { showTypeEditor("_buffer", options) },
|
||||
getEditStack: function () { return [...editStack] },
|
||||
buildEditForm: buildEditForm,
|
||||
validateNode: validateNode,
|
||||
updateNodeProperties: updateNodeProperties,
|
||||
@@ -2114,6 +2196,7 @@ RED.editor = (function() {
|
||||
filteredEditPanes[type] = filter
|
||||
}
|
||||
editPanes[type] = definition;
|
||||
}
|
||||
},
|
||||
prepareConfigNodeSelect: prepareConfigNodeSelect,
|
||||
}
|
||||
})();
|
||||
|
@@ -121,7 +121,7 @@
|
||||
var i=0,l=bufferBinValue.length;
|
||||
var c = 0;
|
||||
for(i=0;i<l;i++) {
|
||||
var d = parseInt(bufferBinValue[i]);
|
||||
var d = parseInt(Number(bufferBinValue[i]));
|
||||
if (!isString && (isNaN(d) || d < 0 || d > 255)) {
|
||||
valid = false;
|
||||
break;
|
||||
|
@@ -585,7 +585,7 @@ RED.editor.codeEditor.monaco = (function() {
|
||||
createMonacoCompletionItem("set (flow context)", 'flow.set("${1:name}", ${1:value});','Set a value in flow context',range),
|
||||
createMonacoCompletionItem("get (global context)", 'global.get("${1:name}");','Get a value from global context',range),
|
||||
createMonacoCompletionItem("set (global context)", 'global.set("${1:name}", ${1:value});','Set a value in global context',range),
|
||||
createMonacoCompletionItem("get (env)", 'env.get("${1|NR_NODE_ID,NR_NODE_NAME,NR_NODE_PATH,NR_GROUP_ID,NR_GROUP_NAME,NR_FLOW_ID,NR_FLOW_NAME|}");','Get env variable value',range),
|
||||
createMonacoCompletionItem("get (env)", 'env.get("${1|NR_NODE_ID,NR_NODE_NAME,NR_NODE_PATH,NR_GROUP_ID,NR_GROUP_NAME,NR_FLOW_ID,NR_FLOW_NAME,NR_SUBFLOW_NAME,NR_SUBFLOW_ID,NR_SUBFLOW_PATH|}");','Get env variable value',range),
|
||||
createMonacoCompletionItem("cloneMessage (RED.util)", 'RED.util.cloneMessage(${1:msg});',
|
||||
["```typescript",
|
||||
"RED.util.cloneMessage<T extends registry.NodeMessage>(msg: T): T",
|
||||
@@ -966,12 +966,10 @@ RED.editor.codeEditor.monaco = (function() {
|
||||
|
||||
//Unbind ctrl-Enter (default action is to insert a newline in editor) This permits the shortcut to close the tray.
|
||||
try {
|
||||
ed._standaloneKeybindingService.addDynamicKeybinding(
|
||||
'-editor.action.insertLineAfter', // command ID prefixed by '-'
|
||||
null, // keybinding
|
||||
() => {} // need to pass an empty handler
|
||||
);
|
||||
} catch (error) { }
|
||||
monaco.editor.addKeybindingRule({keybinding: 0, command: "-editor.action.insertLineAfter"});
|
||||
} catch (error) {
|
||||
console.warn(error)
|
||||
}
|
||||
|
||||
ed.nodered = {
|
||||
refreshModuleLibs: refreshModuleLibs //expose this for function node externalModules refresh
|
||||
|
@@ -1,8 +1,9 @@
|
||||
RED.editor.envVarList = (function() {
|
||||
|
||||
var currentLocale = 'en-US';
|
||||
var DEFAULT_ENV_TYPE_LIST = ['str','num','bool','json','bin','env'];
|
||||
var DEFAULT_ENV_TYPE_LIST_INC_CRED = ['str','num','bool','json','bin','env','cred','jsonata'];
|
||||
const DEFAULT_ENV_TYPE_LIST = ['str','num','bool','json','bin','env'];
|
||||
const DEFAULT_ENV_TYPE_LIST_INC_CONFTYPES = ['str','num','bool','json','bin','env','conf-types'];
|
||||
const DEFAULT_ENV_TYPE_LIST_INC_CRED = ['str','num','bool','json','bin','env','cred','jsonata'];
|
||||
|
||||
/**
|
||||
* Create env var edit interface
|
||||
@@ -10,8 +11,8 @@ RED.editor.envVarList = (function() {
|
||||
* @param node - subflow node
|
||||
*/
|
||||
function buildPropertiesList(envContainer, node) {
|
||||
|
||||
var isTemplateNode = (node.type === "subflow");
|
||||
if(RED.editor.envVarList.debug) { console.log('envVarList: buildPropertiesList', envContainer, node) }
|
||||
const isTemplateNode = (node.type === "subflow");
|
||||
|
||||
envContainer
|
||||
.css({
|
||||
@@ -83,7 +84,14 @@ RED.editor.envVarList = (function() {
|
||||
// if `opt.ui` does not exist, then apply defaults. If these
|
||||
// defaults do not change then they will get stripped off
|
||||
// before saving.
|
||||
if (opt.type === 'cred') {
|
||||
if (opt.type === 'conf-types') {
|
||||
opt.ui = opt.ui || {
|
||||
icon: "fa fa-cog",
|
||||
type: "conf-types",
|
||||
opts: {opts:[]}
|
||||
}
|
||||
opt.ui.type = "conf-types";
|
||||
} else if (opt.type === 'cred') {
|
||||
opt.ui = opt.ui || {
|
||||
icon: "",
|
||||
type: "cred"
|
||||
@@ -119,7 +127,7 @@ RED.editor.envVarList = (function() {
|
||||
}
|
||||
});
|
||||
|
||||
buildEnvEditRow(uiRow, opt.ui, nameField, valueField);
|
||||
buildEnvEditRow(uiRow, opt, nameField, valueField);
|
||||
nameField.trigger('change');
|
||||
}
|
||||
},
|
||||
@@ -181,21 +189,23 @@ RED.editor.envVarList = (function() {
|
||||
* @param nameField - name field of env var
|
||||
* @param valueField - value field of env var
|
||||
*/
|
||||
function buildEnvEditRow(container, ui, nameField, valueField) {
|
||||
function buildEnvEditRow(container, opt, nameField, valueField) {
|
||||
const ui = opt.ui
|
||||
if(RED.editor.envVarList.debug) { console.log('envVarList: buildEnvEditRow', container, ui, nameField, valueField) }
|
||||
container.addClass("red-ui-editor-subflow-env-ui-row")
|
||||
var topRow = $('<div></div>').appendTo(container);
|
||||
$('<div></div>').appendTo(topRow);
|
||||
$('<div>').text(RED._("editor.icon")).appendTo(topRow);
|
||||
$('<div>').text(RED._("editor.label")).appendTo(topRow);
|
||||
$('<div>').text(RED._("editor.inputType")).appendTo(topRow);
|
||||
$('<div class="red-env-ui-input-type-col">').text(RED._("editor.inputType")).appendTo(topRow);
|
||||
|
||||
var row = $('<div></div>').appendTo(container);
|
||||
$('<div><i class="red-ui-editableList-item-handle fa fa-bars"></i></div>').appendTo(row);
|
||||
var typeOptions = {
|
||||
'input': {types:DEFAULT_ENV_TYPE_LIST},
|
||||
'select': {opts:[]},
|
||||
'spinner': {},
|
||||
'cred': {}
|
||||
'input': {types:DEFAULT_ENV_TYPE_LIST_INC_CONFTYPES},
|
||||
'select': {opts:[]},
|
||||
'spinner': {},
|
||||
'cred': {}
|
||||
};
|
||||
if (ui.opts) {
|
||||
typeOptions[ui.type] = ui.opts;
|
||||
@@ -260,15 +270,16 @@ RED.editor.envVarList = (function() {
|
||||
labelInput.attr("placeholder",$(this).val())
|
||||
});
|
||||
|
||||
var inputCell = $('<div></div>').appendTo(row);
|
||||
var inputCellInput = $('<input type="text">').css("width","100%").appendTo(inputCell);
|
||||
var inputCell = $('<div class="red-env-ui-input-type-col"></div>').appendTo(row);
|
||||
var uiInputTypeInput = $('<input type="text">').css("width","100%").appendTo(inputCell);
|
||||
if (ui.type === "input") {
|
||||
inputCellInput.val(ui.opts.types.join(","));
|
||||
uiInputTypeInput.val(ui.opts.types.join(","));
|
||||
}
|
||||
var checkbox;
|
||||
var selectBox;
|
||||
|
||||
inputCellInput.typedInput({
|
||||
// the options presented in the UI section for an "input" type selection
|
||||
uiInputTypeInput.typedInput({
|
||||
types: [
|
||||
{
|
||||
value:"input",
|
||||
@@ -429,7 +440,7 @@ RED.editor.envVarList = (function() {
|
||||
}
|
||||
});
|
||||
ui.opts.opts = vals;
|
||||
inputCellInput.typedInput('value',Date.now())
|
||||
uiInputTypeInput.typedInput('value',Date.now())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -496,12 +507,13 @@ RED.editor.envVarList = (function() {
|
||||
} else {
|
||||
delete ui.opts.max;
|
||||
}
|
||||
inputCellInput.typedInput('value',Date.now())
|
||||
uiInputTypeInput.typedInput('value',Date.now())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'conf-types',
|
||||
{
|
||||
value:"none",
|
||||
label:RED._("editor.inputs.none"), icon:"fa fa-times",hasValue:false
|
||||
@@ -519,14 +531,20 @@ RED.editor.envVarList = (function() {
|
||||
// In the case of 'input' type, the typedInput uses the multiple-option
|
||||
// mode. Its value needs to be set to a comma-separately list of the
|
||||
// selected options.
|
||||
inputCellInput.typedInput('value',ui.opts.types.join(","))
|
||||
uiInputTypeInput.typedInput('value',ui.opts.types.join(","))
|
||||
} else if (ui.type === 'conf-types') {
|
||||
// In the case of 'conf-types' type, the typedInput will be populated
|
||||
// with a list of all config nodes types installed.
|
||||
// Restore the value to the last selected type
|
||||
uiInputTypeInput.typedInput('value', opt.type)
|
||||
} else {
|
||||
// No other type cares about `value`, but doing this will
|
||||
// force a refresh of the label now that `ui.opts` has
|
||||
// been updated.
|
||||
inputCellInput.typedInput('value',Date.now())
|
||||
uiInputTypeInput.typedInput('value',Date.now())
|
||||
}
|
||||
|
||||
if(RED.editor.envVarList.debug) { console.log('envVarList: inputCellInput on:typedinputtypechange. ui.type = ' + ui.type) }
|
||||
switch (ui.type) {
|
||||
case 'input':
|
||||
valueField.typedInput('types',ui.opts.types);
|
||||
@@ -544,7 +562,7 @@ RED.editor.envVarList = (function() {
|
||||
valueField.typedInput('types',['cred']);
|
||||
break;
|
||||
default:
|
||||
valueField.typedInput('types',DEFAULT_ENV_TYPE_LIST)
|
||||
valueField.typedInput('types', DEFAULT_ENV_TYPE_LIST);
|
||||
}
|
||||
if (ui.type === 'checkbox') {
|
||||
valueField.typedInput('type','bool');
|
||||
@@ -556,8 +574,46 @@ RED.editor.envVarList = (function() {
|
||||
}
|
||||
|
||||
}).on("change", function(evt,type) {
|
||||
if (ui.type === 'input') {
|
||||
var types = inputCellInput.typedInput('value');
|
||||
const selectedType = $(this).typedInput('type') // the UI typedInput type
|
||||
if(RED.editor.envVarList.debug) { console.log('envVarList: inputCellInput on:change. selectedType = ' + selectedType) }
|
||||
if (selectedType === 'conf-types') {
|
||||
const selectedConfigType = $(this).typedInput('value') || opt.type
|
||||
let activeWorkspace = RED.nodes.workspace(RED.workspaces.active());
|
||||
if (!activeWorkspace) {
|
||||
activeWorkspace = RED.nodes.subflow(RED.workspaces.active());
|
||||
}
|
||||
|
||||
// get a list of all config nodes matching the selectedValue
|
||||
const configNodes = [];
|
||||
RED.nodes.eachConfig(function(config) {
|
||||
if (config.type == selectedConfigType && (!config.z || config.z === activeWorkspace.id)) {
|
||||
const modulePath = config._def?.set?.id || ''
|
||||
let label = RED.utils.getNodeLabel(config, config.id) || config.id;
|
||||
label += config.d ? ' ['+RED._('workspace.disabled')+']' : '';
|
||||
const _config = {
|
||||
_type: selectedConfigType,
|
||||
value: config.id,
|
||||
label: label,
|
||||
title: modulePath ? modulePath + ' - ' + label : label,
|
||||
enabled: config.d !== true,
|
||||
disabled: config.d === true,
|
||||
}
|
||||
configNodes.push(_config);
|
||||
}
|
||||
});
|
||||
const tiTypes = {
|
||||
value: selectedConfigType,
|
||||
label: "config",
|
||||
icon: "fa fa-cog",
|
||||
options: configNodes,
|
||||
}
|
||||
valueField.typedInput('types', [tiTypes]);
|
||||
valueField.typedInput('type', selectedConfigType);
|
||||
valueField.typedInput('value', opt.value);
|
||||
|
||||
|
||||
} else if (ui.type === 'input') {
|
||||
var types = uiInputTypeInput.typedInput('value');
|
||||
ui.opts.types = (types === "") ? ["str"] : types.split(",");
|
||||
valueField.typedInput('types',ui.opts.types);
|
||||
}
|
||||
@@ -569,7 +625,7 @@ RED.editor.envVarList = (function() {
|
||||
})
|
||||
// Set the input to the right type. This will trigger the 'typedinputtypechange'
|
||||
// event handler (just above ^^) to update the value if needed
|
||||
inputCellInput.typedInput('type',ui.type)
|
||||
uiInputTypeInput.typedInput('type',ui.type)
|
||||
}
|
||||
|
||||
function setLocale(l, list) {
|
||||
|
@@ -169,7 +169,7 @@
|
||||
var currentScrollTop = $(".red-ui-editor-type-markdown-panel-preview").scrollTop();
|
||||
$(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue()));
|
||||
$(".red-ui-editor-type-markdown-panel-preview").scrollTop(currentScrollTop);
|
||||
mermaid.init();
|
||||
RED.editor.mermaid.render()
|
||||
},200);
|
||||
})
|
||||
if (options.header) {
|
||||
@@ -178,7 +178,7 @@
|
||||
|
||||
if (value) {
|
||||
$(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue()));
|
||||
mermaid.init();
|
||||
RED.editor.mermaid.render()
|
||||
}
|
||||
panels = RED.panels.create({
|
||||
id:"red-ui-editor-type-markdown-panels",
|
||||
|
54
packages/node_modules/@node-red/editor-client/src/js/ui/editors/mermaid.js
vendored
Normal file
54
packages/node_modules/@node-red/editor-client/src/js/ui/editors/mermaid.js
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
RED.editor.mermaid = (function () {
|
||||
let initializing = false
|
||||
let loaded = false
|
||||
let pendingEvals = []
|
||||
let diagramIds = 0
|
||||
|
||||
function render(selector = '.mermaid') {
|
||||
// $(selector).hide()
|
||||
if (!loaded) {
|
||||
pendingEvals.push(selector)
|
||||
|
||||
if (!initializing) {
|
||||
initializing = true
|
||||
$.getScript(
|
||||
'vendor/mermaid/mermaid.min.js',
|
||||
function (data, stat, jqxhr) {
|
||||
mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
theme: RED.settings.get('mermaid', {}).theme
|
||||
})
|
||||
loaded = true
|
||||
while(pendingEvals.length > 0) {
|
||||
const pending = pendingEvals.shift()
|
||||
render(pending)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
const nodes = document.querySelectorAll(selector)
|
||||
|
||||
nodes.forEach(async node => {
|
||||
if (!node.getAttribute('mermaid-processed')) {
|
||||
const mermaidContent = node.innerText
|
||||
node.setAttribute('mermaid-processed', true)
|
||||
try {
|
||||
const { svg } = await mermaid.render('mermaid-render-'+Date.now()+'-'+(diagramIds++), mermaidContent);
|
||||
node.innerHTML = svg
|
||||
} catch (err) {
|
||||
$('<div>').css({
|
||||
fontSize: '0.8em',
|
||||
border: '1px solid var(--red-ui-border-color-error)',
|
||||
padding: '5px',
|
||||
marginBottom: '10px',
|
||||
}).text(err.toString()).prependTo(node)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return {
|
||||
render: render,
|
||||
};
|
||||
})();
|
@@ -196,7 +196,7 @@
|
||||
}
|
||||
|
||||
$('<div class="form-row">'+
|
||||
'<label for="node-input-show-label-btn" data-i18n="editor.label"></label>'+
|
||||
'<label for="node-input-show-label" data-i18n="editor.label"></label>'+
|
||||
'<span style="margin-right: 2px;"/>'+
|
||||
'<input type="checkbox" id="node-input-show-label"/>'+
|
||||
'</div>').appendTo(dialogForm);
|
||||
|
@@ -71,7 +71,7 @@ RED.envVar = (function() {
|
||||
};
|
||||
if (item.name.trim() !== "") {
|
||||
new_env.push(item);
|
||||
if ((item.type === "cred") && (item.value !== "__PWRD__")) {
|
||||
if (item.type === "cred") {
|
||||
credentials.map[item.name] = item.value;
|
||||
credentials.map["has_"+item.name] = (item.value !== "");
|
||||
item.value = "__PWRD__";
|
||||
|
@@ -1,46 +0,0 @@
|
||||
// Mermaid diagram stub library for on-demand dynamic loading
|
||||
// Will be overwritten after script loading by $.getScript
|
||||
var mermaid = (function () {
|
||||
var enabled /* = undefined */;
|
||||
|
||||
var initializing = false;
|
||||
var initCalled = false;
|
||||
|
||||
function initialize(opt) {
|
||||
if (enabled === undefined) {
|
||||
if (RED.settings.markdownEditor &&
|
||||
RED.settings.markdownEditor.mermaid) {
|
||||
enabled = RED.settings.markdownEditor.mermaid.enabled;
|
||||
}
|
||||
else {
|
||||
enabled = true;
|
||||
}
|
||||
}
|
||||
if (enabled) {
|
||||
initializing = true;
|
||||
$.getScript("vendor/mermaid/mermaid.min.js",
|
||||
function (data, stat, jqxhr) {
|
||||
$(".mermaid").show();
|
||||
// invoke loaded mermaid API
|
||||
initializing = false;
|
||||
mermaid.initialize(opt);
|
||||
if (initCalled) {
|
||||
mermaid.init();
|
||||
initCalled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (initializing) {
|
||||
$(".mermaid").hide();
|
||||
initCalled = true;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
initialize: initialize,
|
||||
init: init,
|
||||
};
|
||||
})();
|
@@ -484,7 +484,7 @@ RED.palette = (function() {
|
||||
var currentLabel = paletteNode.attr("data-palette-label");
|
||||
var currentInfo = paletteNode.attr("data-palette-info");
|
||||
|
||||
if (currentLabel !== sf.name || currentInfo !== sf.info) {
|
||||
if (currentLabel !== sf.name || currentInfo !== sf.info || sf.in.length > 0 || sf.out.length > 0) {
|
||||
paletteNode.attr("data-palette-info",sf.info);
|
||||
setLabel(sf.type+":"+sf.id,paletteNode,sf.name,RED.utils.renderMarkdown(sf.info||""));
|
||||
}
|
||||
|
@@ -166,7 +166,7 @@ RED.projects.settings = (function() {
|
||||
var description = addTargetToExternalLinks($('<span class="red-ui-text-bidi-aware" dir=\"'+RED.text.bidi.resolveBaseTextDir(desc)+'">'+desc+'</span>')).appendTo(container);
|
||||
description.find(".red-ui-text-bidi-aware").contents().filter(function() { return this.nodeType === 3 && this.textContent.trim() !== "" }).wrap( "<span></span>" );
|
||||
setTimeout(function () {
|
||||
mermaid.init();
|
||||
RED.editor.mermaid.render()
|
||||
}, 200);
|
||||
}
|
||||
|
||||
|
@@ -647,9 +647,9 @@ RED.sidebar.versionControl = (function() {
|
||||
$.getJSON("projects/"+activeProject.name+"/commits/"+entry.sha,function(result) {
|
||||
result.project = activeProject;
|
||||
result.parents = entry.parents;
|
||||
result.oldRev = entry.sha+"~1";
|
||||
result.oldRev = entry.parents[0].length !== 0 ? entry.sha+"~1" : entry.sha;
|
||||
result.newRev = entry.sha;
|
||||
result.oldRevTitle = RED._("sidebar.project.versionControl.commitCapital")+" "+entry.sha.substring(0,7)+"~1";
|
||||
result.oldRevTitle = entry.parents[0].length !== 0 ? RED._("sidebar.project.versionControl.commitCapital")+" "+entry.sha.substring(0,7)+"~1" : " ";
|
||||
result.newRevTitle = RED._("sidebar.project.versionControl.commitCapital")+" "+entry.sha.substring(0,7);
|
||||
result.date = humanizeSinceDate(parseInt(entry.date));
|
||||
RED.diff.showCommitDiff(result);
|
||||
|
@@ -909,17 +909,19 @@ RED.subflow = (function() {
|
||||
|
||||
|
||||
/**
|
||||
* Create interface for controlling env var UI definition
|
||||
* Build the edit dialog for a subflow template (creating/modifying a subflow template)
|
||||
* @param {Object} uiContainer - the jQuery container for the environment variable list
|
||||
* @param {Object} node - the subflow template node
|
||||
*/
|
||||
function buildEnvControl(envList,node) {
|
||||
function buildEnvControl(uiContainer,node) {
|
||||
var tabs = RED.tabs.create({
|
||||
id: "subflow-env-tabs",
|
||||
onchange: function(tab) {
|
||||
if (tab.id === "subflow-env-tab-preview") {
|
||||
var inputContainer = $("#subflow-input-ui");
|
||||
var list = envList.editableList("items");
|
||||
var list = uiContainer.editableList("items");
|
||||
var exportedEnv = exportEnvList(list, true);
|
||||
buildEnvUI(inputContainer, exportedEnv,node);
|
||||
buildEnvUI(inputContainer, exportedEnv, node);
|
||||
}
|
||||
$("#subflow-env-tabs-content").children().hide();
|
||||
$("#" + tab.id).show();
|
||||
@@ -957,12 +959,33 @@ RED.subflow = (function() {
|
||||
RED.editor.envVarList.setLocale(locale);
|
||||
}
|
||||
|
||||
|
||||
function buildEnvUIRow(row, tenv, ui, node) {
|
||||
/**
|
||||
* Build a UI row for a subflow instance environment variable
|
||||
* Also used to build the UI row for subflow template preview
|
||||
* @param {JQuery} row - A form row element
|
||||
* @param {Object} tenv - A template environment variable
|
||||
* @param {String} tenv.name - The name of the environment variable
|
||||
* @param {String} tenv.type - The type of the environment variable
|
||||
* @param {String} tenv.value - The value set for this environment variable
|
||||
* @param {Object} tenv.parent - The parent environment variable
|
||||
* @param {String} tenv.parent.value - The value set for the parent environment variable
|
||||
* @param {String} tenv.parent.type - The type of the parent environment variable
|
||||
* @param {Object} tenv.ui - The UI configuration for the environment variable
|
||||
* @param {String} tenv.ui.icon - The icon for the environment variable
|
||||
* @param {Object} tenv.ui.label - The label for the environment variable
|
||||
* @param {String} tenv.ui.type - The type of the UI control for the environment variable
|
||||
* @param {Object} node - The subflow instance node
|
||||
*/
|
||||
function buildEnvUIRow(row, tenv, node) {
|
||||
if(RED.subflow.debug) { console.log("buildEnvUIRow", tenv) }
|
||||
const ui = tenv.ui || {}
|
||||
ui.label = ui.label||{};
|
||||
if ((tenv.type === "cred" || (tenv.parent && tenv.parent.type === "cred")) && !ui.type) {
|
||||
ui.type = "cred";
|
||||
ui.opts = {};
|
||||
} else if (tenv.type === "conf-types") {
|
||||
ui.type = "conf-types"
|
||||
ui.opts = { types: ['conf-types'] }
|
||||
} else if (!ui.type) {
|
||||
ui.type = "input";
|
||||
ui.opts = { types: RED.editor.envVarList.DEFAULT_ENV_TYPE_LIST }
|
||||
@@ -1006,9 +1029,10 @@ RED.subflow = (function() {
|
||||
if (tenv.hasOwnProperty('type')) {
|
||||
val.type = tenv.type;
|
||||
}
|
||||
const elId = getSubflowEnvPropertyName(tenv.name)
|
||||
switch(ui.type) {
|
||||
case "input":
|
||||
input = $('<input type="text">').css('width','70%').appendTo(row);
|
||||
input = $('<input type="text">').css('width','70%').attr('id', elId).appendTo(row);
|
||||
if (ui.opts.types && ui.opts.types.length > 0) {
|
||||
var inputType = val.type;
|
||||
if (ui.opts.types.indexOf(inputType) === -1) {
|
||||
@@ -1035,7 +1059,7 @@ RED.subflow = (function() {
|
||||
}
|
||||
break;
|
||||
case "select":
|
||||
input = $('<select>').css('width','70%').appendTo(row);
|
||||
input = $('<select>').css('width','70%').attr('id', elId).appendTo(row);
|
||||
if (ui.opts.opts) {
|
||||
ui.opts.opts.forEach(function(o) {
|
||||
$('<option>').val(o.v).text(RED.editor.envVarList.lookupLabel(o.l, o.l['en-US']||o.v, locale)).appendTo(input);
|
||||
@@ -1046,7 +1070,7 @@ RED.subflow = (function() {
|
||||
case "checkbox":
|
||||
label.css("cursor","default");
|
||||
var cblabel = $('<label>').css('width','70%').appendTo(row);
|
||||
input = $('<input type="checkbox">').css({
|
||||
input = $('<input type="checkbox">').attr('id', elId).css({
|
||||
marginTop: 0,
|
||||
width: 'auto',
|
||||
height: '34px'
|
||||
@@ -1064,7 +1088,7 @@ RED.subflow = (function() {
|
||||
input.prop("checked",boolVal);
|
||||
break;
|
||||
case "spinner":
|
||||
input = $('<input>').css('width','70%').appendTo(row);
|
||||
input = $('<input>').css('width','70%').attr('id', elId).appendTo(row);
|
||||
var spinnerOpts = {};
|
||||
if (ui.opts.hasOwnProperty('min')) {
|
||||
spinnerOpts.min = ui.opts.min;
|
||||
@@ -1093,18 +1117,25 @@ RED.subflow = (function() {
|
||||
default: 'cred'
|
||||
})
|
||||
break;
|
||||
}
|
||||
if (input) {
|
||||
input.attr('id',getSubflowEnvPropertyName(tenv.name))
|
||||
case "conf-types":
|
||||
// let clsId = 'config-node-input-' + val.type + '-' + val.value + '-' + Math.floor(Math.random() * 100000);
|
||||
// clsId = clsId.replace(/\W/g, '-');
|
||||
// input = $('<input>').css('width','70%').addClass(clsId).attr('id', elId).appendTo(row);
|
||||
input = $('<input>').css('width','70%').attr('id', elId).appendTo(row);
|
||||
const _type = tenv.parent?.type || tenv.type;
|
||||
RED.editor.prepareConfigNodeSelect(node, tenv.name, _type, 'node-input-subflow-env', null, tenv);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create environment variable input UI
|
||||
* Build the edit form for a subflow instance
|
||||
* Also used to build the preview form in the subflow template edit dialog
|
||||
* @param uiContainer - container for UI
|
||||
* @param envList - env var definitions of template
|
||||
*/
|
||||
function buildEnvUI(uiContainer, envList, node) {
|
||||
if(RED.subflow.debug) { console.log("buildEnvUI",envList) }
|
||||
uiContainer.empty();
|
||||
for (var i = 0; i < envList.length; i++) {
|
||||
var tenv = envList[i];
|
||||
@@ -1112,7 +1143,7 @@ RED.subflow = (function() {
|
||||
continue;
|
||||
}
|
||||
var row = $("<div/>", { class: "form-row" }).appendTo(uiContainer);
|
||||
buildEnvUIRow(row,tenv, tenv.ui || {}, node);
|
||||
buildEnvUIRow(row, tenv, node);
|
||||
}
|
||||
}
|
||||
// buildEnvUI
|
||||
@@ -1185,6 +1216,9 @@ RED.subflow = (function() {
|
||||
delete ui.opts
|
||||
}
|
||||
break;
|
||||
case "conf-types":
|
||||
delete ui.opts;
|
||||
break;
|
||||
default:
|
||||
delete ui.opts;
|
||||
}
|
||||
@@ -1207,8 +1241,9 @@ RED.subflow = (function() {
|
||||
if (/^subflow:/.test(node.type)) {
|
||||
var subflowDef = RED.nodes.subflow(node.type.substring(8));
|
||||
if (subflowDef.env) {
|
||||
subflowDef.env.forEach(function(env) {
|
||||
subflowDef.env.forEach(function(env, i) {
|
||||
var item = {
|
||||
index: i,
|
||||
name:env.name,
|
||||
parent: {
|
||||
type: env.type,
|
||||
@@ -1273,6 +1308,7 @@ RED.subflow = (function() {
|
||||
}
|
||||
|
||||
function exportSubflowInstanceEnv(node) {
|
||||
if(RED.subflow.debug) { console.log("exportSubflowInstanceEnv",node) }
|
||||
var env = [];
|
||||
// First, get the values for the SubflowTemplate defined properties
|
||||
// - these are the ones with custom UI elements
|
||||
@@ -1319,6 +1355,9 @@ RED.subflow = (function() {
|
||||
item.type = 'bool';
|
||||
item.value = ""+input.prop("checked");
|
||||
break;
|
||||
case "conf-types":
|
||||
item.value = input.val()
|
||||
item.type = data.parent.value;
|
||||
}
|
||||
if (ui.type === "cred" || item.type !== data.parent.type || item.value !== data.parent.value) {
|
||||
env.push(item);
|
||||
@@ -1332,8 +1371,15 @@ RED.subflow = (function() {
|
||||
return 'node-input-subflow-env-'+name.replace(/[^a-z0-9-_]/ig,"_");
|
||||
}
|
||||
|
||||
// Called by subflow.oneditprepare for both instances and templates
|
||||
|
||||
/**
|
||||
* Build the subflow edit form
|
||||
* Called by subflow.oneditprepare for both instances and templates
|
||||
* @param {"subflow"|"subflow-template"} type - the type of subflow being edited
|
||||
* @param {Object} node - the node being edited
|
||||
*/
|
||||
function buildEditForm(type,node) {
|
||||
if(RED.subflow.debug) { console.log("buildEditForm",type,node) }
|
||||
if (type === "subflow-template") {
|
||||
// This is the tabbed UI that offers the env list - with UI options
|
||||
// plus the preview tab
|
||||
|
@@ -158,6 +158,7 @@ RED.sidebar.config = (function() {
|
||||
entry.data('node',node.id);
|
||||
nodeDiv.data('node',node.id);
|
||||
var label = $('<div class="red-ui-palette-label"></div>').text(labelText).appendTo(nodeDiv);
|
||||
|
||||
if (node.d) {
|
||||
nodeDiv.addClass("red-ui-palette-node-config-disabled");
|
||||
$('<i class="fa fa-ban"></i>').prependTo(label);
|
||||
@@ -179,6 +180,20 @@ RED.sidebar.config = (function() {
|
||||
nodeDiv.addClass("red-ui-palette-node-config-unused");
|
||||
}
|
||||
}
|
||||
|
||||
if (!node.valid) {
|
||||
nodeDiv.addClass("red-ui-palette-node-config-invalid")
|
||||
const nodeDivAnnotations = $('<svg class="red-ui-palette-node-annotations red-ui-flow-node-error" width="10" height="10"></svg>').appendTo(nodeDiv)
|
||||
const errorBadge = document.createElementNS("http://www.w3.org/2000/svg","path");
|
||||
errorBadge.setAttribute("d","M 0,9 l 10,0 -5,-8 z");
|
||||
nodeDivAnnotations.append($(errorBadge))
|
||||
RED.popover.tooltip(nodeDivAnnotations, function () {
|
||||
if (node.validationErrors && node.validationErrors.length > 0) {
|
||||
return RED._("editor.errors.invalidProperties")+"<br> - "+node.validationErrors.join("<br> - ")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
nodeDiv.on('click',function(e) {
|
||||
e.stopPropagation();
|
||||
RED.view.select(false);
|
||||
|
@@ -232,7 +232,7 @@ RED.sidebar.context = (function() {
|
||||
typeHint: data.format,
|
||||
sourceId: id+"."+k,
|
||||
tools: tools,
|
||||
path: ""
|
||||
path: k
|
||||
}).appendTo(propRow.children()[1]);
|
||||
}
|
||||
})
|
||||
@@ -278,7 +278,7 @@ RED.sidebar.context = (function() {
|
||||
typeHint: data.format,
|
||||
sourceId: id+"."+k,
|
||||
tools: tools,
|
||||
path: ""
|
||||
path: k
|
||||
}).appendTo(propRow.children()[1]);
|
||||
}
|
||||
});
|
||||
@@ -299,7 +299,7 @@ RED.sidebar.context = (function() {
|
||||
typeHint: v.format,
|
||||
sourceId: id+"."+k,
|
||||
tools: tools,
|
||||
path: ""
|
||||
path: k
|
||||
}).appendTo(propRow.children()[1]);
|
||||
if (contextStores.length > 1) {
|
||||
$("<span>",{class:"red-ui-sidebar-context-property-storename"}).text(v.store).appendTo($(propRow.children()[0]))
|
||||
|
@@ -383,6 +383,7 @@ RED.sidebar.help = (function() {
|
||||
$(this).toggleClass('expanded',!isExpanded);
|
||||
})
|
||||
helpSection.parent().scrollTop(0);
|
||||
RED.editor.mermaid.render()
|
||||
}
|
||||
|
||||
function set(html,title) {
|
||||
|
@@ -464,7 +464,7 @@ RED.sidebar.info = (function() {
|
||||
}
|
||||
$(this).toggleClass('expanded',!isExpanded);
|
||||
});
|
||||
mermaid.init();
|
||||
RED.editor.mermaid.render()
|
||||
}
|
||||
|
||||
var tips = (function() {
|
||||
|
@@ -435,10 +435,15 @@ RED.tourGuide = (function() {
|
||||
|
||||
function listTour() {
|
||||
return [
|
||||
{
|
||||
id: "4_0",
|
||||
label: "4.0",
|
||||
path: "./tours/welcome.js"
|
||||
},
|
||||
{
|
||||
id: "3_1",
|
||||
label: "3.1",
|
||||
path: "./tours/welcome.js"
|
||||
path: "./tours/3.1/welcome.js"
|
||||
},
|
||||
{
|
||||
id: "3_0",
|
||||
|
@@ -186,8 +186,15 @@ RED.typeSearch = (function() {
|
||||
var iconContainer = $('<div/>',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv);
|
||||
RED.utils.createIconElement(icon_url, iconContainer, false);
|
||||
|
||||
|
||||
if (!/^_action_:/.test(object.type) && object.type !== "junction") {
|
||||
if (/^subflow:/.test(object.type)) {
|
||||
var sf = RED.nodes.subflow(object.type.substring(8));
|
||||
if (sf.in.length > 0) {
|
||||
$('<div/>',{class:"red-ui-search-result-node-port"}).appendTo(nodeDiv);
|
||||
}
|
||||
if (sf.out.length > 0) {
|
||||
$('<div/>',{class:"red-ui-search-result-node-port red-ui-search-result-node-output"}).appendTo(nodeDiv);
|
||||
}
|
||||
} else if (!/^_action_:/.test(object.type) && object.type !== "junction") {
|
||||
if (def.inputs > 0) {
|
||||
$('<div/>',{class:"red-ui-search-result-node-port"}).appendTo(nodeDiv);
|
||||
}
|
||||
@@ -323,7 +330,7 @@ RED.typeSearch = (function() {
|
||||
}
|
||||
}
|
||||
function applyFilter(filter,type,def) {
|
||||
return !filter ||
|
||||
return !def || !filter ||
|
||||
(
|
||||
(!filter.spliceMultiple) &&
|
||||
(!filter.type || type === filter.type) &&
|
||||
|
@@ -101,28 +101,8 @@ RED.utils = (function() {
|
||||
|
||||
renderer.code = function (code, lang) {
|
||||
if(lang === "mermaid") {
|
||||
// mermaid diagram rendering
|
||||
if (mermaidIsEnabled === undefined) {
|
||||
if (RED.settings.markdownEditor &&
|
||||
RED.settings.markdownEditor.mermaid) {
|
||||
mermaidIsEnabled = RED.settings.markdownEditor.mermaid.enabled;
|
||||
}
|
||||
else {
|
||||
mermaidIsEnabled = true;
|
||||
}
|
||||
}
|
||||
if (mermaidIsEnabled) {
|
||||
if (!mermaidIsInitialized) {
|
||||
mermaidIsInitialized = true;
|
||||
mermaid.initialize({startOnLoad:false});
|
||||
}
|
||||
return `<pre class='mermaid'>${code}</pre>`;
|
||||
}
|
||||
else {
|
||||
return `<details><summary>${RED._("markdownEditor.mermaid.summary")}</summary><pre><code>${code}</code></pre></details>`;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return `<pre class='mermaid'>${code}</pre>`;
|
||||
} else {
|
||||
return "<pre><code>" +code +"</code></pre>";
|
||||
}
|
||||
};
|
||||
@@ -503,6 +483,16 @@ RED.utils = (function() {
|
||||
$('<span class="red-ui-debug-msg-type-string-swatch"></span>').css('backgroundColor',obj).appendTo(e);
|
||||
}
|
||||
|
||||
let n = RED.nodes.node(obj) ?? RED.nodes.workspace(obj);
|
||||
if (n) {
|
||||
if (options.nodeSelector && "function" == typeof options.nodeSelector) {
|
||||
e.css('cursor', 'pointer').on("click", function(evt) {
|
||||
evt.preventDefault();
|
||||
options.nodeSelector(n.id);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
} else if (typeof obj === 'number') {
|
||||
e = $('<span class="red-ui-debug-msg-type-number"></span>').appendTo(entryObj);
|
||||
|
||||
@@ -609,6 +599,7 @@ RED.utils = (function() {
|
||||
exposeApi: exposeApi,
|
||||
// tools: tools // Do not pass tools down as we
|
||||
// keep them attached to the top-level header
|
||||
nodeSelector: options.nodeSelector,
|
||||
}
|
||||
).appendTo(row);
|
||||
}
|
||||
@@ -639,6 +630,7 @@ RED.utils = (function() {
|
||||
exposeApi: exposeApi,
|
||||
// tools: tools // Do not pass tools down as we
|
||||
// keep them attached to the top-level header
|
||||
nodeSelector: options.nodeSelector,
|
||||
}
|
||||
).appendTo(row);
|
||||
}
|
||||
@@ -695,6 +687,7 @@ RED.utils = (function() {
|
||||
exposeApi: exposeApi,
|
||||
// tools: tools // Do not pass tools down as we
|
||||
// keep them attached to the top-level header
|
||||
nodeSelector: options.nodeSelector,
|
||||
}
|
||||
).appendTo(row);
|
||||
}
|
||||
@@ -917,6 +910,54 @@ RED.utils = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a typed property is valid according to the type.
|
||||
* Returns true if valid.
|
||||
* Return String error message if invalid
|
||||
* @param {*} propertyType
|
||||
* @param {*} propertyValue
|
||||
* @returns true if valid, String if invalid
|
||||
*/
|
||||
function validateTypedProperty(propertyValue, propertyType, opt) {
|
||||
if (propertyValue && /^\${[^}]+}$/.test(propertyValue)) {
|
||||
// Allow ${ENV_VAR} value
|
||||
return true
|
||||
}
|
||||
let error
|
||||
if (propertyType === 'json') {
|
||||
try {
|
||||
JSON.parse(propertyValue);
|
||||
} catch(err) {
|
||||
error = RED._("validator.errors.invalid-json", {
|
||||
error: err.message
|
||||
})
|
||||
}
|
||||
} else if (propertyType === 'msg' || propertyType === 'flow' || propertyType === 'global' ) {
|
||||
if (!RED.utils.validatePropertyExpression(propertyValue)) {
|
||||
error = RED._("validator.errors.invalid-prop")
|
||||
}
|
||||
} else if (propertyType === 'num') {
|
||||
if (!/^NaN$|^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$|^[+-]?(0b|0B)[01]+$|^[+-]?(0o|0O)[0-7]+$|^[+-]?(0x|0X)[0-9a-fA-F]+$/.test(propertyValue)) {
|
||||
error = RED._("validator.errors.invalid-num")
|
||||
}
|
||||
} else if (propertyType === 'jsonata') {
|
||||
try {
|
||||
jsonata(propertyValue)
|
||||
} catch(err) {
|
||||
error = RED._("validator.errors.invalid-expr", {
|
||||
error: err.message
|
||||
})
|
||||
}
|
||||
}
|
||||
if (error) {
|
||||
if (opt && opt.label) {
|
||||
return opt.label+': '+error
|
||||
}
|
||||
return error
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function getMessageProperty(msg,expr) {
|
||||
var result = null;
|
||||
var msgPropParts;
|
||||
@@ -1451,6 +1492,7 @@ RED.utils = (function() {
|
||||
getDarkerColor: getDarkerColor,
|
||||
parseModuleList: parseModuleList,
|
||||
checkModuleAllowed: checkModuleAllowed,
|
||||
getBrowserInfo: getBrowserInfo
|
||||
getBrowserInfo: getBrowserInfo,
|
||||
validateTypedProperty: validateTypedProperty
|
||||
}
|
||||
})();
|
||||
|
@@ -4155,10 +4155,15 @@ RED.view = (function() {
|
||||
scaleFactor = 30/largestEdge;
|
||||
}
|
||||
var width = img.width * scaleFactor;
|
||||
if (width > 20) {
|
||||
scaleFactor *= 20/width;
|
||||
width = 20;
|
||||
}
|
||||
var height = img.height * scaleFactor;
|
||||
icon.attr("width",width);
|
||||
icon.attr("height",height);
|
||||
icon.attr("x",15-width/2);
|
||||
icon.attr("y",(30-height)/2);
|
||||
}
|
||||
icon.attr("xlink:href",iconUrl);
|
||||
icon.style("display",null);
|
||||
@@ -4187,7 +4192,7 @@ RED.view = (function() {
|
||||
nodeEl.__statusGroup__.style.display = "none";
|
||||
} else {
|
||||
nodeEl.__statusGroup__.style.display = "inline";
|
||||
let backgroundWidth = 12
|
||||
let backgroundWidth = 15
|
||||
var fill = status_colours[d.status.fill]; // Only allow our colours for now
|
||||
if (d.status.shape == null && fill == null) {
|
||||
backgroundWidth = 0
|
||||
@@ -4207,7 +4212,11 @@ RED.view = (function() {
|
||||
nodeEl.__statusLabel__.textContent = "";
|
||||
}
|
||||
const textSize = nodeEl.__statusLabel__.getBBox()
|
||||
nodeEl.__statusBackground__.setAttribute('width', backgroundWidth + textSize.width + 6)
|
||||
backgroundWidth += textSize.width
|
||||
if (backgroundWidth > 0 && textSize.width > 0) {
|
||||
backgroundWidth += 6
|
||||
}
|
||||
nodeEl.__statusBackground__.setAttribute('width', backgroundWidth)
|
||||
}
|
||||
delete d.dirtyStatus;
|
||||
}
|
||||
@@ -4619,8 +4628,8 @@ RED.view = (function() {
|
||||
statusBackground.setAttribute("y",-1);
|
||||
statusBackground.setAttribute("width",200);
|
||||
statusBackground.setAttribute("height",13);
|
||||
statusBackground.setAttribute("rx",1);
|
||||
statusBackground.setAttribute("ry",1);
|
||||
statusBackground.setAttribute("rx",2);
|
||||
statusBackground.setAttribute("ry",2);
|
||||
|
||||
statusEl.appendChild(statusBackground);
|
||||
node[0][0].__statusBackground__ = statusBackground;
|
||||
@@ -6243,6 +6252,10 @@ RED.view = (function() {
|
||||
}
|
||||
})
|
||||
}
|
||||
if (selection.links) {
|
||||
selectedLinks.clear();
|
||||
selection.links.forEach(selectedLinks.add);
|
||||
}
|
||||
}
|
||||
}
|
||||
updateSelection();
|
||||
|
@@ -17,6 +17,8 @@
|
||||
|
||||
RED.workspaces = (function() {
|
||||
|
||||
const documentTitle = document.title;
|
||||
|
||||
var activeWorkspace = 0;
|
||||
var workspaceIndex = 0;
|
||||
|
||||
@@ -339,12 +341,18 @@ RED.workspaces = (function() {
|
||||
$("#red-ui-workspace-chart").show();
|
||||
activeWorkspace = tab.id;
|
||||
window.location.hash = 'flow/'+tab.id;
|
||||
if (tab.label) {
|
||||
document.title = `${documentTitle} : ${tab.label}`
|
||||
} else {
|
||||
document.title = documentTitle
|
||||
}
|
||||
$("#red-ui-workspace").toggleClass("red-ui-workspace-disabled", !!tab.disabled);
|
||||
$("#red-ui-workspace").toggleClass("red-ui-workspace-locked", !!tab.locked);
|
||||
} else {
|
||||
$("#red-ui-workspace-chart").hide();
|
||||
activeWorkspace = 0;
|
||||
window.location.hash = '';
|
||||
document.title = documentTitle
|
||||
}
|
||||
event.workspace = activeWorkspace;
|
||||
RED.events.emit("workspace:change",event);
|
||||
|
Reference in New Issue
Block a user