From 68331fc40c4390f9b9f5bec29baecec8ea5ab083 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Wed, 8 Jun 2022 21:56:17 +0100 Subject: [PATCH 001/237] implement flows runtime stop/start API and UI --- Gruntfile.js | 1 + .../@node-red/editor-api/lib/admin/flows.js | 23 +++ .../@node-red/editor-api/lib/admin/index.js | 6 + .../editor-client/src/images/start.svg | 4 + .../editor-client/src/images/stop.svg | 4 + .../@node-red/editor-client/src/js/red.js | 5 + .../@node-red/editor-client/src/js/runtime.js | 53 +++++++ .../editor-client/src/js/ui/common/menu.js | 12 ++ .../editor-client/src/js/ui/deploy.js | 133 ++++++++++++------ .../@node-red/editor-client/src/js/ui/view.js | 3 + .../editor-client/src/sass/flow.scss | 7 + .../@node-red/runtime/lib/api/flows.js | 84 ++++++++++- .../@node-red/runtime/lib/api/settings.js | 12 ++ .../@node-red/runtime/lib/flows/index.js | 50 +++++-- packages/node_modules/node-red/settings.js | 15 +- .../editor-api/lib/admin/flows_spec.js | 97 +++++++++++++ .../@node-red/runtime/lib/api/flows_spec.js | 122 ++++++++++++++++ .../@node-red/runtime/lib/flows/index_spec.js | 89 +++++++++++- 18 files changed, 657 insertions(+), 63 deletions(-) create mode 100644 packages/node_modules/@node-red/editor-client/src/images/start.svg create mode 100644 packages/node_modules/@node-red/editor-client/src/images/stop.svg create mode 100644 packages/node_modules/@node-red/editor-client/src/js/runtime.js diff --git a/Gruntfile.js b/Gruntfile.js index 979b38051..af0aa0e95 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -142,6 +142,7 @@ module.exports = function(grunt) { "packages/node_modules/@node-red/editor-client/src/js/settings.js", "packages/node_modules/@node-red/editor-client/src/js/user.js", "packages/node_modules/@node-red/editor-client/src/js/comms.js", + "packages/node_modules/@node-red/editor-client/src/js/runtime.js", "packages/node_modules/@node-red/editor-client/src/js/text/bidi.js", "packages/node_modules/@node-red/editor-client/src/js/text/format.js", "packages/node_modules/@node-red/editor-client/src/js/ui/state.js", diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/flows.js b/packages/node_modules/@node-red/editor-api/lib/admin/flows.js index 11b30e446..611d9c2ca 100644 --- a/packages/node_modules/@node-red/editor-api/lib/admin/flows.js +++ b/packages/node_modules/@node-red/editor-api/lib/admin/flows.js @@ -68,5 +68,28 @@ module.exports = { }).catch(function(err) { apiUtils.rejectHandler(req,res,err); }) + }, + getState: function(req,res) { + const opts = { + user: req.user, + req: apiUtils.getRequestLogObject(req) + } + runtimeAPI.flows.getState(opts).then(function(result) { + res.json(result); + }).catch(function(err) { + apiUtils.rejectHandler(req,res,err); + }) + }, + postState: function(req,res) { + const opts = { + user: req.user, + requestedState: req.get("Node-RED-Flow-Run-State-Change")||"", + req: apiUtils.getRequestLogObject(req) + } + runtimeAPI.flows.setState(opts).then(function(result) { + res.json(result); + }).catch(function(err) { + apiUtils.rejectHandler(req,res,err); + }) } } diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/index.js b/packages/node_modules/@node-red/editor-api/lib/admin/index.js index 87fd0dec0..078779f5a 100644 --- a/packages/node_modules/@node-red/editor-api/lib/admin/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/admin/index.js @@ -54,6 +54,12 @@ module.exports = { adminApp.get("/flows",needsPermission("flows.read"),flows.get,apiUtil.errorHandler); adminApp.post("/flows",needsPermission("flows.write"),flows.post,apiUtil.errorHandler); + // Flows/state + adminApp.get("/flows/state", needsPermission("flows.read"), flows.getState, apiUtil.errorHandler); + if (!settings.runtimeState || settings.runtimeState.enabled !== false) { + adminApp.post("/flows/state", needsPermission("flows.write"), flows.postState, apiUtil.errorHandler); + } + // Flow adminApp.get("/flow/:id",needsPermission("flows.read"),flow.get,apiUtil.errorHandler); adminApp.post("/flow",needsPermission("flows.write"),flow.post,apiUtil.errorHandler); diff --git a/packages/node_modules/@node-red/editor-client/src/images/start.svg b/packages/node_modules/@node-red/editor-client/src/images/start.svg new file mode 100644 index 000000000..9623be86c --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/images/start.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/packages/node_modules/@node-red/editor-client/src/images/stop.svg b/packages/node_modules/@node-red/editor-client/src/images/stop.svg new file mode 100644 index 000000000..13b1a945a --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/images/stop.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/packages/node_modules/@node-red/editor-client/src/js/red.js b/packages/node_modules/@node-red/editor-client/src/js/red.js index 57d2d1e33..939c32f2e 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/red.js +++ b/packages/node_modules/@node-red/editor-client/src/js/red.js @@ -297,6 +297,10 @@ var RED = (function() { // handled below return; } + if (notificationId === "flows-run-state") { + // handled in editor-client/src/js/runtime.js + return; + } if (notificationId === "project-update") { loader.start(RED._("event.loadingProject"), 0); RED.nodes.clear(); @@ -747,6 +751,7 @@ var RED = (function() { RED.keyboard.init(buildMainMenu); RED.nodes.init(); + RED.runtime.init() RED.comms.connect(); $("#red-ui-main-container").show(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/runtime.js b/packages/node_modules/@node-red/editor-client/src/js/runtime.js new file mode 100644 index 000000000..939517877 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/runtime.js @@ -0,0 +1,53 @@ +RED.runtime = (function() { + let state = "" + let settings = {ui: true, enabled: true}; + const STOPPED = "stopped" + const STARTED = "started" + return { + init: function() { + // refresh the current runtime status from server + settings = RED.settings.runtimeState; + RED.runtime.requestState() + + // {id:"flows-run-state", started: false, state: "stopped", retain:true} + RED.comms.subscribe("notification/flows-run-state",function(topic,msg) { + RED.events.emit("flows-run-state",msg); + RED.runtime.updateState(msg.state); + }); + }, + get state() { + return state + }, + get started() { + return state === STARTED + }, + get states() { + return { STOPPED, STARTED } + }, + updateState: function(newState) { + state = newState; + // disable pointer events on node buttons (e.g. inject/debug nodes) + $(".red-ui-flow-node-button").toggleClass("red-ui-flow-node-button-stopped", state === STOPPED) + // show/hide Start/Stop based on current state + if(!RED.settings.runtimeState || RED.settings.runtimeState.ui !== false) { + RED.menu.setVisible("deploymenu-item-runtime-stop", state === STARTED) + RED.menu.setVisible("deploymenu-item-runtime-start", state === STOPPED) + } + }, + requestState: function(callback) { + $.ajax({ + headers: { + "Accept":"application/json" + }, + cache: false, + url: 'flows/state', + success: function(data) { + RED.runtime.updateState(data.state) + if(callback) { + callback(data.state) + } + } + }); + } + } +})() \ No newline at end of file diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js index 417189b33..f42da5830 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js @@ -139,6 +139,9 @@ RED.menu = (function() { if (opt.disabled) { item.addClass("disabled"); } + if (opt.visible === false) { + item.addClass("hide"); + } } @@ -249,6 +252,14 @@ RED.menu = (function() { } } + function setVisible(id,state) { + if (!state) { + $("#"+id).parent().addClass("hide"); + } else { + $("#"+id).parent().removeClass("hide"); + } + } + function addItem(id,opt) { var item = createMenuItem(opt); if (opt !== null && opt.group) { @@ -305,6 +316,7 @@ RED.menu = (function() { isSelected: isSelected, toggleSelected: toggleSelected, setDisabled: setDisabled, + setVisible: setVisible, addItem: addItem, removeItem: removeItem, setAction: setAction, diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js index 5b73ed271..69460f8cf 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js @@ -63,16 +63,18 @@ RED.deploy = (function() { ''+ ''+ '').prependTo(".red-ui-header-toolbar"); - RED.menu.init({id:"red-ui-header-button-deploy-options", - options: [ - {id:"deploymenu-item-full",toggle:"deploy-type",icon:"red/images/deploy-full.svg",label:RED._("deploy.full"),sublabel:RED._("deploy.fullDesc"),selected: true, onselect:function(s) { if(s){changeDeploymentType("full")}}}, - {id:"deploymenu-item-flow",toggle:"deploy-type",icon:"red/images/deploy-flows.svg",label:RED._("deploy.modifiedFlows"),sublabel:RED._("deploy.modifiedFlowsDesc"), onselect:function(s) {if(s){changeDeploymentType("flows")}}}, - {id:"deploymenu-item-node",toggle:"deploy-type",icon:"red/images/deploy-nodes.svg",label:RED._("deploy.modifiedNodes"),sublabel:RED._("deploy.modifiedNodesDesc"),onselect:function(s) { if(s){changeDeploymentType("nodes")}}}, - null, - {id:"deploymenu-item-reload", icon:"red/images/deploy-reload.svg",label:RED._("deploy.restartFlows"),sublabel:RED._("deploy.restartFlowsDesc"),onselect:"core:restart-flows"}, - - ] - }); + const mainMenuItems = [ + {id:"deploymenu-item-full",toggle:"deploy-type",icon:"red/images/deploy-full.svg",label:RED._("deploy.full"),sublabel:RED._("deploy.fullDesc"),selected: true, onselect:function(s) { if(s){changeDeploymentType("full")}}}, + {id:"deploymenu-item-flow",toggle:"deploy-type",icon:"red/images/deploy-flows.svg",label:RED._("deploy.modifiedFlows"),sublabel:RED._("deploy.modifiedFlowsDesc"), onselect:function(s) {if(s){changeDeploymentType("flows")}}}, + {id:"deploymenu-item-node",toggle:"deploy-type",icon:"red/images/deploy-nodes.svg",label:RED._("deploy.modifiedNodes"),sublabel:RED._("deploy.modifiedNodesDesc"),onselect:function(s) { if(s){changeDeploymentType("nodes")}}}, + null + ] + if(!RED.settings.runtimeState || RED.settings.runtimeState.ui !== false) { + mainMenuItems.push({id:"deploymenu-item-runtime-start", icon:"red/images/start.svg",label:"Start"/*RED._("deploy.startFlows")*/,sublabel:"Start Flows" /*RED._("deploy.startFlowsDesc")*/,onselect:"core:start-flows", visible:false}) + mainMenuItems.push({id:"deploymenu-item-runtime-stop", icon:"red/images/stop.svg",label:"Stop"/*RED._("deploy.startFlows")*/,sublabel:"Stop Flows" /*RED._("deploy.startFlowsDesc")*/,onselect:"core:stop-flows", visible:false}) + } + mainMenuItems.push({id:"deploymenu-item-reload", icon:"red/images/deploy-reload.svg",label:RED._("deploy.restartFlows"),sublabel:RED._("deploy.restartFlowsDesc"),onselect:"core:restart-flows"}) + RED.menu.init({id:"red-ui-header-button-deploy-options", options: mainMenuItems }); } else if (type == "simple") { var label = options.label || RED._("deploy.deploy"); var icon = 'red/images/deploy-full-o.svg'; @@ -100,6 +102,8 @@ RED.deploy = (function() { RED.actions.add("core:deploy-flows",save); if (type === "default") { + RED.actions.add("core:stop-flows",function() { stopStartFlows("stop") }); + RED.actions.add("core:start-flows",function() { stopStartFlows("start") }); RED.actions.add("core:restart-flows",restart); RED.actions.add("core:set-deploy-type-to-full",function() { RED.menu.setSelected("deploymenu-item-full",true);}); RED.actions.add("core:set-deploy-type-to-modified-flows",function() { RED.menu.setSelected("deploymenu-item-flow",true); }); @@ -270,18 +274,74 @@ RED.deploy = (function() { function sanitize(html) { return html.replace(/&/g,"&").replace(//g,">") } - function restart() { - var startTime = Date.now(); - $(".red-ui-deploy-button-content").css('opacity',0); - $(".red-ui-deploy-button-spinner").show(); - var deployWasEnabled = !$("#red-ui-header-button-deploy").hasClass("disabled"); - $("#red-ui-header-button-deploy").addClass("disabled"); - deployInflight = true; + + function shadeShow() { $("#red-ui-header-shade").show(); $("#red-ui-editor-shade").show(); $("#red-ui-palette-shade").show(); $("#red-ui-sidebar-shade").show(); - + } + function shadeHide() { + $("#red-ui-header-shade").hide(); + $("#red-ui-editor-shade").hide(); + $("#red-ui-palette-shade").hide(); + $("#red-ui-sidebar-shade").hide(); + } + function deployButtonSetBusy(){ + $(".red-ui-deploy-button-content").css('opacity',0); + $(".red-ui-deploy-button-spinner").show(); + $("#red-ui-header-button-deploy").addClass("disabled"); + } + function deployButtonClearBusy(){ + $(".red-ui-deploy-button-content").css('opacity',1); + $(".red-ui-deploy-button-spinner").hide(); + } + function stopStartFlows(state) { + const startTime = Date.now(); + const deployWasEnabled = !$("#red-ui-header-button-deploy").hasClass("disabled"); + deployInflight = true; + deployButtonSetBusy(); + shadeShow(); + RED.runtime.updateState(state); + $.ajax({ + url:"flows/state", + type: "POST", + data: {state: state}, + headers: { + "Node-RED-Flow-Run-State-Change": state + } + }).done(function(data,textStatus,xhr) { + if (deployWasEnabled) { + $("#red-ui-header-button-deploy").removeClass("disabled"); + } + RED.runtime.updateState((data && data.state) || "unknown" ) + RED.notify('

Done

',"success"); + }).fail(function(xhr,textStatus,err) { + if (deployWasEnabled) { + $("#red-ui-header-button-deploy").removeClass("disabled"); + } + if (xhr.status === 401) { + RED.notify("Not authorized" ,"error"); + } else if (xhr.responseText) { + RED.notify("Operation failed: " + xhr.responseText,"error"); + } else { + RED.notify("Operation failed: no response","error"); + } + RED.runtime.requestState() + }).always(function() { + const delta = Math.max(0,300-(Date.now()-startTime)); + setTimeout(function() { + deployButtonClearBusy(); + shadeHide() + deployInflight = false; + },delta); + }); + } + function restart() { + var startTime = Date.now(); + var deployWasEnabled = !$("#red-ui-header-button-deploy").hasClass("disabled"); + deployInflight = true; + deployButtonSetBusy(); $.ajax({ url:"flows", type: "POST", @@ -307,15 +367,10 @@ RED.deploy = (function() { RED.notify(RED._("deploy.deployFailed",{message:RED._("deploy.errors.noResponse")}),"error"); } }).always(function() { - deployInflight = false; var delta = Math.max(0,300-(Date.now()-startTime)); setTimeout(function() { - $(".red-ui-deploy-button-content").css('opacity',1); - $(".red-ui-deploy-button-spinner").hide(); - $("#red-ui-header-shade").hide(); - $("#red-ui-editor-shade").hide(); - $("#red-ui-palette-shade").hide(); - $("#red-ui-sidebar-shade").hide(); + deployButtonClearBusy(); + deployInflight = false; },delta); }); } @@ -450,21 +505,17 @@ RED.deploy = (function() { const nns = RED.nodes.createCompleteNodeSet(); const startTime = Date.now(); - $(".red-ui-deploy-button-content").css('opacity', 0); - $(".red-ui-deploy-button-spinner").show(); - $("#red-ui-header-button-deploy").addClass("disabled"); - + deployButtonSetBusy(); const data = { flows: nns }; - - if (!force) { + data.runtimeState = RED.runtime.state; + if (data.runtimeState === RED.runtime.states.STOPPED || force) { + data._rev = RED.nodes.version(); + } else { data.rev = RED.nodes.version(); } - + deployInflight = true; - $("#red-ui-header-shade").show(); - $("#red-ui-editor-shade").show(); - $("#red-ui-palette-shade").show(); - $("#red-ui-sidebar-shade").show(); + shadeShow(); $.ajax({ url: "flows", type: "POST", @@ -550,15 +601,11 @@ RED.deploy = (function() { RED.notify(RED._("deploy.deployFailed", { message: RED._("deploy.errors.noResponse") }), "error"); } }).always(function () { - deployInflight = false; const delta = Math.max(0, 300 - (Date.now() - startTime)); setTimeout(function () { - $(".red-ui-deploy-button-content").css('opacity', 1); - $(".red-ui-deploy-button-spinner").hide(); - $("#red-ui-header-shade").hide(); - $("#red-ui-editor-shade").hide(); - $("#red-ui-palette-shade").hide(); - $("#red-ui-sidebar-shade").hide(); + deployInflight = false; + deployButtonClearBusy() + shadeHide() }, delta); }); } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index adab87e38..59aa3fe6e 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -4792,6 +4792,9 @@ RED.view = (function() { if (d._def.button) { var buttonEnabled = isButtonEnabled(d); this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-disabled", !buttonEnabled); + if(RED.runtime && Object.hasOwn(RED.runtime,'started')) { + this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-stopped", !RED.runtime.started); + } var x = d._def.align == "right"?d.w-6:-25; if (d._def.button.toggle && !d[d._def.button.toggle]) { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/flow.scss b/packages/node_modules/@node-red/editor-client/src/sass/flow.scss index 2e6de1932..105d32cd6 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/flow.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/flow.scss @@ -176,6 +176,13 @@ cursor: default; } } + &.red-ui-flow-node-button-stopped { + opacity: 0.4; + .red-ui-flow-node-button-button { + cursor: default; + pointer-events: none; + } + } } .red-ui-flow-node-button-button { cursor: pointer; diff --git a/packages/node_modules/@node-red/runtime/lib/api/flows.js b/packages/node_modules/@node-red/runtime/lib/api/flows.js index d28a0479a..83ef68021 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/flows.js +++ b/packages/node_modules/@node-red/runtime/lib/api/flows.js @@ -73,6 +73,10 @@ var api = module.exports = { if (deploymentType === 'reload') { apiPromise = runtime.flows.loadFlows(true); } else { + //ensure the runtime running/stopped state matches the deploying editor. If not, then copy the _rev number to flows.rev + if(flows.hasOwnProperty('_rev') && !flows.hasOwnProperty('rev') && (flows.runtimeState !== "stopped" || runtime.flows.started)) { + flows.rev = flows._rev + } if (flows.hasOwnProperty('rev')) { var currentVersion = runtime.flows.getFlows().rev; if (currentVersion !== flows.rev) { @@ -255,5 +259,83 @@ var api = module.exports = { } } return sendCredentials; - } + }, + /** + * Gets running state of runtime flows + * @param {Object} opts + * @param {User} opts.user - the user calling the api + * @param {Object} opts.req - the request to log (optional) + * @return {{state:string, started:boolean}} - the current run state of the flows + * @memberof @node-red/runtime_flows + */ + getState: async function(opts) { + runtime.log.audit({event: "flows.getState"}, opts.req); + const result = { + state: runtime.flows.started ? "started" : "stopped", + started: !!runtime.flows.started, + rev: runtime.flows.getFlows().rev + } + return result; + }, + /** + * Sets running state of runtime flows + * @param {Object} opts + * @param {Object} opts.req - the request to log (optional) + * @param {User} opts.user - the user calling the api + * @param {string} opts.requestedState - the requested state. Valid values are "start" and "stop". + * @return {Promise} - the active flow configuration + * @memberof @node-red/runtime_flows + */ + setState: async function(opts) { + opts = opts || {}; + const makeError = (error, errcode, statusCode) => { + const message = typeof error == "object" ? error.message : error + const err = typeof error == "object" ? error : new Error(message||"Unexpected Error") + err.status = err.status || statusCode || 400; + err.code = err.code || errcode || "unexpected_error" + runtime.log.audit({ + event: "flows.setState", + state: opts.requestedState || "", + error: errcode || "unexpected_error", + message: err.code + }, opts.req); + return err + } + + const getState = () => { + return { + state: runtime.flows.started ? "started" : "stopped", + started: !!runtime.flows.started, + rev: runtime.flows.getFlows().rev, + } + } + + if(runtime.settings.runtimeState ? runtime.settings.runtimeState.enabled === false : false) { + throw (makeError("Method Not Allowed", "not_allowed", 405)) + } + switch (opts.requestedState) { + case "start": + try { + try { + runtime.settings.set('flowsRunStateRequested', opts.requestedState); + } catch(err) { } + await runtime.flows.startFlows("full") + return getState() + } catch (err) { + throw (makeError(err, err.code, 500)) + } + case "stop": + try { + try { + runtime.settings.set('flowsRunStateRequested', opts.requestedState); + } catch(err) { } + await runtime.flows.stopFlows("full") + return getState() + } catch (err) { + throw (makeError(err, err.code, 500)) + } + default: + throw (makeError("Cannot set runtime state. Invalid state", "invalid_run_state", 400)) + } + }, } diff --git a/packages/node_modules/@node-red/runtime/lib/api/settings.js b/packages/node_modules/@node-red/runtime/lib/api/settings.js index f56b8ab61..e35a7861d 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/settings.js +++ b/packages/node_modules/@node-red/runtime/lib/api/settings.js @@ -148,6 +148,18 @@ var api = module.exports = { enabled: (runtime.settings.diagnostics && runtime.settings.diagnostics.enabled === false) ? false : true, ui: (runtime.settings.diagnostics && runtime.settings.diagnostics.ui === false) ? false : true } + if(safeSettings.diagnostics.enabled === false) { + safeSettings.diagnostics.ui = false; // cannot have UI without endpoint + } + + safeSettings.runtimeState = { + //unless runtimeState.ui and runtimeState.enabled are explicitly false, they will default to true. + enabled: (runtime.settings.runtimeState && runtime.settings.runtimeState.enabled === false) ? false : true, + ui: (runtime.settings.runtimeState && runtime.settings.runtimeState.ui === false) ? false : true + } + if(safeSettings.runtimeState.enabled === false) { + safeSettings.runtimeState.ui = false; // cannot have UI without endpoint + } runtime.settings.exportNodeSettings(safeSettings); runtime.plugins.exportPluginSettings(safeSettings); diff --git a/packages/node_modules/@node-red/runtime/lib/flows/index.js b/packages/node_modules/@node-red/runtime/lib/flows/index.js index ae9131ec0..b707b4aaf 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/index.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/index.js @@ -261,6 +261,7 @@ function getFlows() { async function start(type,diff,muteLog) { type = type||"full"; + let reallyStarted = started started = true; var i; // If there are missing types, report them, emit the necessary runtime event and return @@ -365,24 +366,42 @@ async function start(type,diff,muteLog) { } } // Having created or updated all flows, now start them. - for (id in activeFlows) { - if (activeFlows.hasOwnProperty(id)) { - try { - activeFlows[id].start(diff); + let startFlows = true + try { + startFlows = settings.get('flowsRunStateRequested'); + } catch(err) { + } + startFlows = (startFlows !== "stop"); - // Create a map of node id to flow id and also a subflowInstance lookup map - var activeNodes = activeFlows[id].getActiveNodes(); - Object.keys(activeNodes).forEach(function(nid) { - activeNodesToFlow[nid] = id; - }); - } catch(err) { - console.log(err.stack); + if (startFlows) { + for (id in activeFlows) { + if (activeFlows.hasOwnProperty(id)) { + try { + activeFlows[id].start(diff); + // Create a map of node id to flow id and also a subflowInstance lookup map + var activeNodes = activeFlows[id].getActiveNodes(); + Object.keys(activeNodes).forEach(function(nid) { + activeNodesToFlow[nid] = id; + }); + } catch(err) { + console.log(err.stack); + } } } + reallyStarted = true; + events.emit("flows:started", {config: activeConfig, type: type, diff: diff}); + // Deprecated event + events.emit("nodes-started"); + } else { + started = false; } - events.emit("flows:started", {config: activeConfig, type: type, diff: diff}); - // Deprecated event - events.emit("nodes-started"); + + const state = { + started: reallyStarted, + state: reallyStarted ? "started" : "stopped", + } + events.emit("runtime-event",{id:"flows-run-state", payload: state, retain:true}); + if (credentialsPendingReset === true) { credentialsPendingReset = false; @@ -390,7 +409,7 @@ async function start(type,diff,muteLog) { events.emit("runtime-event",{id:"runtime-state",retain:true}); } - if (!muteLog) { + if (!muteLog && reallyStarted) { if (type !== "full") { log.info(log._("nodes.flows.started-modified-"+type)); } else { @@ -471,6 +490,7 @@ function stop(type,diff,muteLog) { } } events.emit("flows:stopped",{config: activeConfig, type: type, diff: diff}); + events.emit("runtime-event",{id:"flows-run-state", payload: {started: false, state: "stopped"}, retain:true}); // Deprecated event events.emit("nodes-stopped"); }); diff --git a/packages/node_modules/node-red/settings.js b/packages/node_modules/node-red/settings.js index 2e2b7035e..72f81266d 100644 --- a/packages/node_modules/node-red/settings.js +++ b/packages/node_modules/node-red/settings.js @@ -242,6 +242,7 @@ module.exports = { /******************************************************************************* * Runtime Settings * - lang + * - runtimeState * - diagnostics * - logging * - contextStorage @@ -267,7 +268,19 @@ module.exports = { /** enable or disable diagnostics display in the node-red editor. Must be set to `false` to disable */ ui: true, }, - + /** Configure runtimeState options + * - enabled: When `enabled` is `true` (or unset), runtime Start/Stop will + * be available at http://localhost:1880/flows/state + * - ui: When `ui` is `true` (or unset), the action `core:start-flows` and + * `core:stop-flows` be available to logged in users of node-red editor + * Also, the deploy menu (when set to default) will show a stop or start button + */ + runtimeState: { + /** enable or disable flows/state endpoint. Must be set to `false` to disable */ + enabled: true, + /** show or hide runtime stop/start options in the node-red editor. Must be set to `false` to hide */ + ui: true, + }, /** Configure the logging output */ logging: { /** Only console logging is currently supported */ diff --git a/test/unit/@node-red/editor-api/lib/admin/flows_spec.js b/test/unit/@node-red/editor-api/lib/admin/flows_spec.js index ac295a194..ba09c9fa1 100644 --- a/test/unit/@node-red/editor-api/lib/admin/flows_spec.js +++ b/test/unit/@node-red/editor-api/lib/admin/flows_spec.js @@ -32,7 +32,9 @@ describe("api/admin/flows", function() { app = express(); app.use(bodyParser.json()); app.get("/flows",flows.get); + app.get("/flows/state",flows.getState); app.post("/flows",flows.post); + app.post("/flows/state",flows.postState); }); it('returns flow - v1', function(done) { @@ -208,4 +210,99 @@ describe("api/admin/flows", function() { done(); }); }); + it('returns flows run state', function (done) { + var setFlows = sinon.spy(function () { return Promise.resolve(); }); + flows.init({ + flows: { + setFlows, + getState: async function () { + return { started: true, state: "started" }; + } + } + }); + request(app) + .get('/flows/state') + .set('Accept', 'application/json') + .set('Node-RED-Deployment-Type', 'reload') + .expect(200) + .end(function (err, res) { + if (err) { + return done(err); + } + try { + res.body.should.have.a.property('started', true); + res.body.should.have.a.property('state', "started"); + done(); + } catch (e) { + return done(e); + } + }); + }); + it('sets flows run state - stopped', function (done) { + var setFlows = sinon.spy(function () { return Promise.resolve(); }); + flows.init({ + flows: { + setFlows: setFlows, + getState: async function () { + return { started: true, state: "started" }; + }, + setState: async function () { + return { started: false, state: "stopped" }; + }, + } + }); + request(app) + .post('/flows/state') + .set('Accept', 'application/json') + .set('Node-RED-Flow-Run-State-Change', 'stop') + .expect(200) + .end(function (err, res) { + if (err) { + return done(err); + } + try { + res.body.should.have.a.property('started', false); + res.body.should.have.a.property('state', "stopped"); + done(); + } catch (e) { + return done(e); + } + }); + }); + it('sets flows run state - bad value', function (done) { + var setFlows = sinon.spy(function () { return Promise.resolve(); }); + const makeError = (error, errcode, statusCode) => { + const message = typeof error == "object" ? error.message : error + const err = typeof error == "object" ? error : new Error(message||"Unexpected Error") + err.status = err.status || statusCode || 400; + err.code = err.code || errcode || "unexpected_error" + return err + } + flows.init({ + flows: { + setFlows: setFlows, + getState: async function () { + return { started: true, state: "started" }; + }, + setState: async function () { + var err = (makeError("Cannot set runtime state. Invalid state", "invalid_run_state", 400)) + var p = Promise.reject(err); + p.catch(()=>{}); + return p; + }, + } + }); + request(app) + .post('/flows/state') + .set('Accept', 'application/json') + .set('Node-RED-Flow-Run-State-Change', 'bad-state') + .expect(400) + .end(function(err,res) { + if (err) { + return done(err); + } + res.body.should.have.property("code","invalid_run_state"); + done(); + }); + }); }); diff --git a/test/unit/@node-red/runtime/lib/api/flows_spec.js b/test/unit/@node-red/runtime/lib/api/flows_spec.js index 9062ef52f..0f560e3f8 100644 --- a/test/unit/@node-red/runtime/lib/api/flows_spec.js +++ b/test/unit/@node-red/runtime/lib/api/flows_spec.js @@ -427,4 +427,126 @@ describe("runtime-api/flows", function() { }); }); + describe("flow run state", function() { + var startFlows, stopFlows, runtime; + beforeEach(function() { + let flowsStarted = true; + let flowsState = "started"; + startFlows = sinon.spy(function(type) { + if (type !== "full") { + var err = new Error(); + // TODO: quirk of internal api - uses .code for .status + err.code = 400; + var p = Promise.reject(err); + p.catch(()=>{}); + return p; + } + flowsStarted = true; + flowsState = "started"; + return Promise.resolve(); + }); + stopFlows = sinon.spy(function(type) { + if (type !== "full") { + var err = new Error(); + // TODO: quirk of internal api - uses .code for .status + err.code = 400; + var p = Promise.reject(err); + p.catch(()=>{}); + return p; + } + flowsStarted = false; + flowsState = "stopped"; + return Promise.resolve(); + }); + runtime = { + log: mockLog(), + settings: { + runtimeState: { + enabled: true, + ui: true, + }, + }, + flows: { + get started() { + return flowsStarted; + }, + startFlows, + stopFlows, + getFlows: function() { return {rev:"currentRev",flows:[]} }, + } + } + }) + + it("gets flows run state", async function() { + flows.init(runtime); + const state = await flows.getState({}) + state.should.have.property("started", true) + state.should.have.property("state", "started") + }); + it("permits getting flows run state when setting disabled", async function() { + runtime.settings.runtimeState.enabled = false; + flows.init(runtime); + const state = await flows.getState({}) + state.should.have.property("started", true) + state.should.have.property("state", "started") + }); + it("start flows", async function() { + flows.init(runtime); + const state = await flows.setState({requestedState:"start"}) + state.should.have.property("started", true) + state.should.have.property("state", "started") + stopFlows.called.should.not.be.true(); + startFlows.called.should.be.true(); + }); + it("stop flows", async function() { + flows.init(runtime); + const state = await flows.setState({requestedState:"stop"}) + state.should.have.property("started", false) + state.should.have.property("state", "stopped") + stopFlows.called.should.be.true(); + startFlows.called.should.not.be.true(); + }); + it("rejects starting flows when setting disabled", async function() { + let err; + runtime.settings.runtimeState.enabled = false; + flows.init(runtime); + try { + await flows.setState({requestedState:"start"}) + } catch (error) { + err = error + } + stopFlows.called.should.not.be.true(); + startFlows.called.should.not.be.true(); + should(err).have.property("code", "not_allowed") + should(err).have.property("status", 405) + }); + it("rejects stopping flows when setting disabled", async function() { + let err; + runtime.settings.runtimeState.enabled = false; + flows.init(runtime); + try { + await flows.setState({requestedState:"stop"}) + } catch (error) { + err = error + } + stopFlows.called.should.not.be.true(); + startFlows.called.should.not.be.true(); + should(err).have.property("code", "not_allowed") + should(err).have.property("status", 405) + }); + it("rejects setting invalid flows run state", async function() { + let err; + flows.init(runtime); + try { + await flows.setState({requestedState:"bad-state"}) + } catch (error) { + err = error + } + stopFlows.called.should.not.be.true(); + startFlows.called.should.not.be.true(); + should(err).have.property("code", "invalid_run_state") + should(err).have.property("status", 400) + }); + }); + }); diff --git a/test/unit/@node-red/runtime/lib/flows/index_spec.js b/test/unit/@node-red/runtime/lib/flows/index_spec.js index 737846100..bacb94b0f 100644 --- a/test/unit/@node-red/runtime/lib/flows/index_spec.js +++ b/test/unit/@node-red/runtime/lib/flows/index_spec.js @@ -131,7 +131,7 @@ describe('flows/index', function() { // eventsOn.calledOnce.should.be.true(); // }); // }); - +/* describe('#setFlows', function() { it('sets the full flow', function(done) { var originalConfig = [ @@ -300,6 +300,7 @@ describe('flows/index', function() { }); }); }); + */ describe('#startFlows', function() { it('starts the loaded config', function(done) { @@ -321,6 +322,87 @@ describe('flows/index', function() { return flows.startFlows(); }); }); + it('emits runtime-event "flows-run-state" "started"', async function () { + var originalConfig = [ + { id: "t1-1", x: 10, y: 10, z: "t1", type: "test", wires: [] }, + { id: "t1", type: "tab" } + ]; + storage.getFlows = function () { + return Promise.resolve({ flows: originalConfig }); + } + let receivedEvent = null; + const handleEvent = (data) => { + console.log(data) + if(data && data.id === 'flows-run-state') { + receivedEvent = data; + } + } + events.on('runtime-event', handleEvent); + flows.init({ log: mockLog, settings: {}, storage: storage }); + await flows.load() + await flows.startFlows() + events.removeListener("runtime-event", handleEvent); + + //{id:"flows-run-state", payload: {started: true, state: "started"} + should(receivedEvent).not.be.null() + receivedEvent.should.have.property("id", "flows-run-state") + receivedEvent.should.have.property("payload", { started: true, state: "started" }) + receivedEvent.should.have.property("retain", true) + }); + it('emits runtime-event "flows-run-state" "stopped"', async function () { + const originalConfig = [ + { id: "t1-1", x: 10, y: 10, z: "t1", type: "test", wires: [] }, + { id: "t1", type: "tab" } + ]; + storage.getFlows = function () { + return Promise.resolve({ flows: originalConfig }); + } + let receivedEvent = null; + const handleEvent = (data) => { + if(data && data.id === 'flows-run-state') { + receivedEvent = data; + } + } + events.on('runtime-event', handleEvent); + flows.init({ log: mockLog, settings: {}, storage: storage }); + await flows.load() + await flows.startFlows() + await flows.stopFlows() + events.removeListener("runtime-event", handleEvent); + + //{id:"flows-run-state", payload: {started: true, state: "started"} + should(receivedEvent).not.be.null() + receivedEvent.should.have.property("id", "flows-run-state") + receivedEvent.should.have.property("payload", { started: false, state: "stopped" }) + receivedEvent.should.have.property("retain", true) + }); + // it('raises error when invalid flows run state requested', async function () { + // const originalConfig = [ + // { id: "t1-1", x: 10, y: 10, z: "t1", type: "test", wires: [] }, + // { id: "t1", type: "tab" } + // ]; + // storage.getFlows = function () { + // return Promise.resolve({ flows: originalConfig }); + // } + // let receivedEvent = null; + // const handleEvent = (data) => { + // if(data && data.id === 'flows-run-state') { + // receivedEvent = data; + // } + // } + // events.on('runtime-event', handleEvent); + // flows.init({ log: mockLog, settings: {}, storage: storage }); + // await flows.load() + // await flows.startFlows() + // await flows.stopFlows() + // events.removeListener("runtime-event", handleEvent); + + // //{id:"flows-run-state", payload: {started: true, state: "started"} + // should(receivedEvent).not.be.null() + // receivedEvent.should.have.property("id", "flows-run-state") + // receivedEvent.should.have.property("payload", { started: false, state: "stopped" }) + // receivedEvent.should.have.property("retain", true) + // }); it('does not start if nodes missing', function(done) { var originalConfig = [ {id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]}, @@ -415,7 +497,7 @@ describe('flows/index', function() { describe.skip('#get',function() { }); - +/* describe('#eachNode', function() { it('iterates the flow nodes', function(done) { var originalConfig = [ @@ -582,7 +664,7 @@ describe('flows/index', function() { ]; flows.init({log:mockLog, settings:{},storage:storage}); flows.setFlows(originalConfig).then(function() { - /*jshint immed: false */ + try { flows.checkTypeInUse("used-module"); done("type_in_use error not thrown"); @@ -666,4 +748,5 @@ describe('flows/index', function() { describe('#enableFlow', function() { it.skip("enableFlow"); }) + */ }); From d4e6136b09395799596eff1e588164009b2ea66c Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Thu, 9 Jun 2022 15:27:50 +0100 Subject: [PATCH 002/237] re-enable tests i had temporarily disabled --- .../@node-red/runtime/lib/flows/index_spec.js | 36 ++----------------- 1 file changed, 3 insertions(+), 33 deletions(-) diff --git a/test/unit/@node-red/runtime/lib/flows/index_spec.js b/test/unit/@node-red/runtime/lib/flows/index_spec.js index bacb94b0f..5e8f8a46e 100644 --- a/test/unit/@node-red/runtime/lib/flows/index_spec.js +++ b/test/unit/@node-red/runtime/lib/flows/index_spec.js @@ -131,7 +131,7 @@ describe('flows/index', function() { // eventsOn.calledOnce.should.be.true(); // }); // }); -/* + describe('#setFlows', function() { it('sets the full flow', function(done) { var originalConfig = [ @@ -300,7 +300,6 @@ describe('flows/index', function() { }); }); }); - */ describe('#startFlows', function() { it('starts the loaded config', function(done) { @@ -332,7 +331,6 @@ describe('flows/index', function() { } let receivedEvent = null; const handleEvent = (data) => { - console.log(data) if(data && data.id === 'flows-run-state') { receivedEvent = data; } @@ -376,33 +374,6 @@ describe('flows/index', function() { receivedEvent.should.have.property("payload", { started: false, state: "stopped" }) receivedEvent.should.have.property("retain", true) }); - // it('raises error when invalid flows run state requested', async function () { - // const originalConfig = [ - // { id: "t1-1", x: 10, y: 10, z: "t1", type: "test", wires: [] }, - // { id: "t1", type: "tab" } - // ]; - // storage.getFlows = function () { - // return Promise.resolve({ flows: originalConfig }); - // } - // let receivedEvent = null; - // const handleEvent = (data) => { - // if(data && data.id === 'flows-run-state') { - // receivedEvent = data; - // } - // } - // events.on('runtime-event', handleEvent); - // flows.init({ log: mockLog, settings: {}, storage: storage }); - // await flows.load() - // await flows.startFlows() - // await flows.stopFlows() - // events.removeListener("runtime-event", handleEvent); - - // //{id:"flows-run-state", payload: {started: true, state: "started"} - // should(receivedEvent).not.be.null() - // receivedEvent.should.have.property("id", "flows-run-state") - // receivedEvent.should.have.property("payload", { started: false, state: "stopped" }) - // receivedEvent.should.have.property("retain", true) - // }); it('does not start if nodes missing', function(done) { var originalConfig = [ {id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]}, @@ -497,7 +468,7 @@ describe('flows/index', function() { describe.skip('#get',function() { }); -/* + describe('#eachNode', function() { it('iterates the flow nodes', function(done) { var originalConfig = [ @@ -664,7 +635,7 @@ describe('flows/index', function() { ]; flows.init({log:mockLog, settings:{},storage:storage}); flows.setFlows(originalConfig).then(function() { - + /*jshint immed: false */ try { flows.checkTypeInUse("used-module"); done("type_in_use error not thrown"); @@ -748,5 +719,4 @@ describe('flows/index', function() { describe('#enableFlow', function() { it.skip("enableFlow"); }) - */ }); From 1b4f2b9c537b6c5f44fed5dbc83c1bd8cef6c3f1 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Thu, 9 Jun 2022 22:29:28 +0100 Subject: [PATCH 003/237] fix formatting (resolve merge conflict) --- packages/node_modules/node-red/settings.js | 142 ++++++++++----------- 1 file changed, 71 insertions(+), 71 deletions(-) diff --git a/packages/node_modules/node-red/settings.js b/packages/node_modules/node-red/settings.js index 72f81266d..e27a9a584 100644 --- a/packages/node_modules/node-red/settings.js +++ b/packages/node_modules/node-red/settings.js @@ -250,18 +250,18 @@ module.exports = { * - externalModules ******************************************************************************/ - /** Uncomment the following to run node-red in your preferred language. - * Available languages include: en-US (default), ja, de, zh-CN, zh-TW, ru, ko - * Some languages are more complete than others. - */ - // lang: "de", + /** Uncomment the following to run node-red in your preferred language. + * Available languages include: en-US (default), ja, de, zh-CN, zh-TW, ru, ko + * Some languages are more complete than others. + */ + // lang: "de", /** Configure diagnostics options * - enabled: When `enabled` is `true` (or unset), diagnostics data will * be available at http://localhost:1880/diagnostics * - ui: When `ui` is `true` (or unset), the action `show-system-info` will * be available to logged in users of node-red editor - */ + */ diagnostics: { /** enable or disable diagnostics endpoint. Must be set to `false` to disable */ enabled: true, @@ -281,74 +281,74 @@ module.exports = { /** show or hide runtime stop/start options in the node-red editor. Must be set to `false` to hide */ ui: true, }, - /** Configure the logging output */ - logging: { - /** Only console logging is currently supported */ - console: { - /** Level of logging to be recorded. Options are: - * fatal - only those errors which make the application unusable should be recorded - * error - record errors which are deemed fatal for a particular request + fatal errors - * warn - record problems which are non fatal + errors + fatal errors - * info - record information about the general running of the application + warn + error + fatal errors - * debug - record information which is more verbose than info + info + warn + error + fatal errors - * trace - record very detailed logging + debug + info + warn + error + fatal errors - * off - turn off all logging (doesn't affect metrics or audit) - */ - level: "info", - /** Whether or not to include metric events in the log output */ - metrics: false, - /** Whether or not to include audit events in the log output */ - audit: false - } - }, + /** Configure the logging output */ + logging: { + /** Only console logging is currently supported */ + console: { + /** Level of logging to be recorded. Options are: + * fatal - only those errors which make the application unusable should be recorded + * error - record errors which are deemed fatal for a particular request + fatal errors + * warn - record problems which are non fatal + errors + fatal errors + * info - record information about the general running of the application + warn + error + fatal errors + * debug - record information which is more verbose than info + info + warn + error + fatal errors + * trace - record very detailed logging + debug + info + warn + error + fatal errors + * off - turn off all logging (doesn't affect metrics or audit) + */ + level: "info", + /** Whether or not to include metric events in the log output */ + metrics: false, + /** Whether or not to include audit events in the log output */ + audit: false + } + }, - /** Context Storage - * The following property can be used to enable context storage. The configuration - * provided here will enable file-based context that flushes to disk every 30 seconds. - * Refer to the documentation for further options: https://nodered.org/docs/api/context/ - */ - //contextStorage: { - // default: { - // module:"localfilesystem" - // }, - //}, + /** Context Storage + * The following property can be used to enable context storage. The configuration + * provided here will enable file-based context that flushes to disk every 30 seconds. + * Refer to the documentation for further options: https://nodered.org/docs/api/context/ + */ + //contextStorage: { + // default: { + // module:"localfilesystem" + // }, + //}, - /** `global.keys()` returns a list of all properties set in global context. - * This allows them to be displayed in the Context Sidebar within the editor. - * In some circumstances it is not desirable to expose them to the editor. The - * following property can be used to hide any property set in `functionGlobalContext` - * from being list by `global.keys()`. - * By default, the property is set to false to avoid accidental exposure of - * their values. Setting this to true will cause the keys to be listed. - */ - exportGlobalContextKeys: false, + /** `global.keys()` returns a list of all properties set in global context. + * This allows them to be displayed in the Context Sidebar within the editor. + * In some circumstances it is not desirable to expose them to the editor. The + * following property can be used to hide any property set in `functionGlobalContext` + * from being list by `global.keys()`. + * By default, the property is set to false to avoid accidental exposure of + * their values. Setting this to true will cause the keys to be listed. + */ + exportGlobalContextKeys: false, - /** Configure how the runtime will handle external npm modules. - * This covers: - * - whether the editor will allow new node modules to be installed - * - whether nodes, such as the Function node are allowed to have their - * own dynamically configured dependencies. - * The allow/denyList options can be used to limit what modules the runtime - * will install/load. It can use '*' as a wildcard that matches anything. - */ - externalModules: { - // autoInstall: false, /** Whether the runtime will attempt to automatically install missing modules */ - // autoInstallRetry: 30, /** Interval, in seconds, between reinstall attempts */ - // palette: { /** Configuration for the Palette Manager */ - // allowInstall: true, /** Enable the Palette Manager in the editor */ - // allowUpdate: true, /** Allow modules to be updated in the Palette Manager */ - // allowUpload: true, /** Allow module tgz files to be uploaded and installed */ - // allowList: ['*'], - // denyList: [], - // allowUpdateList: ['*'], - // denyUpdateList: [] - // }, - // modules: { /** Configuration for node-specified modules */ - // allowInstall: true, - // allowList: [], - // denyList: [] - // } - }, + /** Configure how the runtime will handle external npm modules. + * This covers: + * - whether the editor will allow new node modules to be installed + * - whether nodes, such as the Function node are allowed to have their + * own dynamically configured dependencies. + * The allow/denyList options can be used to limit what modules the runtime + * will install/load. It can use '*' as a wildcard that matches anything. + */ + externalModules: { + // autoInstall: false, /** Whether the runtime will attempt to automatically install missing modules */ + // autoInstallRetry: 30, /** Interval, in seconds, between reinstall attempts */ + // palette: { /** Configuration for the Palette Manager */ + // allowInstall: true, /** Enable the Palette Manager in the editor */ + // allowUpdate: true, /** Allow modules to be updated in the Palette Manager */ + // allowUpload: true, /** Allow module tgz files to be uploaded and installed */ + // allowList: ['*'], + // denyList: [], + // allowUpdateList: ['*'], + // denyUpdateList: [] + // }, + // modules: { /** Configuration for node-specified modules */ + // allowInstall: true, + // allowList: [], + // denyList: [] + // } + }, /******************************************************************************* From 326f346936e79e5da8a36fdb3a47e11aaff15a35 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Tue, 14 Jun 2022 22:54:00 +0200 Subject: [PATCH 004/237] Import default export if node is a transpiled es module --- packages/node_modules/@node-red/registry/lib/loader.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/node_modules/@node-red/registry/lib/loader.js b/packages/node_modules/@node-red/registry/lib/loader.js index fc508aa57..61f28ab86 100644 --- a/packages/node_modules/@node-red/registry/lib/loader.js +++ b/packages/node_modules/@node-red/registry/lib/loader.js @@ -359,6 +359,7 @@ function loadNodeSet(node) { try { var loadPromise = null; var r = require(node.file); + r = r.__esModule ? r.default : r if (typeof r === "function") { var red = registryUtil.createNodeApi(node); From 6c0d6c5425125d8dd89469eb7d6d4fc5f8e31479 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Wed, 15 Jun 2022 10:16:12 +0100 Subject: [PATCH 005/237] Join-reduce keep existing msg properties see https://discourse.nodered.org/t/no-response-object-after-split-join/63919 --- .../node_modules/@node-red/nodes/core/sequence/17-split.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/sequence/17-split.js b/packages/node_modules/@node-red/nodes/core/sequence/17-split.js index e158f344c..325cf85b5 100644 --- a/packages/node_modules/@node-red/nodes/core/sequence/17-split.js +++ b/packages/node_modules/@node-red/nodes/core/sequence/17-split.js @@ -314,11 +314,13 @@ module.exports = function(RED) { if (err) { return done(err); } - msgInfo.send({payload: result}); + msgInfo.msg.payload = result; + msgInfo.send(msgInfo.msg); done(); }); } else { - msgInfo.send({payload: result}); + msgInfo.msg.payload = result; + msgInfo.send(msgInfo.msg); done(); } } else { From 4ed559af95ae845792549a5f4061b873b2971be5 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 16 Jun 2022 15:44:51 +0100 Subject: [PATCH 006/237] Update changelog and version for 3-beta.3 --- CHANGELOG.md | 38 +++++++++++++++++++ package.json | 2 +- .../@node-red/editor-api/package.json | 6 +-- .../@node-red/editor-client/package.json | 2 +- .../node_modules/@node-red/nodes/package.json | 2 +- .../@node-red/registry/package.json | 4 +- .../@node-red/runtime/package.json | 6 +-- .../node_modules/@node-red/util/package.json | 2 +- packages/node_modules/node-red/package.json | 10 ++--- 9 files changed, 55 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd3793487..254e6a977 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,41 @@ +#### 3.0.0-beta.3: Beta Release + +Editor + + - Fix disable junction (#3671) @HiroyasuNishiyama + - Add Japanese translations for v2.2.3 (#3672) @kazuhitoyokoi + - Reset mouse state when switching tabs (#3643) @knolleary + - Fix uncorrect fix of junction to subflow conversion (#3666) @HiroyasuNishiyama + - Fix undoing junction to subflow (#3653) @HiroyasuNishiyama + - Fix conversion of junction to subflow (#3652) @HiroyasuNishiyama + - Fix to include junction to exported nodes (#3650) @HiroyasuNishiyama + - Fix z-index value for shade to cover nodes in palette (#3649) @kazuhitoyokoi + - Fix to extend escaped subflow category characters (#3647) @HiroyasuNishiyama + - Fix to sanitize tab name (#3646) @HiroyasuNishiyama + - Fix selector placement (#3644) @bonanitech + - Add Japanese translations for v3.0-beta.2 (#3622) @kazuhitoyokoi + - Fix new folder menu of save to library dialog (#3633) @HiroyasuNishiyama + - Fix layer of palette node (#3638) @HiroyasuNishiyama + - Fix to place a node dragged from palette within the workspace (#3637) @HiroyasuNishiyama + - Fix typo in CSS (#3628) @bonanitech + - Use the correct variable for the gutter text color (#3615) @bonanitech + + +Runtime + + - Support loading node modules from `nodesdir` (#3676) @Steve-Mcl + - fix buffer parse error message of evaluateNodeProperty (#3624) @HiroyasuNishiyama + +Nodes + + - File: Further simplify file node filename entry UX (v3) (#3677) @Steve-Mcl + - Function: Fix initial cursor position of init/finalize tab of function node (#3674) @HiroyasuNishiyama + - Function: Fix ESM module loading in Function node (#3645) @knolleary + - Inject: Fix JSONata evaluation of inject button (#3632) @HiroyasuNishiyama + - TCP: Dont delete TCP socket twice (#3630) @Steve-Mcl + - MQTT Node: define noproxy variable (#3626) @Steve-Mcl + - Debug: i18n debug sidebar node label (#3623) @HiroyasuNishiyama + #### 3.0.0-beta.2: Beta Release **Migration from 2.x** diff --git a/package.json b/package.json index d22cf89dd..854626fb7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index 793c5e516..7c403ac4b 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-api", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/util": "3.0.0-beta.2", - "@node-red/editor-client": "3.0.0-beta.2", + "@node-red/util": "3.0.0-beta.3", + "@node-red/editor-client": "3.0.0-beta.3", "bcryptjs": "2.4.3", "body-parser": "1.20.0", "clone": "2.1.2", diff --git a/packages/node_modules/@node-red/editor-client/package.json b/packages/node_modules/@node-red/editor-client/package.json index 4380f5170..2a32cbded 100644 --- a/packages/node_modules/@node-red/editor-client/package.json +++ b/packages/node_modules/@node-red/editor-client/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-client", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index 2a0d7e7c1..f0a3b0b1b 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/nodes", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index 29e1b8dd6..43ab2f00c 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/registry", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,7 +16,7 @@ } ], "dependencies": { - "@node-red/util": "3.0.0-beta.2", + "@node-red/util": "3.0.0-beta.3", "clone": "2.1.2", "fs-extra": "10.1.0", "semver": "7.3.7", diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json index 7ee872153..dcf7bf47c 100644 --- a/packages/node_modules/@node-red/runtime/package.json +++ b/packages/node_modules/@node-red/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/runtime", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/registry": "3.0.0-beta.2", - "@node-red/util": "3.0.0-beta.2", + "@node-red/registry": "3.0.0-beta.3", + "@node-red/util": "3.0.0-beta.3", "async-mutex": "0.3.2", "clone": "2.1.2", "express": "4.18.1", diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index 302e6f84e..3b4f90418 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/util", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/node-red/package.json b/packages/node_modules/node-red/package.json index e089fea71..36207c17e 100644 --- a/packages/node_modules/node-red/package.json +++ b/packages/node_modules/node-red/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "3.0.0-beta.2", + "version": "3.0.0-beta.3", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "flow" ], "dependencies": { - "@node-red/editor-api": "3.0.0-beta.2", - "@node-red/runtime": "3.0.0-beta.2", - "@node-red/util": "3.0.0-beta.2", - "@node-red/nodes": "3.0.0-beta.2", + "@node-red/editor-api": "3.0.0-beta.3", + "@node-red/runtime": "3.0.0-beta.3", + "@node-red/util": "3.0.0-beta.3", + "@node-red/nodes": "3.0.0-beta.3", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "express": "4.18.1", From 83655a749c4eb3b410ec261ae1d908b3671f96ca Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 16 Jun 2022 17:01:11 +0100 Subject: [PATCH 007/237] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 254e6a977..87fcc30bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Editor + - Add Right-Click content menu (#3678) @knolleary - Fix disable junction (#3671) @HiroyasuNishiyama - Add Japanese translations for v2.2.3 (#3672) @kazuhitoyokoi - Reset mouse state when switching tabs (#3643) @knolleary From 1844633ff1f22fed00b4b7cd0fcda3cb29cc1fd0 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 16 Jun 2022 22:56:54 +0100 Subject: [PATCH 008/237] Include scroll offset when positioning quick-add dialog Fixes #3684 --- .../node_modules/@node-red/editor-client/src/js/ui/view.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 9f28d7191..522b86cbc 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -1071,8 +1071,8 @@ RED.view = (function() { var oy = point[1]; const offset = $("#red-ui-workspace-chart").offset() - var clientX = ox + offset.left - var clientY = oy + offset.top + var clientX = ox + offset.left - $("#red-ui-workspace-chart").scrollLeft() + var clientY = oy + offset.top - $("#red-ui-workspace-chart").scrollTop() if (RED.settings.get("editor").view['view-snap-grid']) { // eventLayer.append("circle").attr("cx",point[0]).attr("cy",point[1]).attr("r","2").attr('fill','red') From 53184715bc2568440efba79109fc203b2569f93c Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 16 Jun 2022 23:18:02 +0100 Subject: [PATCH 009/237] Fix menu padding to handle both icons and submenus Fixes #3683 --- .../editor-client/src/js/ui/common/menu.js | 23 ++++++++++++++++++- .../editor-client/src/sass/dropdownMenu.scss | 14 ++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js index 6327f5268..c5b8d1d06 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js @@ -133,6 +133,8 @@ RED.menu = (function() { if (opt.options) { item.addClass("red-ui-menu-dropdown-submenu"+(opt.direction!=='right'?" pull-left":"")); var submenu = $('
    ').appendTo(item); + var hasIcons = false + var hasSubmenus = false for (var i=0;i li > a, & > li > a:focus { display: block; - padding: 4px 20px 4px 12px; + padding: 4px 12px 4px 32px; clear: both; font-weight: normal; line-height: 20px; @@ -58,6 +58,18 @@ & > li.pull-left > a:focus { padding: 4px 12px 4px 32px; } + &.red-ui-menu-dropdown-noicons > li > a, + &.red-ui-menu-dropdown-noicons > li > a:focus { + padding: 4px 12px 4px 12px; + } + + &.red-ui-menu-dropdown-submenus > li > a, + &.red-ui-menu-dropdown-submenus > li > a:focus { + padding-right: 20px; + } + + + & > .active > a, & > .active > a:hover, & > .active > a:focus { From e3d6d242ac5396a2d7ec3f0bcf235e3eedbaf345 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 16 Jun 2022 23:29:46 +0100 Subject: [PATCH 010/237] Fix handling of spacebar inside JSON visual editor Fixes #3682 The treeList keyboard handling was consuming the event - used to select the item in the list. The fix here adds a 'selectable' flag on the treeList that can be used to disabled (=false) the keyboard selection of items. --- .../@node-red/editor-client/src/js/ui/common/treeList.js | 2 ++ .../@node-red/editor-client/src/js/ui/editors/json.js | 1 + 2 files changed, 3 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js index 3b6e2cde3..85c6f0992 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js @@ -21,6 +21,7 @@ * - multi : boolean - if true, .selected will return an array of results * otherwise, returns the first selected item * - sortable: boolean/string - TODO: see editableList + * - selectable: boolean - default true - whether individual items can be selected * - rootSortable: boolean - if 'sortable' is set, then setting this to * false, prevents items being sorted to the * top level of the tree @@ -118,6 +119,7 @@ switch(evt.keyCode) { case 32: // SPACE case 13: // ENTER + if (!that.options.selectable) { return } if (evt.altKey || evt.ctrlKey || evt.metaKey || evt.shiftKey) { return } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/json.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/json.js index 8531ba2c5..394350f26 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/json.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/json.js @@ -534,6 +534,7 @@ var container = $("#red-ui-editor-type-json-tab-ui-container").css({"height":"100%"}); var filterDepth = Infinity; var list = $('
    ').appendTo(container).treeList({ + selectable: false, rootSortable: false, sortable: ".red-ui-editor-type-json-editor-item-handle", }).on("treelistchangeparent", function(event, evt) { From 0000f2a34d274d0785d6d7c6a8021ff38d8000fa Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sat, 18 Jun 2022 00:51:05 +0900 Subject: [PATCH 011/237] Support i18n in context menu --- .../@node-red/editor-client/locales/en-US/editor.json | 6 ++++++ .../@node-red/editor-client/locales/ja/editor.json | 6 ++++++ .../@node-red/editor-client/src/js/ui/contextMenu.js | 8 ++++---- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index 01107d49d..e3028272b 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -1187,5 +1187,11 @@ "missing-config": "__prop__: missing configuration node", "validation-error": "__prop__: validation error: __node__, __id__: __error__" } + }, + "contextMenu": { + "insert": "Insert", + "node": "Node", + "junction": "Junction", + "linkNodes": "Link Nodes" } } diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index 4c918b615..a78fb7d24 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -1188,6 +1188,12 @@ "validation-error": "__prop__: チェックエラー: __node__, __id__: __error__" } }, + "contextMenu": { + "insert": "挿入", + "node": "ノード", + "junction": "分岐点", + "linkNodes": "Linkノード" + }, "action-list": { "toggle-show-tips": "ヒント表示切替", "show-about": "Node-REDの説明を表示", diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js index 379ed5433..9920789f5 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js @@ -48,10 +48,10 @@ RED.contextMenu = (function() { const menuItems = [ { onselect: 'core:show-action-list', onpostselect: function() {} }, { - label: 'Insert', + label: RED._("contextMenu.insert"), options: [ { - label: 'Node', + label: RED._("contextMenu.node"), onselect: function() { RED.view.showQuickAddDialog({ position: [ options.x - offset.left, options.y - offset.top ], @@ -62,12 +62,12 @@ RED.contextMenu = (function() { } }, { - label: 'Junction', + label: RED._("contextMenu.junction"), onselect: 'core:split-wires-with-junctions', disabled: hasSelection || !hasLinks }, { - label: 'Link Nodes', + label: RED._("contextMenu.linkNodes"), onselect: 'core:split-wire-with-link-nodes', disabled: hasSelection || !hasLinks } From 418b3ee7d68f280e30340032b28df97106a8d87e Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sat, 18 Jun 2022 00:56:34 +0900 Subject: [PATCH 012/237] Fix Japanese translations to be same as others --- .../@node-red/editor-client/locales/ja/editor.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index a78fb7d24..70cb0aa22 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -1308,7 +1308,7 @@ "search": "検索", "search-previous": "前を検索", "search-next": "次を検索", - "show-action-list": "アクション一覧を表示", + "show-action-list": "動作一覧を表示", "confirm-edit-tray": "編集を完了", "cancel-edit-tray": "編集をキャンセル", "show-remote-diff": "リモートとの変更差分を表示", @@ -1330,6 +1330,6 @@ "zoom-out": "ズームアウト", "zoom-reset": "ズームリセット", "toggle-navigator": "ナビゲータ表示切替", - "show-system-info": "システムインフォメーション" + "show-system-info": "システム情報" } } From 3ad95bec3ca553ddd35e48e95aaf171e504d7a97 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sat, 18 Jun 2022 00:57:30 +0900 Subject: [PATCH 013/237] Add Japanese translation for file nodes --- packages/node_modules/@node-red/nodes/locales/ja/messages.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/node_modules/@node-red/nodes/locales/ja/messages.json b/packages/node_modules/@node-red/nodes/locales/ja/messages.json index c508a3e76..6e16daa6f 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ja/messages.json @@ -928,6 +928,7 @@ "write": "write file", "read": "read file", "filename": "ファイル名", + "path": "パス", "action": "動作", "addnewline": "メッセージの入力のたびに改行を追加", "createdir": "ディレクトリが存在しない場合は作成", From 01d9affe61efbf35fdeb304985dfc5061d24299e Mon Sep 17 00:00:00 2001 From: cow0w Date: Fri, 17 Jun 2022 22:18:14 +0300 Subject: [PATCH 014/237] Add support for evalulating {{env.}} within a template node --- .../nodes/core/function/80-template.js | 24 +++++++++++++++++++ test/nodes/core/function/80-template_spec.js | 22 +++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/packages/node_modules/@node-red/nodes/core/function/80-template.js b/packages/node_modules/@node-red/nodes/core/function/80-template.js index d4c27cfd0..69954aba2 100644 --- a/packages/node_modules/@node-red/nodes/core/function/80-template.js +++ b/packages/node_modules/@node-red/nodes/core/function/80-template.js @@ -44,6 +44,14 @@ module.exports = function(RED) { return undefined; } + function parseEnv(key) { + var match = /^env\.(.+)/.exec(key); + if (match) { + return match[1]; + } + return undefined; + } + /** * Custom Mustache Context capable to collect message property and node * flow and global context @@ -74,6 +82,11 @@ module.exports = function(RED) { return value; } + // try env + if (parseEnv(name)) { + return this.cachedContextTokens[name]; + } + // try flow/global context: var context = parseContext(name); if (context) { @@ -156,6 +169,17 @@ module.exports = function(RED) { var tokens = extractTokens(mustache.parse(template)); var resolvedTokens = {}; tokens.forEach(function(name) { + var env_name = parseEnv(name); + if (env_name) { + var promise = new Promise((resolve, reject) => { + var val = RED.util.evaluateNodeProperty(env_name, 'env', node) + resolvedTokens[name] = val; + resolve(); + }); + promises.push(promise); + return; + } + var context = parseContext(name); if (context) { var type = context.type; diff --git a/test/nodes/core/function/80-template_spec.js b/test/nodes/core/function/80-template_spec.js index e944824b3..b4f530d05 100644 --- a/test/nodes/core/function/80-template_spec.js +++ b/test/nodes/core/function/80-template_spec.js @@ -144,6 +144,28 @@ describe('template node', function() { }); }); + describe('env var', function() { + before(function() { + process.env.TEST = 'xyzzy'; + }) + after(function() { + delete process.env.TEST; + }) + + it('should modify payload from env variable', function(done) { + var flow = [{id:"n1",z:"t1", type:"template", field:"payload", template:"payload={{env.TEST}}",wires:[["n2"]]},{id:"n2",z:"t1",type:"helper"}]; + helper.load(templateNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + msg.should.have.property('payload', 'payload=xyzzy'); + done(); + }); + n1.receive({payload:"foo",topic: "bar"}); + }); + }); + }); + it('should modify payload from flow context', function(done) { var flow = [{id:"n1",z:"t1", type:"template", field:"payload", template:"payload={{flow.value}}",wires:[["n2"]]},{id:"n2",z:"t1",type:"helper"}]; helper.load(templateNode, flow, function() { From 75c9353cbddbbeb7203ddc5ebb24dc3fb8b0b0ea Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sun, 19 Jun 2022 01:23:28 +0900 Subject: [PATCH 015/237] Fix keys to insert junction --- .../node_modules/@node-red/editor-client/src/tours/welcome.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js index 4b6f5f66d..1289e6bee 100644 --- a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js +++ b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js @@ -34,7 +34,7 @@ export default { "en-US": `

    To make it easier to route wires around your flows, it is now possible to add junction nodes that give you more control.

    -

    Junctions can be added to wires by holding the Alt key +

    Junctions can be added to wires by holding both the Alt key and the Shift key then click and drag the mouse across the wires.

    `, // "ja": `

    フローのワイヤーの経路をより制御しやすくするために、分岐点ノードを追加できるようになりました。

    //

    シフトキーを押しながら、マウスの右ボタンをクリックし、ワイヤーを横切るようにドラッグすることで、分岐点を追加できます。

    ` From 13885493acbc09d0578894717fff65daaf6e3a61 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sun, 19 Jun 2022 01:26:48 +0900 Subject: [PATCH 016/237] Update Japanese translations in welcome tour --- .../@node-red/editor-client/src/tours/welcome.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js index 1289e6bee..98d83c7e5 100644 --- a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js +++ b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js @@ -9,19 +9,22 @@ export default { }, description: { "en-US": "

    This is the final beta release of Node-RED 3.0.

    Let's take a moment to discover the new features in this release.

    ", - // "ja": "

    これはNode-RED 3.0の最初のベータリリースです。これには、最終リリースで計画しているほぼ全ての機能が含まれています。

    本リリースの新機能を見つけてみましょう。

    " + "ja": "

    これはNode-RED 3.0の最後のベータリリースです。

    本リリースの新機能を見つけてみましょう。

    " } }, { title: { - "en-US": "Context Menu" + "en-US": "Context Menu", + "ja": "コンテキストメニュー" }, image: 'images/context-menu.png', description: { "en-US": `

    The editor now has its own context menu when you right-click in the workspace.

    This makes many of the built-in actions much easier - to access.

    ` + to access.

    `, + "ja": `

    ワークスペースで右クリックすると、エディタに独自のコンテキストメニューが表示されるようになりました。

    +

    これによって多くの組み込み動作を、より簡単に利用できます。

    ` } }, { @@ -36,8 +39,8 @@ export default { you more control.

    Junctions can be added to wires by holding both the Alt key and the Shift key then click and drag the mouse across the wires.

    `, - // "ja": `

    フローのワイヤーの経路をより制御しやすくするために、分岐点ノードを追加できるようになりました。

    - //

    シフトキーを押しながら、マウスの右ボタンをクリックし、ワイヤーを横切るようにドラッグすることで、分岐点を追加できます。

    ` + "ja": `

    フローのワイヤーの経路をより制御しやすくするために、分岐点ノードを追加できるようになりました。

    +

    Altキーとシフトキーを押しながらマウスをクリックし、ワイヤーを横切るようにドラッグすることで、分岐点を追加できます。

    ` }, }, { From 643541eebdc4394937c0038e5582ff21586a9589 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sun, 19 Jun 2022 01:37:55 +0900 Subject: [PATCH 017/237] Remove unnecessary spaces --- .../@node-red/nodes/locales/ja/function/10-function.html | 2 +- .../@node-red/nodes/locales/ja/network/10-mqtt.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/locales/ja/function/10-function.html b/packages/node_modules/@node-red/nodes/locales/ja/function/10-function.html index a18e5e8a8..960b755f6 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/function/10-function.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/function/10-function.html @@ -28,7 +28,7 @@

    返却/sendの対象は次のとおりです:

    • 単一メッセージオブジェクト - 最初の出力に接続されたノードに渡されます
    • -
    • メッセージオブジェクトの配列 - 対応する出力に接続されたノードに渡されます
    • +
    • メッセージオブジェクトの配列 - 対応する出力に接続されたノードに渡されます

    注: 初期化処理の実行はノードの初期化中に行われます。そのため、初期化処理タブにsendを記述した場合に後続ノードでメッセージを受け取れないことがあります。

    配列要素が配列の場合には、複数のメッセージを対応する出力に送出します。

    diff --git a/packages/node_modules/@node-red/nodes/locales/ja/network/10-mqtt.html b/packages/node_modules/@node-red/nodes/locales/ja/network/10-mqtt.html index 1b43ea097..435829e1e 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/network/10-mqtt.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/network/10-mqtt.html @@ -89,7 +89,7 @@
    userProperties オブジェクト
    MQTTv5: メッセージのユーザプロパティ
    messageExpiryInterval 数値
    -
    MQTTv5: 秒単位のメッセージの有効期限
    +
    MQTTv5: 秒単位のメッセージの有効期限
    topicAlias 数値
    MQTTv5: 使用するMQTTトピックエイリアス
    From 5e592427e940bd095440bb2ea0bf65636fe30194 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sun, 19 Jun 2022 01:56:59 +0900 Subject: [PATCH 018/237] Add Japanese translation in action list for junction --- .../@node-red/editor-client/locales/ja/editor.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index 70cb0aa22..ac09a1e87 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -1330,6 +1330,7 @@ "zoom-out": "ズームアウト", "zoom-reset": "ズームリセット", "toggle-navigator": "ナビゲータ表示切替", - "show-system-info": "システム情報" + "show-system-info": "システム情報", + "split-wires-with-junctions": "分岐点によりワイヤーを分割" } } From 6c2297c3659ddbd5cc784ba07e97680d4816eaea Mon Sep 17 00:00:00 2001 From: Dennis Neufeld Date: Sat, 18 Jun 2022 19:41:46 +0200 Subject: [PATCH 019/237] Update german translation --- .../editor-client/locales/de/editor.json | 191 ++++++++++++------ 1 file changed, 131 insertions(+), 60 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/de/editor.json b/packages/node_modules/@node-red/editor-client/locales/de/editor.json index 11d8e5d96..dd931a812 100755 --- a/packages/node_modules/@node-red/editor-client/locales/de/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/de/editor.json @@ -44,7 +44,8 @@ "loadNodes": "Lade Nodes __count__", "loadFlows": "Lade Flows", "importFlows": "Füge Flows dem Arbeitsbereich hinzu", - "importError": "

    Fehler beim Laden von Flows.

    __message__

    " + "importError": "

    Fehler beim Laden von Flows.

    __message__

    ", + "loadingProject": "Lade Projekt" }, "workspace": { "defaultName": "Flow __number__", @@ -53,7 +54,16 @@ "delete": "Sind Sie sicher, dass '__label__' gelöscht werden soll?", "dropFlowHere": "Hier kann der Flow eingefügt werden", "addFlow": "Flow hinzufügen", + "addFlowToRight": "Flow zum Arbeitsbereich rechts hinzufügen", + "hideFlow": "Flow ausblenden", + "hideOtherFlows": "Andere Flows ausblenden", + "showAllFlows": "Alle Flows anzeigen", + "hideAllFlows": "Alle Flows ausblenden", + "hiddenFlows": "Liste __count__ ausgeblendeten Flow auf", + "hiddenFlows_plural": "Liste __count__ ausgeblendete Flows auf", + "showLastHiddenFlow": "Letzten ausgeblendeten Flow anzeigen", "listFlows": "Flows auflisten", + "listSubflows": "Subflows auflisten", "status": "Status", "enabled": "Aktiviert", "disabled": "Deaktiviert", @@ -65,6 +75,8 @@ "view": { "view": "Ansicht", "grid": "Raster", + "storeZoom": "Zoomstufe beim Laden wiederherstellen", + "storePosition": "Scrollposition beim Laden wiederherstellen", "showGrid": "Raster anzeigen", "snapGrid": "Am Raster ausrichten", "gridSize": "Rastergröße", @@ -82,8 +94,9 @@ "palette": { "show": "Palette anzeigen" }, + "edit": "Bearbeiten", "settings": "Einstellungen", - "userSettings": "Einstellungen", + "userSettings": "Benutzereinstellungen", "nodes": "Nodes", "displayStatus": "Node-Status anzeigen", "displayConfig": "Konfigurations-Nodes", @@ -92,7 +105,7 @@ "search": "Flows durchsuchen", "searchInput": "Flows durchsuchen", "subflows": "Subflow", - "createSubflow": "Erstellen", + "createSubflow": "Subflow erstellen", "selectionToSubflow": "Auswahl in Subflow umwandeln", "flows": "Flow", "add": "Hinzufügen", @@ -104,24 +117,42 @@ "editPalette": "Palette verwalten", "other": "Sonstige", "showTips": "Tipps anzeigen", + "showWelcomeTours": "Geführte Touren für neue Versionen anzeigen", "help": "Node-RED-Website", - "projects": "Projekt", + "projects": "Projekte", "projects-new": "Neu", "projects-open": "Öffnen", - "projects-settings": "Einstellungen", + "projects-settings": "Projekteinstellungen", "showNodeLabelDefault": "Zeige Namen von neu hinzugefügten Nodes", - "groups": "Gruppe", + "codeEditor": "Code-Editor", + "groups": "Gruppen", "groupSelection": "Auswahl gruppieren", "ungroupSelection": "Gruppe auflösen", "groupMergeSelection": "Auswahl der Gruppe hinzufügen", - "groupRemoveSelection": "Auswahl aus der Gruppe entfernen" + "groupRemoveSelection": "Auswahl aus der Gruppe entfernen", + "arrange": "Anordnen", + "alignLeft": "Links ausrichten", + "alignCenter": "Zentrieren", + "alignRight": "Rechts ausrichten", + "alignTop": "Oben ausrichten", + "alignMiddle": "Mittig ausrichten", + "alignBottom": "Unten ausrichten", + "distributeHorizontally": "Horizontal verteilen", + "distributeVertically": "Vertikal verteilen", + "moveToBack": "Nach hinten verschieben", + "moveToFront": "Nach vorne verschieben", + "moveBackwards": "Rückwärts verschieben", + "moveForwards": "Vorwärts verschieben" } }, "actions": { "toggle-navigator": "Navigator ein-/ausblenden", "zoom-out": "Verkleinern", "zoom-reset": "Vergrößerung rücksetzen", - "zoom-in": "Vergrößern" + "zoom-in": "Vergrößern", + "search-flows": "Flows durchsuchen", + "search-prev": "Vorherige", + "search-next": "Nächste" }, "user": { "loggedInAs": "Angemeldet als __name__", @@ -131,7 +162,7 @@ "loginFailed": "Anmeldung fehlgeschlagen", "notAuthorized": "Nicht berechtigt", "errors": { - "settings": "Sie müssen angemeldet sein, um auf die Einstellungen zuzugreifen zu können", + "settings": "Sie müssen angemeldet sein, um auf die Einstellungen zugreifen zu können", "deploy": "Sie müssen angemeldet sein, um Änderungen übernehmen (deploy) zu können", "notAuthorized": "Sie müssen angemeldet sein, um diese Aktion ausführen zu können" } @@ -141,7 +172,7 @@ "warnings": { "undeployedChanges": "Node hat nicht übernommene (deploy) Änderungen", "nodeActionDisabled": "Node-Aktionen deaktiviert", - "nodeActionDisabledSubflow": "Node-Aktionen deaktiviert im Subflow", + "nodeActionDisabledSubflow": "Node-Aktionen innerhalb des Subflows deaktiviert", "missing-types": "

    Flows gestoppt aufgrund fehlender Node-Typen

    ", "missing-modules": "

    Flows angehalten aufgrund fehlender Module

    ", "safe-mode": "

    Flows sind im abgesicherten Modus gestoppt.

    Flows können bearbeitet und übernommen (deploy) werden, um sie neu zu starten.

    ", @@ -157,21 +188,21 @@ "error": "Fehler: __message__", "errors": { "lostConnection": "Verbindung zum Server verloren. Verbindung wird erneut hergestellt ...", - "lostConnectionReconnect": "Verbindung zum Server verloren. Verbindung wird in __time__ s versucht wieder herzustellen.", - "lostConnectionTry": "Jetzt testen", + "lostConnectionReconnect": "Verbindung zum Server verloren. Wiederherstellung der Verbindung in __time__s.", + "lostConnectionTry": "Jetzt versuchen", "cannotAddSubflowToItself": "Subflow kann nicht zu sich selbst hinzugefügt werden", "cannotAddCircularReference": "Subflow kann nicht hinzugefügt werden, da ein zirkulärer Bezug erkannt wurde", - "unsupportedVersion": "

    Nicht unterstützte Version von Node.js erkannt.

    Es muss ein Upgrade auf das neueste LTS-Release von Node.js durchgeführt werden.

    ", + "unsupportedVersion": "

    Nicht unterstützte Version von Node.js erkannt.

    Es sollte ein Upgrade auf das neueste LTS-Release von Node.js durchgeführt werden.

    ", "failedToAppendNode": "

    Fehler beim Laden von '__module__'.

    __error__

    " }, "project": { - "change-branch": "Wechsel in den Branch '__project__'", - "merge-abort": "Merge abgebrochen", + "change-branch": "Wechsel in den lokalen Branch '__project__'", + "merge-abort": "Git-Merge abgebrochen", "loaded": "Projekt '__project__' geladen", "updated": "Projekt '__project__' aktualisiert", "pull": "Projekt '__project__' erneut geladen", "revert": "Änderungen im Projekt '__project__' rückgängig gemacht", - "merge-complete": "Merge abgeschlossen", + "merge-complete": "Git-Merge abgeschlossen", "setupCredentials": "Berechtigungen einrichten", "setupProjectFiles": "Projektdateien einrichten", "no": "Nein, Danke", @@ -186,7 +217,7 @@ "no-thanks": "Nein, Danke", "create-default-project": "Standardprojektdateien erstellen", "show-merge-conflicts": "Merge-Konflikte anzeigen", - "unknownNodesButton": "Finden Sie unbekannte nodes" + "unknownNodesButton": "Nach unbekannten Nodes suchen" } }, "clipboard": { @@ -204,17 +235,17 @@ "subflow_plural": "__count__ Subflows", "replacedNodes": "__count__ Node ersetzt", "replacedNodes_plural": "__count__ Nodes ersetzt", - "pasteNodes": "Flow-JSON hier einfügen oder", + "pasteNodes": "Flow-JSON einfügen oder", "selectFile": "Datei für Import auswählen", - "importNodes": "Import", - "exportNodes": "Export", + "importNodes": "Importiere Nodes", + "exportNodes": "Exportiere Nodes", "download": "Download", - "importUnrecognised": "Importierter Typ nicht erkannt:", - "importUnrecognised_plural": "Importierte Typen nicht erkannt:", - "importDuplicate": "Importiertes doppeltes Node:", - "importDuplicate_plural": "Importierte doppelte Nodes:", - "nodesExported": "Nodes in der Zwischenablage abgelegt", - "nodesImported": "Eingefügt:", + "importUnrecognised": "Nicht erkannter Typ importiert:", + "importUnrecognised_plural": "Nicht erkannte Typen importiert:", + "importDuplicate": "Doppelte Node importiert:", + "importDuplicate_plural": "Doppelte Nodes importiert:", + "nodesExported": "Nodes in die Zwischenablage exportiert", + "nodesImported": "Importiert:", "nodeCopied": "__count__ Node kopiert", "nodeCopied_plural": "__count__ Nodes kopiert", "groupCopied": "__count__ Gruppe kopiert", @@ -230,11 +261,11 @@ "all": "Alle Flows", "compact": "Kompakt", "formatted": "Formatiert", - "copy": "In Zwischenablage exportieren", + "copy": "In Zwischenablage kopieren", "export": "In Bibliothek exportieren", "exportAs": "Exportiere als", "overwrite": "Ersetzen", - "exists": "

    '__file__' existiert bereits.

    Soll sie ersetzt werden?

    " + "exists": "

    \"__file__\" existiert bereits.

    Soll sie ersetzt werden?

    " }, "import": { "import": "Importiere in", @@ -270,9 +301,9 @@ "successfulRestart": "Flows erfolgreich neugestartet", "deployFailed": "Übernahme (deploy) fehlgeschlagen: __message__", "unusedConfigNodes": "Einige Konfigurations-Nodes werden nicht verwendet.", - "unusedConfigNodesButton":"Finden Sie ungenutzte konfig nodes", - "unknownNodesButton":"Finden Sie unbekannte nodes", - "invalidNodesButton":"Finden Sie ungültige nodes", + "unusedConfigNodesButton": "Suche nach unbenutzten Konfigurations-Nodes", + "unknownNodesButton": "Suche nach unbekannten Nodes", + "invalidNodesButton": "Suche nach ungültigen Nodes", "errors": { "noResponse": "Keine Antwort vom Server" }, @@ -355,10 +386,10 @@ "keys": "Schlüsselwörter", "keysPlaceholder": "Komma-getrennte Schlüsselwörter", "author": "Author", - "authorPlaceholder": "Dein Name ", + "authorPlaceholder": "Ihr Name ", "desc": "Beschreibung", "env": { - "restore": "Stelle auf Subflow-Standard zurück", + "restore": "Subflow-Standard wiederherstellen", "remove": "Entferne Umgebungsvariable" }, "errors": { @@ -367,9 +398,9 @@ } }, "group": { - "editGroup": "Editiere Gruppe: __name__", + "editGroup": "Bearbeite Gruppe: __name__", "errors": { - "cannotCreateDiffGroups": "Kann keine Gruppe erzeugen mit Nodes von verschiedenen Gruppen", + "cannotCreateDiffGroups": "Kann keine Gruppe mit Nodes von anderen Gruppen erstellen", "cannotAddSubflowPorts": "Kann keine Subflow-Anschlüsse zu einer Gruppe hinzufügen" } }, @@ -383,7 +414,7 @@ "addNewConfig": "Neuen Konfigurations-Node '__type__' hinzufügen", "editNode": "Node '__type__' bearbeiten", "editConfig": "Konfigurations-Node '__type__' bearbeiten", - "addNewType": "Neuen Typ '__type__' hinzufügen", + "addNewType": "Neuen Typ '__type__' hinzufügen ...", "nodeProperties": "Node-Eigenschaften", "label": "Name", "color": "Farbe", @@ -406,7 +437,7 @@ "loadCredentials": "Lade Node-Berechtigungen", "inputs": { "input": "Eingang", - "select": "Wähle", + "select": "Auswahl", "checkbox": "Checkbox", "spinner": "Spinner", "none": "Kein", @@ -449,23 +480,27 @@ "shortcut": "Tastenkürzel", "scope": "Geltungsbereich", "unassigned": "Nicht zugeordnet", - "global": "global", + "global": "Global", "workspace": "Arbeitsbereich", - "selectAll": "Alle Nodes auswählen", + "selectAll": "Alles auswählen", + "selectNone": "Alles abwählen", "selectAllConnected": "Alle verbundenen Nodes auswählen", "addRemoveNode": "Node aus Auswahl hinzufügen/entfernen", "editSelected": "Ausgewählten Node bearbeiten", "deleteSelected": "Ausgewählte Nodes oder Links löschen", - "importNode": "Node importieren", - "exportNode": "Node exportieren", + "importNode": "Nodes importieren", + "exportNode": "Nodes exportieren", "nudgeNode": "Ausgewählte Nodes verschieben (1px)", "moveNode": "Ausgewählte Nodes verschieben (20px)", "toggleSidebar": "Seitenleiste ein-/ausblenden", "togglePalette": "Palette ein-/ausblenden", "copyNode": "Ausgewählte Nodes kopieren", "cutNode": "Ausgewählte Nodes ausschneiden", - "pasteNode": "Node einfügen", + "pasteNode": "Nodes einfügen", + "copyGroupStyle": "Gruppenstil kopieren", + "pasteGroupStyle": "Gruppenstil einfügen", "undoChange": "Letzte Änderung rückgängig machen", + "redoChange": "Letzte Änderung wiederholen", "searchBox": "Suchfeld öffnen", "managePalette": "Palette verwalten", "actionList": "Aktionsliste" @@ -491,7 +526,7 @@ "palette": { "noInfo": "Keine Informationen verfügbar", "filter": "Nodes filtern", - "search": "Modules durchsuchen", + "search": "Module durchsuchen", "addCategory": "Neu hinzufügen ...", "label": { "subflows": "Subflows", @@ -508,8 +543,8 @@ "advanced": "Fortgeschritten" }, "actions": { - "collapse-all": "Kategorien einklappen", - "expand-all": "Kategorien ausklappen" + "collapse-all": "Alle Kategorien einklappen", + "expand-all": "Alle Kategorien ausklappen" }, "event": { "nodeAdded": "Node zur Palette hinzugefügt:", @@ -520,7 +555,8 @@ "nodeEnabled_plural": "Nodes aktiviert:", "nodeDisabled": "Node deaktiviert:", "nodeDisabled_plural": "Nodes deaktiviert:", - "nodeUpgraded": "Upgrade von Node-Modul __module__ auf Version __version__ durchgeführt" + "nodeUpgraded": "Upgrade von Node-Modul __module__ auf Version __version__ durchgeführt", + "unknownNodeRegistered": "Fehler beim Laden des Nodes:
    • __type__
      __error__
    " }, "editor": { "title": "Palette verwalten", @@ -636,7 +672,7 @@ "outline": "Entwurf", "empty": "leer", "globalConfig": "Globale Konfigurations-Nodes", - "triggerAction": "Auslösen", + "triggerAction": "Aktion auslösen", "find": "Suche im Arbeitsbereich" }, "help": { @@ -739,7 +775,7 @@ "userName": "Benutzername", "email": "E-Mail", "workflow": "Arbeitsablauf", - "workfowTip": "Wähle deinen bevorzugten Git-Arbeitsablauf", + "workfowTip": "Wählen Sie Ihren bevorzugten Git-Arbeitsablauf (Workflow)", "workflowManual": "Manuell", "workflowManualTip": "Alle Änderungen müssen manuell übertragen werden (commit) über die Seitenleiste 'Projekthistorie'", "workflowAuto": "Automatisch", @@ -858,6 +894,8 @@ "addTitle": "Element hinzufügen" }, "search": { + "history": "Suchhistorie", + "clear": "Leeren", "empty": "Keine Übereinstimmungen gefunden", "addNode": "Node hinzufügen ...", "options": { @@ -865,7 +903,10 @@ "unusedConfigNodes": "Unbenutzte Konfigurations-Nodes", "invalidNodes": "Ungültige Nodes", "uknownNodes": "Unbekannte Nodes", - "unusedSubflows": "Unbenutzte Subflows" + "unusedSubflows": "Unbenutzte Subflows", + "hiddenFlows": "Versteckte Flows", + "modifiedNodes": "Geänderte Nodes", + "thisFlow": "Aktueller Flow" } }, "expressionEditor": { @@ -887,6 +928,9 @@ "eval": "Fehler beim Auswerten des Ausdrucks\n__message__" } }, + "monaco": { + "setTheme": "Thema auswählen" + }, "jsEditor": { "title": "JavaScript-Editor" }, @@ -896,8 +940,10 @@ "jsonEditor": { "title": "JSON-Editor", "format": "JSON formatieren", - "rawMode": "JSON-Editor", + "rawMode": "Bearbeite JSON", "uiMode": "Visueller Editor", + "rawMode-readonly": "JSON", + "uiMode-readonly": "Visuell", "insertAbove": "Oberhalb einfügen", "insertBelow": "Unterhalb einfügen", "addItem": "Element hinzufügen", @@ -968,7 +1014,7 @@ "clone": "Projekt klonen", "desc0": "Wenn Sie bereits über ein Git-Repository verfügen, das ein Projekt enthält, können Sie es klonen, um damit zu arbeiten.", "already-exists": "Das Projekt ist bereits vorhanden", - "must-contain": "Darf nur A-Z 0-9 _ enthalten", + "must-contain": "Darf nur A-Z 0-9 _ - enthalten", "project-name": "Projektname", "no-info-in-url": "Geben Sie Benutzername & Passwort nicht innerhalb der URL vor", "git-url": "Git-Repository-URL", @@ -1026,7 +1072,7 @@ "desc2": "Im Tab 'Commit-Historie' in der Seitenleiste werden alle Dateien angezeigt, die sich in Ihrem Projekt geändert haben, und um sie ins lokale Repository zu übertragen (commit). Es zeigt Ihnen eine vollständige Historie Ihrer Commits an und ermöglicht es Ihnen, Ihre Commits in ein (remote) Server-Repository zu schieben (push)." }, "create": { - "projects": "Projekt", + "projects": "Projekte", "already-exists": "Das Projekt ist bereits vorhanden", "must-contain": "Darf nur A-Z 0-9 _ enthalten", "no-info-in-url": "Geben Sie Benutzername & Passwort nicht innerhalb der URL vor", @@ -1059,7 +1105,8 @@ "not-git": "Kein Git-Repository", "no-resource": "Repository nicht gefunden", "cant-get-ssh-key-path": "Fehler! Der ausgewählte SSH-Schlüsselpfad kann nicht abgerufen werden.", - "unexpected_error": "unerwarteter_Fehler" + "unexpected_error": "unerwarteter_Fehler", + "clearContext": "Kontextdaten löscshen beim Projektwechsel" }, "delete": { "confirm": "Sind Sie sicher, dass dieses Projekt gelöscht werden soll?" @@ -1106,13 +1153,37 @@ "preview": "Vorschau", "defaultValue": "Standardwert" }, + "tourGuide": { + "takeATour": "Tour starten", + "start": "Start", + "next": "Nächste" + }, + "diagnostics": { + "title": "System-Informationen" + }, "languages": { - "de": "German", - "en-US": "English", - "ja": "Japanese", - "ko": "Korean", - "ru": "Russian", - "zh-CN": "Chinese(Simplified)", - "zh-TW": "Chinese(Traditional)" + "de": "Deutsch", + "en-US": "Englisch", + "ja": "Japanisch", + "ko": "Koreanisch", + "ru": "Russisch", + "zh-CN": "Chinesisch (Vereinfacht)", + "zh-TW": "Chinesisch (Traditionell)" + }, + "validator": { + "errors": { + "invalid-json": "Ungültige JSON-Daten: __error__", + "invalid-json-prop": "__prop__: ungültige JSON-Daten: __error__", + "invalid-prop": "Ungültiger Eigenschaftsausdruck", + "invalid-prop-prop": "__prop__: ungültiger Eigenschaftsausdruck", + "invalid-num": "Ungültige Nummer", + "invalid-num-prop": "__prop__: ungültige Nummer", + "invalid-regexp": "Ungültiges Eingabemuster", + "invalid-regex-prop": "__prop__: ungültiges Eingabemuster", + "missing-required-prop": "__prop__: Eigenschaftswert fehlt", + "invalid-config": "__prop__: ungültige Konfigurations-Node", + "missing-config": "__prop__: Konfigurations-Node fehlt", + "validation-error": "__prop__: Validierungsfehler: __node__, __id__: __error__" + } } } From 3a2fa4073a4275f5b18a134c685ec82bf7e2423a Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Sat, 18 Jun 2022 16:05:25 -0400 Subject: [PATCH 020/237] Move all colours to CSS variables --- .../@node-red/editor-client/src/sass/ace.scss | 34 +-- .../editor-client/src/sass/base.scss | 44 ++-- .../editor-client/src/sass/colors.scss | 4 + .../editor-client/src/sass/debug.scss | 68 +++--- .../editor-client/src/sass/diff.scss | 188 +++++++-------- .../editor-client/src/sass/dragdrop.scss | 4 +- .../editor-client/src/sass/dropdownMenu.scss | 46 ++-- .../editor-client/src/sass/editor.scss | 178 +++++++------- .../editor-client/src/sass/flow.scss | 136 +++++------ .../editor-client/src/sass/forms.scss | 50 ++-- .../editor-client/src/sass/header.scss | 68 +++--- .../editor-client/src/sass/jquery.scss | 62 ++--- .../editor-client/src/sass/keyboard.scss | 22 +- .../editor-client/src/sass/library.scss | 36 +-- .../editor-client/src/sass/mixins.scss | 56 ++--- .../editor-client/src/sass/notifications.scss | 14 +- .../src/sass/palette-editor.scss | 54 ++--- .../editor-client/src/sass/palette.scss | 46 ++-- .../editor-client/src/sass/panels.scss | 18 +- .../editor-client/src/sass/popover.scss | 24 +- .../editor-client/src/sass/projects.scss | 206 ++++++++-------- .../editor-client/src/sass/radialMenu.scss | 20 +- .../editor-client/src/sass/search.scss | 64 ++--- .../editor-client/src/sass/sidebar.scss | 20 +- .../src/sass/style-custom-theme.scss | 18 ++ .../editor-client/src/sass/tab-config.scss | 32 +-- .../editor-client/src/sass/tab-context.scss | 4 +- .../editor-client/src/sass/tab-help.scss | 2 +- .../editor-client/src/sass/tab-info.scss | 88 +++---- .../editor-client/src/sass/tabs.scss | 96 ++++---- .../editor-client/src/sass/tourGuide.scss | 2 +- .../src/sass/ui/common/checkboxSet.scss | 4 +- .../src/sass/ui/common/editableList.scss | 16 +- .../src/sass/ui/common/nodeList.scss | 10 +- .../src/sass/ui/common/searchBox.scss | 14 +- .../src/sass/ui/common/stack.scss | 4 +- .../src/sass/ui/common/treeList.scss | 30 +-- .../src/sass/ui/common/typedInput.scss | 50 ++-- .../editor-client/src/sass/userSettings.scss | 4 +- .../editor-client/src/sass/variables.scss | 221 +++++++++++++++++- .../editor-client/src/sass/workspace.scss | 28 +-- .../src/sass/workspaceToolbar.scss | 16 +- scripts/build-custom-theme.js | 2 +- 43 files changed, 1162 insertions(+), 941 deletions(-) create mode 100644 packages/node_modules/@node-red/editor-client/src/sass/style-custom-theme.scss diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ace.scss b/packages/node_modules/@node-red/editor-client/src/sass/ace.scss index 4e913942e..5aad96b4d 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ace.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ace.scss @@ -2,48 +2,48 @@ .ace_read-only { .ace_scroller { - background: $text-editor-background-disabled; - color: $text-editor-color-disabled; + background: var(--red-ui-text-editor-background-disabled); + color: var(--red-ui-text-editor-color-disabled); } .ace_cursor { color: transparent !important; } } .ace_gutter { - background: $text-editor-gutter-background; + background: var(--red-ui-text-editor-gutter-background); border-top-left-radius: 4px; border-bottom-left-radius: 4px; } .ace_scroller { - background: $text-editor-background; + background: var(--red-ui-text-editor-background); border-top-right-radius: 4px; border-bottom-right-radius: 4px; - color: $text-editor-color; + color: var(--red-ui-text-editor-color); } .ace_marker-layer .ace_active-line { - background: $text-editor-active-line-background; + background: var(--red-ui-text-editor-active-line-background); } .ace_marker-layer .ace_selection { - background: $text-editor-selection-background; + background: var(--red-ui-text-editor-selection-background); border-radius: 1px; } .ace_gutter-cell { - color: $text-editor-gutter-color; + color: var(--red-ui-text-editor-gutter-color); } .ace_gutter-active-line { - background: $text-editor-gutter-active-line-background; + background: var(--red-ui-text-editor-gutter-active-line-background); } .ace_tooltip { - font-family: $primary-font; + font-family: var(--red-ui-primary-font); line-height: 1.4em; max-width: 400px; white-space: normal; background-image: none; - background: $popover-background; - color: $popover-color; + background: var(--red-ui-popover-background); + color: var(--red-ui-popover-color); border-radius: 4px; @include component-shadow; - border-color: $popover-background; + border-color: var(--red-ui-popover-background); } .ace_content { line-height: 1; @@ -55,14 +55,14 @@ #red-ui-event-log-editor { .ace_scroller { - background: $event-log-background; - color: $event-log-color; + background: var(--red-ui-event-log-background); + color: var(--red-ui-event-log-color); } .ace_marker-layer .ace_active-line { - background: $event-log-active-line-background; + background: var(--red-ui-event-log-active-line-background); } .ace_marker-layer .ace_selection { - background: $event-log-selection-background; + background: var(--red-ui-event-log-selection-background); } } } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/base.scss b/packages/node_modules/@node-red/editor-client/src/sass/base.scss index a08e752d4..92a98913f 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/base.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/base.scss @@ -20,12 +20,12 @@ body { } .red-ui-editor { - font-size: $primary-font-size; - font-family: $primary-font; + font-size: var(--red-ui-primary-font-size); + font-family: var(--red-ui-primary-font); padding: 0; margin: 0; - background: $primary-background; - color: $primary-text-color; + background: var(--red-ui-primary-background); + color: var(--red-ui-primary-text-color); line-height: 20px; } @@ -63,15 +63,15 @@ body { .red-ui-icon-picker { a { text-decoration: none; - color: $primary-text-color; + color: var(--red-ui-primary-text-color); } a:hover, a:focus { text-decoration: none; - color: $primary-text-color; + color: var(--red-ui-primary-text-color); } a:focus { - outline: 1px solid $form-input-focus-color; + outline: 1px solid var(--red-ui-form-input-focus-color); } p { @@ -130,7 +130,7 @@ body { hr { margin: 20px 0; border: 0; - border-top: 1px solid $tertiary-border-color; + border-top: 1px solid var(--red-ui-tertiary-border-color); } @@ -150,22 +150,22 @@ body { mask-position: 50% 50%; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; - background-color: $spinner-color; + background-color: var(--red-ui-spinner-color); } .red-ui-font-code { - font-family: $monospace-font; - font-size: $primary-font-size; - color: $text-color-code; + font-family: var(--red-ui-monospace-font); + font-size: var(--red-ui-primary-font-size); + color: var(--red-ui-text-color-code); white-space: nowrap; } code { - font-family: $monospace-font; - font-size: $primary-font-size; + font-family: var(--red-ui-monospace-font); + font-size: var(--red-ui-primary-font-size); padding: 0px; margin: 1px; - color: $text-color-code; + color: var(--red-ui-text-color-code); white-space: nowrap; } @@ -177,8 +177,8 @@ body { word-break: break-all; word-wrap: break-word; white-space: pre-wrap; - background-color:$tertiary-background; - border: 1px solid $tertiary-border-color; + background-color:var(--red-ui-tertiary-background); + border: 1px solid var(--red-ui-tertiary-border-color); border-radius: 2px; } @@ -217,8 +217,8 @@ body { blockquote { padding: 0 0 0 15px; margin: 0 0 20px; - border-left: 4px solid $secondary-border-color; - color: $secondary-text-color; + border-left: 4px solid var(--red-ui-secondary-border-color); + color: var(--red-ui-secondary-text-color); p { font-size: 14px; @@ -244,7 +244,7 @@ body { right: 1px; text-align: center; padding: 40px; - background: $secondary-background; + background: var(--red-ui-secondary-background); &:before { content: ''; display: inline-block; @@ -258,14 +258,14 @@ body { width: 80px; } &.red-ui-component-spinner-sidebar { - background: $secondary-background; + background: var(--red-ui-secondary-background); padding:0; img { width: 40px; } } &.projects-version-control-spinner-sidebar { - background: $secondary-background; + background: var(--red-ui-secondary-background); padding:0; img { width: 20px; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/colors.scss b/packages/node_modules/@node-red/editor-client/src/sass/colors.scss index d8b4fb175..66b3bd48e 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/colors.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/colors.scss @@ -112,9 +112,13 @@ $tab-text-color-disabled-inactive: $secondary-text-color-disabled-inactive; $tab-badge-color: $tertiary-text-color; $tab-background: $secondary-background; $tab-background-active: $secondary-background; +$tab-background-active-alpha: rgba($secondary-background, 0.001); $tab-background-selected: $secondary-background-selected; +$tab-background-selected-alpha: rgba($secondary-background-selected, 0.001); $tab-background-inactive: $secondary-background-inactive; +$tab-background-inactive-alpha: rgba($secondary-background-inactive, 0.001); $tab-background-hover: $secondary-background-hover; +$tab-background-hover-alpha: rgba($secondary-background-hover, 0.001); $palette-header-background: $primary-background; $palette-header-color: $header-text-color; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/debug.scss b/packages/node_modules/@node-red/editor-client/src/sass/debug.scss index 0c810c03f..58099877f 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/debug.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/debug.scss @@ -17,7 +17,7 @@ .red-ui-debug-window { padding:0; margin:0; - background: $secondary-background; + background: var(--red-ui-secondary-background); line-height: 20px; .red-ui-debug-msg-payload { font-size: 14px; @@ -38,15 +38,15 @@ left: 0px; right: 0px; z-index: 20; - background: $tertiary-background; + background: var(--red-ui-tertiary-background); padding: 10px; - border-bottom: 1px solid $secondary-border-color; - box-shadow: 0 2px 6px $shadow; + border-bottom: 1px solid var(--red-ui-secondary-border-color); + box-shadow: 0 2px 6px var(--red-ui-shadow); } #red-ui-sidebar-debug-filter-node-list-row { .red-ui-treeList-label.disabled { font-style: italic; - color: $secondary-text-color-disabled; + color: var(--red-ui-secondary-text-color-disabled); } .red-ui-treeList-label { @@ -57,22 +57,22 @@ background: inherit; } &.focus, &.focus .red-ui-treeList-sublabel-text { - background: $list-item-background-hover !important; + background: var(--red-ui-list-item-background-hover) !important; } } } .red-ui-debug-msg { position: relative; - border-bottom: 1px solid $debug-message-border; - border-left: 8px solid $debug-message-border; - border-right: 8px solid $debug-message-border; + border-bottom: 1px solid var(--red-ui-debug-message-border); + border-left: 8px solid var(--red-ui-debug-message-border); + border-right: 8px solid var(--red-ui-debug-message-border); padding: 2px; &>.red-ui-debug-msg-meta .red-ui-debug-msg-tools { display: none; } &.red-ui-debug-msg-hover { - border-right-color: $debug-message-border-hover; + border-right-color: var(--red-ui-debug-message-border-hover); &>.red-ui-debug-msg-meta .red-ui-debug-msg-tools { display: inline-block; } @@ -86,7 +86,7 @@ display: inline-block; } &:hover { - background: $debug-message-background-hover; + background: var(--red-ui-debug-message-background-hover); &>.red-ui-debug-msg-tools { .red-ui-debug-msg-tools-copy { display: inline-block; @@ -120,9 +120,9 @@ } .red-ui-debug-msg-meta { - background: $debug-message-background; + background: var(--red-ui-debug-message-background); font-size: 11px; - color: $secondary-text-color-inactive; + color: var(--red-ui-secondary-text-color-inactive); overflow-wrap: anywhere; } .red-ui-debug-msg-date { @@ -131,11 +131,11 @@ } .red-ui-debug-msg-topic { display: block; - color: $debug-message-text-color-meta; + color: var(--red-ui-debug-message-text-color-meta); } .red-ui-debug-msg-name { padding: 1px 0px; - color: $secondary-text-color-inactive; + color: var(--red-ui-secondary-text-color-inactive); white-space: nowrap; } .red-ui-debug-msg-tools { @@ -152,39 +152,39 @@ .red-ui-debug-msg-payload { display: block; padding: 2px; - background: $debug-message-background; - font-family: $monospace-font; + background: var(--red-ui-debug-message-background); + font-family: var(--red-ui-monospace-font); font-size: 13px !important; } .red-ui-debug-msg-level-log { - border-left-color: $debug-message-border; - border-right-color: $debug-message-border; + border-left-color: var(--red-ui-debug-message-border); + border-right-color: var(--red-ui-debug-message-border); } .red-ui-debug-msg-level-30 { - border-left-color: $debug-message-border-warning; - border-right-color: $debug-message-border-warning; + border-left-color: var(--red-ui-debug-message-border-warning); + border-right-color: var(--red-ui-debug-message-border-warning); } .red-ui-debug-msg-level-20 { - border-left-color: $debug-message-border-error; - border-right-color: $debug-message-border-error; + border-left-color: var(--red-ui-debug-message-border-error); + border-right-color: var(--red-ui-debug-message-border-error); } .red-ui-debug-msg-object-entry { position: relative; padding-left: 15px; } .red-ui-debug-msg-element { - color: $debug-message-text-color; + color: var(--red-ui-debug-message-text-color); line-height: 1.3em; overflow-wrap: break-word; } .red-ui-debug-msg-object-key { - color: $debug-message-text-color-object-key; + color: var(--red-ui-debug-message-text-color-object-key); } .red-ui-debug-msg-object-value { } .red-ui-debug-msg-object-handle { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); font-size: 1em; width: 1em; text-align: center; @@ -219,17 +219,17 @@ display:none; } .red-ui-debug-msg-object-entry pre { - font-family: $monospace-font; + font-family: var(--red-ui-monospace-font); font-size: 13px; line-height: 1.2em; margin: 0 0 0 -1em; } -.red-ui-debug-msg-type-other { color: $debug-message-text-color-msg-type-other; } -.red-ui-debug-msg-type-string { color: $debug-message-text-color-msg-type-string; } -.red-ui-debug-msg-type-null { color: $debug-message-text-color-msg-type-null; font-style: italic;} -.red-ui-debug-msg-type-meta { color: $debug-message-text-color-msg-type-meta; font-style: italic;} -.red-ui-debug-msg-type-number { color: $debug-message-text-color-msg-type-number; }; +.red-ui-debug-msg-type-other { color: var(--red-ui-debug-message-text-color-msg-type-other); } +.red-ui-debug-msg-type-string { color: var(--red-ui-debug-message-text-color-msg-type-string); } +.red-ui-debug-msg-type-null { color: var(--red-ui-debug-message-text-color-msg-type-null); font-style: italic;} +.red-ui-debug-msg-type-meta { color: var(--red-ui-debug-message-text-color-msg-type-meta); font-style: italic;} +.red-ui-debug-msg-type-number { color: var(--red-ui-debug-message-text-color-msg-type-number); } .red-ui-debug-msg-type-number-toggle { cursor: pointer;} .red-ui-debug-msg-type-string { @@ -241,14 +241,14 @@ padding: 4px 2px 2px; position: relative; &.red-ui-debug-msg-row-pinned { - background: $secondary-background-selected; + background: var(--red-ui-secondary-background-selected); } } .red-ui-debug-msg-expandable { cursor: pointer; } .red-ui-debug-msg-expandable:hover .red-ui-debug-msg-object-handle { - color:$secondary-text-color-hover; + color:var(--red-ui-secondary-text-color-hover); } .red-ui-debug-msg-buffer-opts { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/diff.scss b/packages/node_modules/@node-red/editor-client/src/sass/diff.scss index 38fd36252..a7b3d4dc8 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/diff.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/diff.scss @@ -23,11 +23,11 @@ .red-ui-editableList-container { border-radius:1px; padding:0; - background: $tertiary-background; + background: var(--red-ui-tertiary-background); } .red-ui-diff-list { li { - background: $tertiary-background; + background: var(--red-ui-tertiary-background); padding: 0px; border: none; min-height: 0; @@ -62,29 +62,29 @@ white-space: nowrap; text-overflow: ellipsis; width: 50%; - background: $tertiary-background; + background: var(--red-ui-tertiary-background); text-align: center; - border-top: 1px solid $secondary-border-color; - border-color:$secondary-border-color; - border-left: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); + border-color:var(--red-ui-secondary-border-color); + border-left: 1px solid var(--red-ui-secondary-border-color); } div:last-child { - border-right: 1px solid $secondary-border-color; + border-right: 1px solid var(--red-ui-secondary-border-color); } } .red-ui-diff-dialog-toolbar { box-sizing: border-box; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); text-align: right; padding: 8px 10px; - background: $primary-background; - border-bottom: 1px solid $secondary-border-color; + background: var(--red-ui-primary-background); + border-bottom: 1px solid var(--red-ui-secondary-border-color); white-space: nowrap; } .red-ui-diff-list-flow { - background: $secondary-background; - border: 1px solid $secondary-border-color; + background: var(--red-ui-secondary-background); + border: 1px solid var(--red-ui-secondary-border-color); border-radius: 1px; overflow: hidden; @@ -114,10 +114,10 @@ font-size: 0.9em; &:first-child { - border-top: 1px solid $tertiary-border-color; + border-top: 1px solid var(--red-ui-tertiary-border-color); } &:not(:last-child) { - border-bottom: 1px solid $tertiary-border-color; + border-bottom: 1px solid var(--red-ui-tertiary-border-color); } &.collapsed { @@ -150,8 +150,8 @@ width: 100%; } td, th { - border-top: 1px solid $secondary-border-color; - border-left: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); + border-left: 1px solid var(--red-ui-secondary-border-color); &:first-child { border-left: none; } @@ -166,7 +166,7 @@ overflow:hidden; } &:hover { - background: $secondary-background-selected; + background: var(--red-ui-secondary-background-selected); } } @@ -212,7 +212,7 @@ cursor: pointer; padding: 0; &:hover { - background: $secondary-background-selected; + background: var(--red-ui-secondary-background-selected); } } .red-ui-diff-list-flow-title-meta { @@ -223,7 +223,7 @@ .red-ui-diff-list-node-header { cursor: pointer; &:hover { - background: $secondary-background-selected; + background: var(--red-ui-secondary-background-selected); } } .red-ui-diff-list-node-icon { @@ -232,9 +232,9 @@ margin: 5px; width: 18px; height: 15px; - background: $form-input-background; + background: var(--red-ui-form-input-background); border-radius: 2px; - border: 1px solid $node-border; + border: 1px solid var(--red-ui-node-border); background-position: 5% 50%; background-repeat: no-repeat; background-size: contain; @@ -267,7 +267,7 @@ .red-ui-diff-status-deleted { cursor: default !important; .red-ui-diff-status { - color: $diff-state-deleted; + color: var(--red-ui-diff-state-deleted); } .red-ui-diff-list-node-node { opacity: 0.5; @@ -280,28 +280,28 @@ .red-ui-diff-status-added { cursor: default !important; .red-ui-diff-status { - color: $diff-state-added; + color: var(--red-ui-diff-state-added); } } .red-ui-diff-status-moved { .red-ui-diff-status { - color: $diff-state-moved; + color: var(--red-ui-diff-state-moved); } } .red-ui-diff-status-changed { .red-ui-diff-status { - color: $diff-state-changed; + color: var(--red-ui-diff-state-changed); } } .red-ui-diff-status-unchanged { .red-ui-diff-status { - color: $diff-state-unchanged; + color: var(--red-ui-diff-state-unchanged); } } .red-ui-diff-status-conflict { .red-ui-diff-status { - color: $diff-state-conflict; + color: var(--red-ui-diff-state-conflict); } } .red-ui-diff-list-node-title { @@ -312,7 +312,7 @@ } .red-ui-diff-list-node-properties { margin: 0; - color: $primary-text-color; + color: var(--red-ui-primary-text-color); } .red-ui-diff-status { display: inline-block; @@ -329,7 +329,7 @@ } .red-ui-diff-list-node-description { - color: $form-text-color; + color: var(--red-ui-form-text-color); margin-right: 5px; padding-top: 5px; display: inline-block; @@ -340,11 +340,11 @@ } } -.red-ui-diff-state-added { color: $diff-state-added; } -.red-ui-diff-state-deleted { color: $diff-state-deleted; } -.red-ui-diff-state-changed { color: $diff-state-changed; } -.red-ui-diff-state-unchanged { color: $diff-state-unchanged; } -.red-ui-diff-state-conflicted { color: $diff-state-conflicted; } +.red-ui-diff-state-added { color: var(--red-ui-diff-state-added); } +.red-ui-diff-state-deleted { color: var(--red-ui-diff-state-deleted); } +.red-ui-diff-state-changed { color: var(--red-ui-diff-state-changed); } +.red-ui-diff-state-unchanged { color: var(--red-ui-diff-state-unchanged); } +.red-ui-diff-state-conflicted { color: var(--red-ui-diff-state-conflicted); } .red-ui-diff-list-node-cell { @@ -353,19 +353,19 @@ box-sizing: border-box; width: calc( (100% - 20px) / 2); height: 32px; - border-left: 1px solid $secondary-border-color; + border-left: 1px solid var(--red-ui-secondary-border-color); padding-top: 2px; white-space: nowrap; overflow: hidden; position: relative; } .red-ui-diff-empty { - background: $secondary-background-disabled; + background: var(--red-ui-secondary-background-disabled); background: repeating-linear-gradient( 20deg, - $secondary-background, $secondary-background 5px, - $secondary-background-disabled 5px, - $secondary-background-disabled 10px + var(--red-ui-secondary-background), var(--red-ui-secondary-background) 5px, + var(--red-ui-secondary-background-disabled) 5px, + var(--red-ui-secondary-background-disabled) 10px ); } .red-ui-diff-list-node-cell:first-child { @@ -425,10 +425,10 @@ background: none; } &.red-ui-diff-status-changed { - background: $diff-state-changed-background; + background: var(--red-ui-diff-state-changed-background); } &.red-ui-diff-status-conflict { - background: $diff-state-conflict-background; + background: var(--red-ui-diff-state-conflict-background); } } @@ -439,42 +439,42 @@ label.red-ui-diff-selectbox { bottom:0; width: 35px; text-align: center; - border-left: 1px solid $secondary-border-color; + border-left: 1px solid var(--red-ui-secondary-border-color); margin:0; input[type="radio"] { margin-top: 8px; } &:hover { - background: $secondary-background-hover; + background: var(--red-ui-secondary-background-hover); } } .red-ui-diff-list-node-conflict.red-ui-diff-select-remote { .red-ui-diff-list-node-remote { - background: $diff-state-added-background; + background: var(--red-ui-diff-state-added-background); label { - border-left-color: $diff-state-added-border; + border-left-color: var(--red-ui-diff-state-added-border); } } .red-ui-diff-list-node-local { - background: $diff-state-deleted-background; + background: var(--red-ui-diff-state-deleted-background); label { - border-left-color: $diff-state-deleted-border; + border-left-color: var(--red-ui-diff-state-deleted-border); } } } .red-ui-diff-list-node-conflict.red-ui-diff-select-local { .red-ui-diff-list-node-local { - background: $diff-state-added-background; + background: var(--red-ui-diff-state-added-background); label { - border-left-color: $diff-state-added-border; + border-left-color: var(--red-ui-diff-state-added-border); } } .red-ui-diff-list-node-remote { - background: $diff-state-deleted-background; + background: var(--red-ui-diff-state-deleted-background); label { - border-left-color: $diff-state-deleted-border; + border-left-color: var(--red-ui-diff-state-deleted-border); } } } @@ -500,10 +500,10 @@ ul.red-ui-deploy-dialog-confirm-list { width: 30px; margin-right: 10px; &.fa-check { - color: $text-color-success; + color: var(--red-ui-text-color-success); } &.fa-exclamation { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } } div { @@ -529,7 +529,7 @@ ul.red-ui-deploy-dialog-confirm-list { table.red-ui-diff-text-content { margin: 10px; - border: 1px solid $secondary-border-color; + border: 1px solid var(--red-ui-secondary-border-color); border-radius: 3px; table-layout: fixed; width: calc(100% - 20px); @@ -538,88 +538,88 @@ ul.red-ui-deploy-dialog-confirm-list { word-wrap: break-word; } td.lineno { - font-family: $monospace-font; + font-family: var(--red-ui-monospace-font); text-align: right; - color: $tertiary-text-color; - background: $tertiary-background; + color: var(--red-ui-tertiary-text-color); + background: var(--red-ui-tertiary-background); padding: 1px 5px; &.added { - background: $diff-state-added-header-background; + background: var(--red-ui-diff-state-added-header-background); } &.removed { - background: $diff-state-deleted-header-background; + background: var(--red-ui-diff-state-deleted-header-background); } } td.lineno:nth-child(3) { - border-left: 1px solid $secondary-border-color; + border-left: 1px solid var(--red-ui-secondary-border-color); } td.linetext { - font-family: $monospace-font; + font-family: var(--red-ui-monospace-font); white-space: pre-wrap; padding: 1px 5px; - border-left: 1px solid $tertiary-border-color; + border-left: 1px solid var(--red-ui-tertiary-border-color); span.prefix { width: 30px; display: inline-block; text-align: center; - color: $diff-state-prefix-color; + color: var(--red-ui-diff-state-prefix-color); } &.added { - border-left-color: $diff-state-added-header-border; + border-left-color: var(--red-ui-diff-state-added-header-border); } &.removed { - border-left-color: $diff-state-deleted-header-border; + border-left-color: var(--red-ui-diff-state-deleted-header-border); } } td.blank { - background: $tertiary-background; + background: var(--red-ui-tertiary-background); } td.added { - background: $diff-state-added-background; - color: $diff-state-color; + background: var(--red-ui-diff-state-added-background); + color: var(--red-ui-diff-state-color); } td.removed { - background: $diff-state-deleted-background; - color: $diff-state-color; + background: var(--red-ui-diff-state-deleted-background); + color: var(--red-ui-diff-state-color); } tr.mergeHeader td { - color: $diff-merge-header-color; - background: $diff-merge-header-background; + color: var(--red-ui-diff-merge-header-color); + background: var(--red-ui-diff-merge-header-background); height: 26px; vertical-align: middle; } tr.mergeHeader-separator td { - color: $diff-merge-header-color; - background: $diff-merge-header-border-color; + color: var(--red-ui-diff-merge-header-color); + background: var(--red-ui-diff-merge-header-border-color); height: 0px; } tr.mergeHeader-ours td { - border-top: 2px solid $diff-merge-header-border-color; + border-top: 2px solid var(--red-ui-diff-merge-header-border-color); } tr.mergeHeader-theirs td { - border-bottom: 2px solid $diff-merge-header-border-color; + border-bottom: 2px solid var(--red-ui-diff-merge-header-border-color); } td.unchanged { - background: $diff-state-unchanged-background; - color: $diff-state-unchanged; + background: var(--red-ui-diff-state-unchanged-background); + color: var(--red-ui-diff-state-unchanged); } tr.unchanged { - background: $diff-state-unchanged-background; + background: var(--red-ui-diff-state-unchanged-background); } tr.start-block { - border-top: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); } tr.end-block { - border-bottom: 1px solid $secondary-border-color; + border-bottom: 1px solid var(--red-ui-secondary-border-color); } tr.red-ui-diff-text-file-header td { .filename { - font-family: $monospace-font; + font-family: var(--red-ui-monospace-font); } - background: $primary-background; + background: var(--red-ui-primary-background); padding: 5px 10px 5px 0; - color: $primary-text-color; + color: var(--red-ui-primary-text-color); cursor: pointer; i.red-ui-diff-list-chevron { width: 30px; @@ -631,17 +631,17 @@ ul.red-ui-deploy-dialog-confirm-list { } } tr.red-ui-diff-text-commit-header td { - background: $primary-background; + background: var(--red-ui-primary-background); padding: 5px 10px; - color: $primary-text-color; + color: var(--red-ui-primary-text-color); h3 { font-size: 1.4em; margin: 0; } .commit-summary { - border-top: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); padding-top: 5px; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } .commit-body { margin-bottom:15px; @@ -651,20 +651,20 @@ ul.red-ui-deploy-dialog-confirm-list { } tr.red-ui-diff-text-header > td:not(.red-ui-diff-flow-diff) { - font-family: $monospace-font; + font-family: var(--red-ui-monospace-font); padding: 5px 10px; text-align: left; - color: $diff-text-header-color; - background: $diff-text-header-background; + color: var(--red-ui-diff-text-header-color); + background: var(--red-ui-diff-text-header-background); height: 30px; vertical-align: middle; - border-top: 1px solid $secondary-border-color; - border-bottom: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); + border-bottom: 1px solid var(--red-ui-secondary-border-color); } tr.red-ui-diff-text-expand td { cursor: pointer; &:hover { - background: $diff-text-header-background; + background: var(--red-ui-diff-text-header-background); } } } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/dragdrop.scss b/packages/node_modules/@node-red/editor-client/src/sass/dragdrop.scss index 1476cf890..78646e0e7 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/dragdrop.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/dragdrop.scss @@ -18,7 +18,7 @@ position: absolute; top: 0; bottom: 0; left: 0; right: 0; - background: $dnd-background; + background: var(--red-ui-dnd-background); display:table; width: 100%; height: 100%; @@ -30,7 +30,7 @@ vertical-align: middle; text-align: center; font-size: 40px; - color: $dnd-color; + color: var(--red-ui-dnd-color); i { pointer-events: none; font-size: 80px; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss b/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss index 4cfc1884e..4323e9342 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss @@ -15,8 +15,8 @@ **/ .red-ui-menu-dropdown { - font-family: $primary-font; - font-size: $primary-font-size; + font-family: var(--red-ui-primary-font); + font-size: var(--red-ui-primary-font-size); position: absolute; top: 100%; width: 230px; @@ -28,9 +28,9 @@ margin-left: 0px !important; padding: 5px 0; list-style: none; - background: $menuBackground; - border: 1px solid $secondary-border-color; - box-shadow: 0 5px 10px $shadow; + background: var(--red-ui-menuBackground); + border: 1px solid var(--red-ui-secondary-border-color); + box-shadow: 0 5px 10px var(--red-ui-shadow); &.pull-right { right: 0; @@ -41,7 +41,7 @@ height: 1px; margin: 9px 1px; overflow: hidden; - background-color: $menuDivider; + background-color: var(--red-ui-menuDivider); } & > li > a, & > li > a:focus { @@ -50,7 +50,7 @@ clear: both; font-weight: normal; line-height: 20px; - color: $menuColor; + color: var(--red-ui-menuColor); white-space: normal !important; outline: none; } @@ -73,19 +73,19 @@ & > .active > a, & > .active > a:hover, & > .active > a:focus { - color: $menuActiveColor; + color: var(--red-ui-menuActiveColor); text-decoration: none; - background-color: $menuActiveBackground; + background-color: var(--red-ui-menuActiveBackground); outline: 0; } & > .disabled > a, & > .disabled > a:hover, & > .disabled > a:focus { - color: $menuDisabledColor; + color: var(--red-ui-menuDisabledColor); .red-ui-popover-key { - color: $menuDisabledColor; - border-color: $menuDisabledColor; + color: var(--red-ui-menuDisabledColor); + border-color: var(--red-ui-menuDisabledColor); } } @@ -133,8 +133,8 @@ padding: 0; font-size: 13px; // float: right; - color: $menuColor; - border-color: $menuColor; + color: var(--red-ui-menuColor); + border-color: var(--red-ui-menuColor); } } @@ -151,9 +151,9 @@ .red-ui-menu-dropdown > li > a:focus, .red-ui-menu-dropdown-submenu:hover > a, .red-ui-menu-dropdown-submenu:focus > a { - color: $menuHoverColor; + color: var(--red-ui-menuHoverColor); text-decoration: none; - background-color: $menuHoverBackground; + background-color: var(--red-ui-menuHoverBackground); } .red-ui-menu-dropdown-submenu { @@ -176,7 +176,7 @@ margin-top: 5px; margin-right: -10px; border-color: transparent; - border-left-color: $menuCaret; + border-left-color: var(--red-ui-menuCaret); border-style: solid; border-width: 5px 0 5px 5px; content: " "; @@ -202,7 +202,7 @@ margin-left: -30px; /* Caret Arrow */ border-color: transparent; - border-right-color: $menuCaret; + border-right-color: var(--red-ui-menuCaret); border-style: solid; border-width: 5px 5px 5px 0; content: " "; @@ -227,13 +227,13 @@ } } .red-ui-menu-dropdown-submenu.disabled > a:before { - border-right-color: $menuCaret; + border-right-color: var(--red-ui-menuCaret); } // Menu NG ul.red-ui-menu:not(.red-ui-menu-dropdown) { - font-family: $primary-font; + font-family: var(--red-ui-primary-font); font-size: 12px; list-style-type: none; padding: 0; @@ -244,14 +244,14 @@ ul.red-ui-menu:not(.red-ui-menu-dropdown) { clear: both; font-weight: normal; line-height: 20px; - color: $menuColor; + color: var(--red-ui-menuColor); white-space: nowrap; text-decoration: none; &:hover,&:focus { - color: $menuHoverColor; + color: var(--red-ui-menuHoverColor); text-decoration: none; - background-color: $menuHoverBackground; + background-color: var(--red-ui-menuHoverBackground); border: none; outline: none; } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/editor.scss b/packages/node_modules/@node-red/editor-client/src/sass/editor.scss index 75133df8a..1730b9e35 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/editor.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/editor.scss @@ -32,9 +32,9 @@ width: auto; right: -1000px; bottom: 0; - background: $secondary-background; - border-left: 1px solid $secondary-border-color; - border-bottom: 1px solid $primary-border-color; + background: var(--red-ui-secondary-background); + border-left: 1px solid var(--red-ui-secondary-border-color); + border-bottom: 1px solid var(--red-ui-primary-border-color); box-sizing: content-box; } .red-ui-tray.open { @@ -67,8 +67,8 @@ position: relative; box-sizing: border-box; font-weight: bold; - border-bottom: 1px solid $secondary-border-color; - background: $palette-header-background; + border-bottom: 1px solid var(--red-ui-secondary-border-color); + background: var(--red-ui-palette-header-background); &:after { content: ""; display: table; @@ -88,8 +88,8 @@ height: 26px; line-height: 26px; &.toggle:not(.selected) { - color: $workspace-button-color-selected !important; - background: $workspace-button-background-active; + color: var(--red-ui-workspace-button-color-selected) !important; + background: var(--red-ui-workspace-button-background-active); } } @@ -116,8 +116,8 @@ } .red-ui-tray-titlebar { - color: $header-text-color; - border-bottom: 1px solid $secondary-border-color; + color: var(--red-ui-header-text-color); + border-bottom: 1px solid var(--red-ui-secondary-border-color); padding: 8px; } .red-ui-editor ul.red-ui-tray-breadcrumbs { @@ -131,7 +131,7 @@ margin:0; &:not(:last-child) { - color: $workspace-button-color; + color: var(--red-ui-workspace-button-color); font-weight: normal; &:after { @@ -149,10 +149,10 @@ bottom: 0px; width: 7px; left: -9px; - background-color: $primary-background; + background-color: var(--red-ui-primary-background); cursor: col-resize; - border-left: 1px solid $primary-border-color; - box-shadow: -1px 0 6px $shadow; + border-left: 1px solid var(--red-ui-primary-border-color); + box-shadow: -1px 0 6px var(--red-ui-shadow); &:before { content: ''; @@ -167,11 +167,11 @@ mask-position: 50% 50%; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; - background-color: $grip-color; + background-color: var(--red-ui-grip-color); } &.red-ui-tray-resize-maximised { - background: $primary-background; + background: var(--red-ui-primary-background); cursor: default; } } @@ -182,10 +182,10 @@ button.red-ui-tray-resize-button { height: 37px; line-height: 35px; border: none; - border-bottom: 1px solid $secondary-border-color; + border-bottom: 1px solid var(--red-ui-secondary-border-color); margin: 0; - background: $primary-background; - color: $workspace-button-color; + background: var(--red-ui-primary-background); + color: var(--red-ui-workspace-button-color); } .red-ui-editor .red-ui-tray { @@ -203,16 +203,16 @@ button.red-ui-tray-resize-button { } .input-error { - border-color: $form-input-border-error-color !important; + border-color: var(--red-ui-form-input-border-error-color) !important; } .input-updated { - border-color: $node-selected-color !important; + border-color: var(--red-ui-node-selected-color) !important; } .form-row { clear: both; - color: $form-text-color; + color: var(--red-ui-form-text-color); margin-bottom:12px; } .form-row label { @@ -223,10 +223,10 @@ button.red-ui-tray-resize-button { width:70%; } .form-tips { - background: $form-tips-background; + background: var(--red-ui-form-tips-background); padding: 8px; border-radius: 2px; - border: 1px solid $secondary-border-color; + border: 1px solid var(--red-ui-secondary-border-color); max-width: 450px; } .form-tips code { @@ -238,7 +238,7 @@ button.red-ui-tray-resize-button { } .form-warning { - border-color: $text-color-error; + border-color: var(--red-ui-text-color-error); } } @@ -255,11 +255,11 @@ button.red-ui-tray-resize-button { } } .red-ui-editor-text-container { - border:1px solid $tertiary-border-color; + border:1px solid var(--red-ui-tertiary-border-color); border-radius:5px; overflow: hidden; - font-size: $primary-font-size !important; - font-family: $monospace-font !important; + font-size: var(--red-ui-primary-font-size !important); + font-family: var(--red-ui-monospace-font !important); height: 100%; &.red-ui-editor-text-container-toolbar { @@ -302,7 +302,7 @@ button.red-ui-button-small #red-ui-editor-config-scope-warning { display: inline-block; margin-right: 5px; - color: $text-color-warning; + color: var(--red-ui-text-color-warning); vertical-align: middle; } #red-ui-editor-config-scope { @@ -358,18 +358,18 @@ button.red-ui-button-small padding: 20px 20px 10px; &:last-child { padding-top: 60px; - background: $primary-background; + background: var(--red-ui-primary-background); } } } .red-ui-editor-type-markdown-panel-preview { padding: 10px; - border:1px solid $secondary-border-color; + border:1px solid var(--red-ui-secondary-border-color); border-radius:5px; height: calc(100% - 21px); overflow-y: scroll; - background: $secondary-background; + background: var(--red-ui-secondary-background); } #red-ui-clipboard-hidden { @@ -402,7 +402,7 @@ button.red-ui-button-small span { padding-left: 50px; width: 100px; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } } @@ -427,14 +427,14 @@ button.red-ui-button.red-ui-editor-node-appearance-button { .red-ui-group-layout-picker { padding: 5px; - background: $secondary-background; + background: var(--red-ui-secondary-background); } .red-ui-group-layout-picker-cell-text { position: absolute; width: 14px; height: 2px; - border-top: 2px solid $secondary-text-color; - border-bottom: 2px solid $secondary-text-color; + border-top: 2px solid var(--red-ui-secondary-text-color); + border-bottom: 2px solid var(--red-ui-secondary-text-color); margin: 2px; &.red-ui-group-layout-text-pos-nw { top: 0; left: 0; } @@ -451,7 +451,7 @@ button.red-ui-button.red-ui-editor-node-appearance-button { background-color: #FFF; background-size: 100% 100%; background-position: 0 0, 50% 50%; - background-image: linear-gradient(45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent),linear-gradient(-45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent); + background-image: linear-gradient(45deg, transparent 45%, var(--red-ui-secondary-border-color) 45%, var(--red-ui-secondary-border-color) 55%, transparent 55%, transparent),linear-gradient(-45deg, transparent 45%, var(--red-ui-secondary-border-color) 45%, var(--red-ui-secondary-border-color) 55%, transparent 55%, transparent); border: none; } } @@ -475,17 +475,17 @@ button.red-ui-group-layout-picker-none { width: 100%; margin-bottom: 0; border: none; - border-bottom: 1px solid $form-input-border-color; + border-bottom: 1px solid var(--red-ui-form-input-border-color); } small { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); margin-left: 5px; margin-right: 4px; display: inline-block; min-width: 35px; text-align: right; } - background: $primary-background; + background: var(--red-ui-primary-background); } .red-ui-editor-node-appearance-button { .red-ui-search-result-node { @@ -496,7 +496,7 @@ button.red-ui-group-layout-picker-none { padding: 0; border-style: solid; border-width: 1px; - border-color: $secondary-border-color; + border-color: var(--red-ui-secondary-border-color); } .red-ui-color-picker-swatch { position: absolute; @@ -509,7 +509,7 @@ button.red-ui-group-layout-picker-none { background-color: #FFF; background-size: 100% 100%; background-position: 0 0, 50% 50%; - background-image: linear-gradient(45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent),linear-gradient(-45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent) + background-image: linear-gradient(45deg, transparent 45%, var(--red-ui-secondary-border-color) 45%, var(--red-ui-secondary-border-color) 55%, transparent 55%, transparent),linear-gradient(-45deg, transparent 45%, var(--red-ui-secondary-border-color) 45%, var(--red-ui-secondary-border-color) 55%, transparent 55%, transparent) } .red-ui-search-result-node .red-ui-color-picker-cell-none { border-radius: 4px; @@ -536,7 +536,7 @@ button.red-ui-group-layout-picker-none { top:0;right:0;left:0;bottom:0; background-image:linear-gradient(90deg, transparent 0%, #f00 100%); background-size: 100% 100%; - border: 1px solid $primary-border-color; + border: 1px solid var(--red-ui-primary-border-color); } div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle { @@ -547,9 +547,9 @@ div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle { width: 10px; height: 22px; padding: 0; - border: 1px solid $primary-border-color; + border: 1px solid var(--red-ui-primary-border-color); border-radius: 1px; - background: $secondary-background; + background: var(--red-ui-secondary-background); box-sizing: border-box; } .red-ui-icon-picker { @@ -567,10 +567,10 @@ div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle { position: relative; &.red-ui-icon-list-dark { .red-ui-palette-icon-fa { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } .red-ui-palette-icon-container { - background: $secondary-background; + background: var(--red-ui-secondary-background); border-radius: 4px; } } @@ -583,10 +583,10 @@ div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle { border-radius: 4px; &:hover { - background: $list-item-background-hover; + background: var(--red-ui-list-item-background-hover); } &.selected { - background: $list-item-background-selected; + background: var(--red-ui-list-item-background-selected); .red-ui-search-result-node { // border-color: white; } @@ -597,22 +597,22 @@ div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle { } } .red-ui-icon-list-module { - background: $palette-header-background; + background: var(--red-ui-palette-header-background); font-size: 0.9em; padding: 3px; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); clear: both; i { margin-right: 5px; } } .red-ui-icon-meta { - border-top: 1px solid $secondary-border-color; - background: $tertiary-background; + border-top: 1px solid var(--red-ui-secondary-border-color); + background: var(--red-ui-tertiary-background); height: 24px; span { padding: 4px; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); font-size: 0.9em; line-height: 24px; } @@ -630,7 +630,7 @@ div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle { .red-ui-editor-type-json-editor { height: calc(100% - 10px); .red-ui-treeList-container { - background: $secondary-background; + background: var(--red-ui-secondary-background); } .red-ui-treeList-label { padding-top: 0; @@ -647,7 +647,7 @@ div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle { vertical-align: middle; } &:hover, &:hover .red-ui-treeList-sublabel-text { - background: $secondary-background-disabled; + background: var(--red-ui-secondary-background-disabled); .red-ui-editor-type-json-editor-item-gutter { > span, > button { display: inline-block; @@ -656,11 +656,11 @@ div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle { } &.selected { .red-ui-editor-type-json-editor-item-gutter { - background: $secondary-background-hover; + background: var(--red-ui-secondary-background-hover); } &:hover { .red-ui-editor-type-json-editor-item-gutter { - background: $secondary-background-selected; + background: var(--red-ui-secondary-background-selected); } } } @@ -698,7 +698,7 @@ div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle { border: 2px solid rgba(0,0,0,0); border-radius: 3px; &:not(.red-ui-editor-type-json-editor-label-array-key):hover { - border-color: $list-item-background-hover; + border-color: var(--red-ui-list-item-background-hover); border-style: dashed; } &.readonly { @@ -712,8 +712,8 @@ div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle { height: 100%; line-height: 35px; - color: $tertiary-text-color; - background: $secondary-background-disabled; + color: var(--red-ui-tertiary-text-color); + background: var(--red-ui-secondary-background-disabled); > span { display: inline-block; height: 35px; @@ -755,7 +755,7 @@ button.red-ui-toggleButton.toggle { } >div:first-child { font-size: 0.9em; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); margin: 3px 0 -4px; >div { padding-left: 3px; @@ -767,15 +767,15 @@ button.red-ui-toggleButton.toggle { line-height: 30px; display: inline-block; box-sizing: border-box; - // border-left: 2px dashed $secondary-border-color; - // border-bottom: 2px dashed $secondary-border-color; - // border: 1px dashed $secondary-border-color; + // border-left: 2px dashed var(--red-ui-secondary-border-color); + // border-bottom: 2px dashed var(--red-ui-secondary-border-color); + // border: 1px dashed var(--red-ui-secondary-border-color); border-right: none; &:not(:first-child) { padding: 3px; } // &:last-child { - // border-right: 1px dashed $secondary-border-color; + // border-right: 1px dashed var(--red-ui-secondary-border-color); // } .placeholder-input { position: relative; @@ -800,8 +800,8 @@ button.red-ui-toggleButton.toggle { height: 100%; width: 20px; text-align:center; - border-right: 1px solid $secondary-border-color; - background: $tertiary-background; + border-right: 1px solid var(--red-ui-secondary-border-color); + background: var(--red-ui-tertiary-background); } } input[type="checkbox"] { @@ -817,13 +817,13 @@ button.red-ui-toggleButton.toggle { .red-ui-editableList-item-handle { position:relative; top: 0px; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); } } >div:nth-child(2) { margin: 4px; height: 32px; - border: 1px dashed $secondary-border-color; + border: 1px dashed var(--red-ui-secondary-border-color); text-align: center; a { display: block; @@ -831,7 +831,7 @@ button.red-ui-toggleButton.toggle { height: 100%; line-height: 32px; &:hover { - background: $secondary-background-hover; + background: var(--red-ui-secondary-background-hover); } i { height: 100%; @@ -851,7 +851,7 @@ button.red-ui-toggleButton.toggle { span.red-ui-editor-subflow-env-lang-icon { position: absolute; display: inline-block; - background: $secondary-background; + background: var(--red-ui-secondary-background); opacity: 0.8; width: 20px; line-height: 32px; @@ -864,12 +864,12 @@ span.red-ui-editor-subflow-env-lang-icon { } .red-ui-editor-subflow-env-input-type { - background: $secondary-background; + background: var(--red-ui-secondary-background); height: 100%; box-sizing: border-box; } .red-ui-editor-subflow-env-input-type-placeholder { - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); padding-left: 4px; } @@ -886,7 +886,7 @@ span.red-ui-editor-subflow-env-lang-icon { // border-top: none; // } // &.ui-sortable-helper { -// border: 2px dashed $secondary-border-color; +// border: 2px dashed var(--red-ui-secondary-border-color); // .red-ui-editableList-item-content { // >div { // border: none; @@ -901,15 +901,15 @@ span.red-ui-editor-subflow-env-lang-icon { // >div>div { // display: inline-block; // box-sizing: border-box; -// border-left: 1px dashed $secondary-border-color; -// border-bottom: 1px dashed $secondary-border-color; +// border-left: 1px dashed var(--red-ui-secondary-border-color); +// border-bottom: 1px dashed var(--red-ui-secondary-border-color); // } // >div:first-child { // font-size: 0.9em; // display: grid; // grid-template-columns: 25px auto 20px; // >div { -// border-top: 1px dashed $secondary-border-color; +// border-top: 1px dashed var(--red-ui-secondary-border-color); // padding: 1px; // } // >div:nth-child(3) { @@ -929,9 +929,9 @@ span.red-ui-editor-subflow-env-lang-icon { // // line-height: 30px; // // box-sizing: border-box; // // -// // border-left: 2px dashed $secondary-border-color; +// // border-left: 2px dashed var(--red-ui-secondary-border-color); // border-top: none; -// // border-bottom: 2px dashed $secondary-border-color; +// // border-bottom: 2px dashed var(--red-ui-secondary-border-color); // &:not(:first-child) { // padding: 6px 3px; // } @@ -963,7 +963,7 @@ span.red-ui-editor-subflow-env-lang-icon { // height: 100%; // line-height: 45px; // &:hover { -// background: $secondary-background-hover; +// background: var(--red-ui-secondary-background-hover); // } // } // } @@ -993,11 +993,11 @@ span.red-ui-editor-subflow-env-lang-icon { // } .red-ui-editor-subflow-ui-edit-panel { padding-bottom: 3px; - background: $primary-background; + background: var(--red-ui-primary-background); .red-ui-editableList-border { border: none; border-radius: 0; - border-bottom: 1px solid $secondary-border-color; + border-bottom: 1px solid var(--red-ui-secondary-border-color); } .red-ui-editableList-container { } @@ -1005,10 +1005,10 @@ span.red-ui-editor-subflow-env-lang-icon { margin-left: 2px; } .red-ui-editableList-header { - background: $primary-background; + background: var(--red-ui-primary-background); display: grid; grid-template-columns: 50% 50%; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); div:first-child { padding-left: 23px; } @@ -1019,7 +1019,7 @@ span.red-ui-editor-subflow-env-lang-icon { .red-ui-editableList-container { padding: 0 1px; li { - background: $secondary-background; + background: var(--red-ui-secondary-background); // border-bottom: none; padding: 0; .red-ui-editableList-item-content { @@ -1034,14 +1034,14 @@ span.red-ui-editor-subflow-env-lang-icon { margin-bottom: 0; border:none; width: 100%; - border-right: 1px solid $secondary-border-color; + border-right: 1px solid var(--red-ui-secondary-border-color); border-radius: 0; &:focus { - box-shadow: 0 0 0 1px inset $form-input-focus-color; + box-shadow: 0 0 0 1px inset var(--red-ui-form-input-focus-color); } &:first-child { - border-left: 1px solid $secondary-border-color; + border-left: 1px solid var(--red-ui-secondary-border-color); } } button.red-ui-typedInput-type-select, button.red-ui-typedInput-option-expand, button.red-ui-typedInput-option-trigger { @@ -1131,7 +1131,7 @@ span.red-ui-editor-subflow-env-lang-icon { border-top-left-radius: 4px; border-top-right-radius: 4px; - background: $tertiary-background; + background: var(--red-ui-tertiary-background); padding: 0; >div { display: grid; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/flow.scss b/packages/node_modules/@node-red/editor-client/src/sass/flow.scss index 2e6de1932..2d4877896 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/flow.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/flow.scss @@ -16,14 +16,14 @@ .nr-ui-view-lasso { stroke-width: 1px; - stroke: $view-lasso-stroke; - fill: $view-lasso-fill; + stroke: var(--red-ui-view-lasso-stroke); + fill: var(--red-ui-view-lasso-fill); stroke-dasharray: 10 5; } .nr-ui-view-slice { stroke-width: 1px; - stroke: $view-lasso-stroke; + stroke: var(--red-ui-view-lasso-stroke); fill: none; stroke-dasharray: 10 5; } @@ -33,11 +33,11 @@ font-style: italic; } .red-ui-flow-node-label-white { - fill: $view-background !important; + fill: var(--red-ui-view-background) !important; } .red-ui-flow-node-label { stroke-width: 0; - fill: $node-label-color; + fill: var(--red-ui-node-label-color); font-size: 14px; pointer-events: none; -webkit-touch-callout: none; @@ -54,7 +54,7 @@ .red-ui-flow-port-label { stroke-width: 0; - fill: $node-port-label-color; + fill: var(--red-ui-node-port-label-color); font-size: 16px; dominant-baseline: middle; text-anchor: middle; @@ -65,7 +65,7 @@ .red-ui-flow-node { - stroke: $node-border; + stroke: var(--red-ui-node-border); cursor: move; stroke-width: 1; } @@ -80,7 +80,7 @@ opacity: 0.9; .red-ui-flow-node { stroke-width: 2; - stroke: $node-selected-color !important; + stroke: var(--red-ui-node-selected-color) !important; stroke-dasharray: 10, 4; } } @@ -95,53 +95,53 @@ } &.red-ui-flow-group-active-hovered:not(.red-ui-flow-group-hovered) { .red-ui-flow-group-outline-select { - stroke: $link-link-color; + stroke: var(--red-ui-link-link-color); } } } .red-ui-flow-group-outline { fill: none; - stroke: $node-selected-color; + stroke: var(--red-ui-node-selected-color); stroke-opacity: 0; stroke-width: 12; pointer-events: stroke; } .red-ui-flow-group-outline-select { fill: none; - stroke: $node-selected-color; + stroke: var(--red-ui-node-selected-color); pointer-events: stroke; stroke-opacity: 0; stroke-width: 3; &.red-ui-flow-group-outline-select-background { - stroke: $view-background; + stroke: var(--red-ui-view-background); stroke-width: 6; } } .red-ui-flow-group-body { pointer-events: none; - fill: $group-default-fill; - fill-opacity: $group-default-fill-opacity; + fill: var(--red-ui-group-default-fill); + fill-opacity: var(--red-ui-group-default-fill-opacity); stroke-width: 2; - stroke: $group-default-stroke; - stroke-opacity: $group-default-stroke-opacity; + stroke: var(--red-ui-group-default-stroke); + stroke-opacity: var(--red-ui-group-default-stroke-opacity); } .red-ui-flow-group-label { @include disable-selection; - fill: $group-default-label-color; + fill: var(--red-ui-group-default-label-color); } .red-ui-flow-node-unknown { stroke-dasharray:10,4; - stroke: $node-border-unknown; + stroke: var(--red-ui-node-border-unknown); } .red-ui-flow-node-placeholder { stroke-dasharray:10,4; - stroke: $node-border-placeholder; - fill: $node-background-placeholder; + stroke: var(--red-ui-node-border-placeholder); + fill: var(--red-ui-node-background-placeholder); opacity: 0.5; stroke-width: 2; } @@ -152,19 +152,19 @@ .fa-lg { @include disable-selection; stroke: none; - fill: $node-icon-color; + fill: var(--red-ui-node-icon-color); text-anchor: middle; font-family: FontAwesome; } } .red-ui-flow-node-icon-shade { stroke: none; - fill: $node-icon-background-color-fill; - fill-opacity: $node-icon-background-color-opacity; + fill: var(--red-ui-node-icon-background-color-fill); + fill-opacity: var(--red-ui-node-icon-background-color-opacity); } .red-ui-flow-node-icon-shade-border { - stroke-opacity: $node-icon-border-color-opacity; - stroke: $node-icon-border-color; + stroke-opacity: var(--red-ui-node-icon-border-color-opacity); + stroke: var(--red-ui-node-icon-border-color); stroke-width: 1; } @@ -181,27 +181,27 @@ cursor: pointer; } .red-ui-flow-node-button-background { - fill: $node-background-placeholder; + fill: var(--red-ui-node-background-placeholder); } .red-ui-flow-port { - stroke: $node-border; + stroke: var(--red-ui-node-border); stroke-width: 1; - fill: $node-port-background; + fill: var(--red-ui-node-port-background); cursor: crosshair; } .red-ui-flow-node-error { - fill: $node-status-error-background; - stroke: $node-status-error-border; + fill: var(--red-ui-node-status-error-background); + stroke: var(--red-ui-node-status-error-border); stroke-width:1px; cursor: default; stroke-linejoin: round; stroke-linecap: round; } .red-ui-flow-node-changed { - fill: $node-status-changed-background; - stroke: $node-status-changed-border; + fill: var(--red-ui-node-status-changed-background); + stroke: var(--red-ui-node-status-changed-border); cursor: default; stroke-width:1px; stroke-linejoin: round; @@ -214,13 +214,13 @@ g.red-ui-flow-node-selected { } .red-ui-flow-node,.red-ui-flow-subflow-port { stroke-width: 2; - stroke: $node-selected-color !important; + stroke: var(--red-ui-node-selected-color) !important; } } .red-ui-flow-node-highlighted { - border-color: $node-selected-color !important; + border-color: var(--red-ui-node-selected-color) !important; border-style: dashed !important; - stroke: $node-selected-color; + stroke: var(--red-ui-node-selected-color); stroke-width: 3; stroke-dasharray: 8, 4; } @@ -236,7 +236,7 @@ g.red-ui-flow-node-selected { .red-ui-flow-link-line { stroke-dasharray: 10,8 !important; stroke-width: 2 !important; - stroke: $link-disabled-color; + stroke: var(--red-ui-link-disabled-color); } .red-ui-flow-port { fill-opacity: 1; @@ -254,7 +254,7 @@ g.red-ui-flow-node-selected { &.red-ui-flow-link-line { stroke-dasharray: 10,8 !important; stroke-width: 2 !important; - stroke: $link-disabled-color; + stroke: var(--red-ui-link-disabled-color); } .red-ui-flow-port { fill-opacity: 1; @@ -263,49 +263,49 @@ g.red-ui-flow-node-selected { } @each $current-color in red green yellow blue grey gray { .red-ui-flow-node-status-dot-#{""+$current-color} { - fill: map-get($node-status-colors,$current-color); - stroke: map-get($node-status-colors,$current-color); + fill: var(--red-ui-node-status-colors-#{"" + $current-color}); + stroke: var(--red-ui-node-status-colors-#{"" + $current-color}); } .red-ui-flow-node-status-ring-#{""+$current-color} { - fill: $view-background; - stroke: map-get($node-status-colors,$current-color); + fill: var(--red-ui-view-background); + stroke: var(--red-ui-node-status-colors-#{"" + $current-color}); } } .red-ui-flow-node-status-label { @include disable-selection; stroke-width: 0; - fill: $secondary-text-color; + fill: var(--red-ui-secondary-text-color); font-size:9pt; text-anchor:start; } .red-ui-flow-port-hovered { - stroke: $port-selected-color; - fill: $port-selected-color; + stroke: var(--red-ui-port-selected-color); + fill: var(--red-ui-port-selected-color); } .red-ui-flow-subflow-port { - fill: $node-background-placeholder; - stroke: $node-border; + fill: var(--red-ui-node-background-placeholder); + stroke: var(--red-ui-node-border); } .red-ui-flow-drag-line { - stroke: $node-selected-color !important; + stroke: var(--red-ui-node-selected-color) !important; stroke-width: 3; fill: none; pointer-events: none; } .red-ui-flow-link-line { - stroke: $link-color; + stroke: var(--red-ui-link-color); stroke-width: 3; fill: none; pointer-events: none; } .red-ui-flow-link-link { stroke-width: 2; - stroke: $link-link-color; + stroke: var(--red-ui-link-link-color); fill: none; stroke-dasharray: 25,4; } @@ -314,19 +314,19 @@ g.red-ui-flow-node-selected { } .red-ui-flow-link-port { - fill: $node-link-port-background; - stroke: $link-link-color; + fill: var(--red-ui-node-link-port-background); + stroke: var(--red-ui-link-link-color); stroke-width: 1; } .red-ui-flow-link-group-active .red-ui-flow-link-port { - stroke: $link-link-active-color; + stroke: var(--red-ui-link-link-active-color); } .red-ui-flow-link-group:hover { cursor: pointer; } .red-ui-flow-link-outline { - stroke: $view-background; + stroke: var(--red-ui-view-background); stroke-opacity: 0.4; stroke-width: 5; cursor: crosshair; @@ -334,7 +334,7 @@ g.red-ui-flow-node-selected { pointer-events: none; } .red-ui-flow-link-background { - stroke: $view-background; + stroke: var(--red-ui-view-background); opacity: 0; stroke-width: 20; cursor: crosshair; @@ -345,10 +345,10 @@ g.red-ui-flow-node-selected { } g.red-ui-flow-link-selected path.red-ui-flow-link-line { - stroke: $node-selected-color; + stroke: var(--red-ui-node-selected-color); } g.red-ui-flow-link-unknown path.red-ui-flow-link-line { - stroke: $link-unknown-color; + stroke: var(--red-ui-link-unknown-color); stroke-width: 2; stroke-dasharray: 10, 4; } @@ -364,15 +364,15 @@ g.red-ui-flow-link-unknown path.red-ui-flow-link-line { pointer-events: none; path:first-child { - fill: $popover-background; - stroke: $popover-background; + fill: var(--red-ui-popover-background); + stroke: var(--red-ui-popover-background); stroke-width: 1; } } .red-ui-flow-port-tooltip-label { stroke-width: 0; - fill: $popover-color; - font-family: $primary-font; + fill: var(--red-ui-popover-color); + font-family: var(--red-ui-primary-font); font-size: 12px; pointer-events: none; -webkit-touch-callout: none; @@ -401,18 +401,18 @@ g.red-ui-flow-link-unknown path.red-ui-flow-link-line { } } .red-ui-flow-junction-port { - stroke: $node-border; + stroke: var(--red-ui-node-border); stroke-width: 1; - fill: $node-port-background; + fill: var(--red-ui-node-port-background); cursor: crosshair; transition: transform 0.1s; opacity: 0; pointer-events: none; } .red-ui-flow-junction-background { - stroke: $node-border; + stroke: var(--red-ui-node-border); stroke-width: 1; - fill: $node-port-background; + fill: var(--red-ui-node-port-background); cursor: crosshair; transform: scale(1); transition: transform 0.1s; @@ -421,10 +421,10 @@ g.red-ui-flow-link-unknown path.red-ui-flow-link-line { } } .red-ui-flow-junction-hovered { - stroke: $port-selected-color; - fill: $port-selected-color; + stroke: var(--red-ui-port-selected-color); + fill: var(--red-ui-port-selected-color); } .red-ui-flow-junction.selected .red-ui-flow-junction-background { - stroke: $port-selected-color; - // fill: $port-selected-color; + stroke: var(--red-ui-port-selected-color); + // fill: var(--red-ui-port-selected-color); } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/forms.scss b/packages/node_modules/@node-red/editor-client/src/sass/forms.scss index 022579b27..a281b9265 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/forms.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/forms.scss @@ -99,13 +99,13 @@ margin-bottom: 20px; font-size: 21px; line-height: 40px; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); border: 0; - border-bottom: 1px solid $secondary-border-color; + border-bottom: 1px solid var(--red-ui-secondary-border-color); } legend small { - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); } @@ -125,7 +125,7 @@ button, select, textarea { - font-family: $primary-font; + font-family: var(--red-ui-primary-font); } label { @@ -159,7 +159,7 @@ margin-bottom: 10px; font-size: 14px; line-height: 20px; - color: $form-text-color; + color: var(--red-ui-form-text-color); vertical-align: middle; border-radius: 4px; } @@ -193,8 +193,8 @@ div[contenteditable="true"], .uneditable-input, .placeholder-input { - background-color: $form-input-background; - border: 1px solid $form-input-border-color; + background-color: var(--red-ui-form-input-background); + border: 1px solid var(--red-ui-form-input-border-color); } textarea:focus, @@ -214,7 +214,7 @@ input[type="color"]:focus, div[contenteditable="true"]:focus, .uneditable-input:focus { - border-color: $form-input-focus-color; + border-color: var(--red-ui-form-input-focus-color); outline: 0; outline: thin dotted \9; } @@ -245,8 +245,8 @@ select { width: 220px; - background-color: $form-input-background; - border: 1px solid $form-input-border-color; + background-color: var(--red-ui-form-input-background); + border: 1px solid var(--red-ui-form-input-border-color); } select[multiple], @@ -258,16 +258,16 @@ input[type="file"]:focus, input[type="radio"]:focus, input[type="checkbox"]:focus { - outline: 2px auto $form-input-focus-color; + outline: 2px auto var(--red-ui-form-input-focus-color); outline-offset: -3px; } .uneditable-input, .uneditable-textarea { - color: $form-text-color-disabled; + color: var(--red-ui-form-text-color-disabled); cursor: not-allowed; - background-color: $form-input-background-disabled; - border-color: $form-input-border-color; + background-color: var(--red-ui-form-input-background-disabled); + border-color: var(--red-ui-form-input-border-color); } .uneditable-input { @@ -282,19 +282,19 @@ input:-moz-placeholder, textarea:-moz-placeholder { - color: $form-placeholder-color; + color: var(--red-ui-form-placeholder-color); } input:-ms-input-placeholder, div[contenteditable="true"]:-ms-input-placeholder, textarea:-ms-input-placeholder { - color: $form-placeholder-color; + color: var(--red-ui-form-placeholder-color); } input::-webkit-input-placeholder, div[contenteditable="true"]::-webkit-input-placeholder, textarea::-webkit-input-placeholder { - color: $form-placeholder-color; + color: var(--red-ui-form-placeholder-color); } .radio, @@ -384,7 +384,7 @@ } label.disabled { - color: $form-text-color-disabled; + color: var(--red-ui-form-text-color-disabled); cursor: default; } @@ -395,8 +395,8 @@ select[readonly], textarea[readonly] { cursor: not-allowed; - color: $form-text-color-disabled; - background-color: $form-input-background-disabled; + color: var(--red-ui-form-text-color-disabled); + background-color: var(--red-ui-form-input-background-disabled); } input[type="radio"][disabled], @@ -410,21 +410,21 @@ div[contenteditable="true"]:invalid, textarea:invalid, select:invalid { - border-color: $form-input-border-error-color; + border-color: var(--red-ui-form-input-border-error-color); } input:focus:invalid, div[contenteditable="true"]:focus:invalid, textarea:focus:invalid, select:focus:invalid { - border-color: $form-input-border-error-color; + border-color: var(--red-ui-form-input-border-error-color); } input:focus:invalid:focus, div[contenteditable="true"]:focus:invalid:focus, textarea:focus:invalid:focus, select:focus:invalid:focus { - border-color: $form-input-border-error-color; + border-color: var(--red-ui-form-input-border-error-color); } .input-append, @@ -488,8 +488,8 @@ font-weight: normal; line-height: 20px; text-align: center; - background-color: $form-button-background; - border: 1px solid $form-input-border-color; + background-color: var(--red-ui-form-button-background); + border: 1px solid var(--red-ui-form-input-border-color); } .input-append .add-on, diff --git a/packages/node_modules/@node-red/editor-client/src/sass/header.scss b/packages/node_modules/@node-red/editor-client/src/sass/header.scss index 697a90729..19c15b015 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/header.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/header.scss @@ -24,10 +24,10 @@ left: 0; width: 100%; height: 40px; - background: $header-background; + background: var(--red-ui-header-background); box-sizing: border-box; padding: 0px 0px 0px 20px; - color: $header-menu-color; + color: var(--red-ui-header-menu-color); font-size: 14px; span.red-ui-header-logo { @@ -81,17 +81,17 @@ font-size: 20px; padding: 0px 12px; text-decoration: none; - color: $header-menu-color; + color: var(--red-ui-header-menu-color); margin: auto 5px; vertical-align: middle; - border-left: 2px solid $header-background; - border-right: 2px solid $header-background; + border-left: 2px solid var(--red-ui-header-background); + border-right: 2px solid var(--red-ui-header-background); &:hover { - border-color: $header-menu-item-hover; + border-color: var(--red-ui-header-menu-item-hover); } &:active, &.active { - background: $header-button-background-active; + background: var(--red-ui-header-button-background-active); } &:focus { outline: none; @@ -116,18 +116,18 @@ } .red-ui-deploy-button { - background: $deploy-button-background; - color: $deploy-button-color; + background: var(--red-ui-deploy-button-background); + color: var(--red-ui-deploy-button-color); &:hover { - background: $deploy-button-background-hover; + background: var(--red-ui-deploy-button-background-hover); } &:focus { outline: none; } &:active { - background: $deploy-button-background-active; - color: $deploy-button-color-active; + background: var(--red-ui-deploy-button-background-active); + color: var(--red-ui-deploy-button-color-active); } } @@ -149,21 +149,21 @@ padding: 4px 12px; &.disabled { cursor: default; - background: $deploy-button-background-disabled; - color: $deploy-button-color-disabled; + background: var(--red-ui-deploy-button-background-disabled); + color: var(--red-ui-deploy-button-color-disabled); .red-ui-deploy-button-content>img { opacity: 0.3; } &+ #red-ui-header-button-deploy-options { - background: $deploy-button-background-disabled; - color: $deploy-button-color-active; + background: var(--red-ui-deploy-button-background-disabled); + color: var(--red-ui-deploy-button-color-active); } &+ #red-ui-header-button-deploy-options:hover { - background: $deploy-button-background-disabled-hover; + background: var(--red-ui-deploy-button-background-disabled-hover); } &+ #red-ui-header-button-deploy-options:active { - background: $deploy-button-background-disabled; + background: var(--red-ui-deploy-button-background-disabled); } } @@ -174,23 +174,23 @@ .red-ui-deploy-button-group.open { #red-ui-header-button-deploy-options { - background: $header-button-background-active !important; + background: var(--red-ui-header-button-background-active) !important; } } li.open .button { - background: $header-button-background-active; - border-color: $header-button-background-active; + background: var(--red-ui-header-button-background-active); + border-color: var(--red-ui-header-button-background-active); } ul.red-ui-menu-dropdown { - background: $header-menu-background; - border: 1px solid $header-menu-background; + background: var(--red-ui-header-menu-background); + border: 1px solid var(--red-ui-header-menu-background); width: 260px !important; margin-top: 0; li a { - color: $header-menu-color; + color: var(--red-ui-header-menu-color); padding: 3px 10px 3px 40px; img { max-width: 100%; @@ -199,11 +199,11 @@ border: 3px solid transparent; } .red-ui-popover-key { - color: $header-menu-color-disabled !important; - border-color: $header-menu-color-disabled !important; + color: var(--red-ui-header-menu-color-disabled) !important; + border-color: var(--red-ui-header-menu-color-disabled) !important; } &.active img { - border: 3px solid $header-menu-item-border-active; + border: 3px solid var(--red-ui-header-menu-item-border-active); } span.red-ui-menu-label-container { @@ -217,7 +217,7 @@ text-indent: 0px; } span.red-ui-menu-sublabel { - color: $header-menu-sublabel-color; + color: var(--red-ui-header-menu-sublabel-color); font-size: 13px; display: inline-block; text-indent: 0px; @@ -228,13 +228,13 @@ > li > a:focus, > li:hover > a, > li:focus > a { - background: $header-menu-item-hover !important; + background: var(--red-ui-header-menu-item-hover) !important; } li.red-ui-menu-divider { - background: $headerMenuItemDivider; + background: var(--red-ui-headerMenuItemDivider); } li.disabled a { - color: $header-menu-color-disabled; + color: var(--red-ui-header-menu-color-disabled); } > li.disabled > a:hover, > li.disabled > a:focus { @@ -242,7 +242,7 @@ } } .red-ui-menu-dropdown-submenu>a:before { - border-right-color: $headerMenuCaret; + border-right-color: var(--red-ui-headerMenuCaret); } /* Deploy menu customisations */ @@ -250,7 +250,7 @@ width: 300px !important; li a { padding: 10px 30px; - color: $header-menu-heading-color; + color: var(--red-ui-header-menu-heading-color); span.red-ui-menu-label { font-size: 16px; display: inline-block; @@ -263,7 +263,7 @@ } /* User menu customisations */ #usermenu-item-username > .red-ui-menu-label { - color: $header-menu-heading-color; + color: var(--red-ui-header-menu-heading-color); } #red-ui-header-button-user .user-profile { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/jquery.scss b/packages/node_modules/@node-red/editor-client/src/sass/jquery.scss index b7278b332..27661c459 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/jquery.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/jquery.scss @@ -16,25 +16,25 @@ .ui-widget { font-size: 14px !important; - font-family: $primary-font; + font-family: var(--red-ui-primary-font); } .ui-widget input, .ui-widget div[contenteditable="true"], .ui-widget select, .ui-widget textarea, .ui-widget button { font-size: 14px !important; - font-family: $primary-font; + font-family: var(--red-ui-primary-font); } .ui-widget input, .ui-widget div[contenteditable="true"] { box-shadow: none; } .ui-widget.ui-widget-content { - border: 1px solid $tertiary-border-color; + border: 1px solid var(--red-ui-tertiary-border-color); } .ui-widget-content { - border: 1px solid $secondary-border-color; + border: 1px solid var(--red-ui-secondary-border-color); } .ui-widget-header { - color: $header-text-color; + color: var(--red-ui-header-text-color); } /* jQuery Theme overrides */ @@ -50,7 +50,7 @@ .ui-dialog { border-radius: 1px; - background: $secondary-background; + background: var(--red-ui-secondary-background); padding: 0; @include component-shadow; } @@ -62,20 +62,20 @@ } .ui-dialog .ui-dialog-titlebar { padding: 10px; - background: $primary-background; + background: var(--red-ui-primary-background); border: none; - border-bottom: 1px solid $primary-border-color; + border-bottom: 1px solid var(--red-ui-primary-border-color); border-radius: 0; } .ui-dialog .ui-dialog-buttonpane.ui-widget-content { - background: $tertiary-background; + background: var(--red-ui-tertiary-background); } .ui-corner-all { border-radius: 1px; } .ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { - background: $primary-background; + background: var(--red-ui-primary-background); } .ui-dialog-no-close .ui-dialog-titlebar-close { display: none; @@ -95,8 +95,8 @@ padding: 6px 14px; margin-right: 8px; border-radius: 2px; - color: $workspace-button-color; - background: $workspace-button-background; + color: var(--red-ui-workspace-button-color); + background: var(--red-ui-workspace-button-background); &.leftButton { float: left; @@ -107,18 +107,18 @@ } &.primary { - border-color: $workspace-button-background-primary; - color: $workspace-button-color-primary !important; - background: $workspace-button-background-primary; + border-color: var(--red-ui-workspace-button-background-primary); + color: var(--red-ui-workspace-button-color-primary) !important; + background: var(--red-ui-workspace-button-background-primary); &:not(.disabled):hover { - border-color: $workspace-button-background-primary-hover; - background: $workspace-button-background-primary-hover; - color: $workspace-button-color-primary !important; + border-color: var(--red-ui-workspace-button-background-primary-hover); + background: var(--red-ui-workspace-button-background-primary-hover); + color: var(--red-ui-workspace-button-color-primary) !important; } &.disabled { - border-color: $form-input-border-color; - color: $workspace-button-color-disabled !important; - background: $workspace-button-background; + border-color: var(--red-ui-form-input-border-color); + color: var(--red-ui-workspace-button-color-disabled) !important; + background: var(--red-ui-workspace-button-background); } } &.disabled { @@ -142,10 +142,10 @@ .ui-spinner { border-radius: 4px; padding: 0; - border: 1px solid $form-input-border-color; + border: 1px solid var(--red-ui-form-input-border-color); } .ui-spinner input { - background: $form-input-background; + background: var(--red-ui-form-input-background); margin: 0 17px 0 0; padding: 6px; border: none; @@ -169,8 +169,8 @@ .ui-button, html .ui-button.ui-state-disabled:hover, html .ui-button.ui-state-disabled:active { - border: 1px solid $secondary-border-color; - background: $form-button-background; + border: 1px solid var(--red-ui-secondary-border-color); + background: var(--red-ui-form-button-background); } .ui-state-hover, @@ -180,9 +180,9 @@ html .ui-button.ui-state-disabled:active { .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus, .ui-button:hover, .ui-button:focus { - border: 1px solid $secondary-border-color; - background: $workspace-button-background-hover; - color: $workspace-button-color-hover; + border: 1px solid var(--red-ui-secondary-border-color); + background: var(--red-ui-workspace-button-background-hover); + color: var(--red-ui-workspace-button-color-hover); } .ui-state-active, @@ -191,10 +191,10 @@ html .ui-button.ui-state-disabled:active { a.ui-button:active, .ui-button:active, .ui-button.ui-state-active:hover { - border: 1px solid $secondary-border-color; - background: $workspace-button-background-active; + border: 1px solid var(--red-ui-secondary-border-color); + background: var(--red-ui-workspace-button-background-active); font-weight: normal; - color: $workspace-button-color-active; + color: var(--red-ui-workspace-button-color-active); } .ui-state-active .ui-icon, .ui-button:active .ui-icon { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/keyboard.scss b/packages/node_modules/@node-red/editor-client/src/sass/keyboard.scss index 8c4e5a3a8..c11aa0592 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/keyboard.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/keyboard.scss @@ -23,9 +23,9 @@ } .keyboard-shortcut-list-header { padding:0 5px 0 5px; - border-bottom: 1px solid $primary-border-color; + border-bottom: 1px solid var(--red-ui-primary-border-color); div { - color: $header-text-color !important; + color: var(--red-ui-header-text-color) !important; } .red-ui-searchBox-container { width: calc(100% - 20px); @@ -49,7 +49,7 @@ } } li:hover { - background: $list-item-background-hover; + background: var(--red-ui-list-item-background-hover); } } .keyboard-shortcut-entry { @@ -78,13 +78,13 @@ width: calc(100% - 160px - 100px - 10px); overflow: hidden; i { - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); margin-right: 5px; } } .keyboard-shortcut-entry-scope { width:100px; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); vertical-align: middle; text-align: right; } @@ -94,13 +94,13 @@ } } .keyboard-shortcut-entry-unassigned { - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); .keyboard-shortcut-entry-key { font-style: italic; } } .keyboard-shortcut-entry-expanded { - background: $list-item-background-selected; + background: var(--red-ui-list-item-background-selected); .keyboard-shortcut-entry-key { width: 150px; } @@ -115,12 +115,12 @@ } } .help-key { - border: 1px solid $tertiary-border-color; + border: 1px solid var(--red-ui-tertiary-border-color); padding: 4px; border-radius: 3px; - background: $tertiary-background; - font-family: $monospace-font; - box-shadow: $shade-color 1px 1px 1px; + background: var(--red-ui-tertiary-background); + font-family: var(--red-ui-monospace-font); + box-shadow: var(--red-ui-shade-color 1px 1px 1px); } .help-key-block { white-space: nowrap; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/library.scss b/packages/node_modules/@node-red/editor-client/src/sass/library.scss index 0d284ffce..bb651e4ea 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/library.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/library.scss @@ -18,13 +18,13 @@ pre { margin: 10px 0; border: none; - color: $primary-text-color; + color: var(--red-ui-primary-text-color); span { padding: 5px 0; } span.error { padding: 5px; - border: 1px solid $form-input-border-error-color; + border: 1px solid var(--red-ui-form-input-border-error-color); margin: 0 1px; } } @@ -52,16 +52,16 @@ .red-ui-clipboard-dialog-tab-clipboard { textarea { - color: $secondary-text-color-active !important; + color: var(--red-ui-secondary-text-color-active) !important; resize: none; width: 100%; border-radius: 4px; - font-family: $monospace-font !important; + font-family: var(--red-ui-monospace-font !important); font-size: 13px !important; height: 100%; line-height: 1.3em; padding: 6px 10px; - background: $clipboard-textarea-background; + background: var(--red-ui-clipboard-textarea-background); } } @@ -80,7 +80,7 @@ right: 0; bottom: 0; padding: 0; - background: $form-input-background; + background: var(--red-ui-form-input-background); &>div { height: 100%; box-sizing: border-box; @@ -89,7 +89,7 @@ .red-ui-clipboard-dialog-box { height: 400px; position:relative; - border:1px solid $primary-border-color; + border:1px solid var(--red-ui-primary-border-color); } #red-ui-clipboard-dialog-export-tab-library-filename { @@ -111,7 +111,7 @@ .red-ui-clipboard-dialog-tabs-content>div.red-ui-clipboard-dialog-export-tab-library-browser { height: calc(100% - 60px); margin-bottom: 13px; - border-bottom: 1px solid $primary-border-color; + border-bottom: 1px solid var(--red-ui-primary-border-color); box-sizing: border-box; } #red-ui-clipboard-dialog-import-tab-library-browser { @@ -124,7 +124,7 @@ position: relative; height: 100%; .red-ui-treeList-container { - background: $secondary-background; + background: var(--red-ui-secondary-background); border: none; border-radius: 0; li { @@ -149,14 +149,14 @@ #red-ui-library-dialog-save-browser { height: calc(100% - 60px); - border: 1px solid $primary-border-color; + border: 1px solid var(--red-ui-primary-border-color); margin-bottom: 10px; } #red-ui-library-dialog-load-browser { - // border: 1px solid $primary-border-color; + // border: 1px solid var(--red-ui-primary-border-color); } #red-ui-library-dialog-load-panes { - border: 1px solid $primary-border-color; + border: 1px solid var(--red-ui-primary-border-color); } @@ -180,15 +180,15 @@ position: relative; li:not(:first-child) .red-ui-clipboard-dialog-import-conflicts-item-header { - // border-top: 1px solid $secondary-border-color; + // border-top: 1px solid var(--red-ui-secondary-border-color); } } .red-ui-clipboard-dialog-import-conflicts-item-header { - background: $tertiary-background; + background: var(--red-ui-tertiary-background); & > span:first-child { - color: $header-text-color; + color: var(--red-ui-header-text-color); padding-left: 4px; font-size: 12px; } @@ -199,7 +199,7 @@ bottom: 0; right: 0px; text-align: center; - color: $form-text-color; + color: var(--red-ui-form-text-color); .form-row & label { padding: 2px 0; line-height: 23px; @@ -210,7 +210,7 @@ height: 100%; width: 80px; text-align: center; - border-left: 1px solid $secondary-border-color; + border-left: 1px solid var(--red-ui-secondary-border-color); } input[type="checkbox"] { display: inline-block; @@ -265,7 +265,7 @@ span:nth-child(3), span:nth-child(4) { flex-grow: 0; padding-right: 5px; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); font-size: 0.9em; } } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/mixins.scss b/packages/node_modules/@node-red/editor-client/src/sass/mixins.scss index 9214ea37b..6262597a1 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/mixins.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/mixins.scss @@ -31,33 +31,33 @@ } @mixin component-border { - border: 1px solid $primary-border-color; + border: 1px solid var(--red-ui-primary-border-color); box-sizing: border-box; } @mixin reset-a-style { - color: $workspace-button-color !important; - background: $workspace-button-background; + color: var(--red-ui-workspace-button-color) !important; + background: var(--red-ui-workspace-button-background); text-decoration: none; &.disabled, &:disabled { cursor: default; - color: $workspace-button-color-disabled !important; + color: var(--red-ui-workspace-button-color-disabled) !important; } &:hover, &:focus { text-decoration: none; } - &:not(.disabled):not(:disabled):hover, { - color: $workspace-button-color-hover !important; - background: $workspace-button-background-hover; + &:not(.disabled):not(:disabled):hover { + color: var(--red-ui-workspace-button-color-hover) !important; + background: var(--red-ui-workspace-button-background-hover); } &:not(.disabled):not(:disabled):focus { - color: $workspace-button-color-focus !important; + color: var(--red-ui-workspace-button-color-focus) !important; } &:not(.disabled):not(:disabled):active { - color: $workspace-button-color-active !important; - background: $workspace-button-background-active; + color: var(--red-ui-workspace-button-color-active) !important; + background: var(--red-ui-workspace-button-background-active); text-decoration: none; } } @@ -68,14 +68,14 @@ box-sizing: border-box; display: inline-block; - border: 1px solid $form-input-border-color; + border: 1px solid var(--red-ui-form-input-border-color); text-align: center; margin:0; cursor:pointer; &.selected:not(.disabled):not(:disabled) { - color: $workspace-button-color-selected !important; - background: $workspace-button-background-active; + color: var(--red-ui-workspace-button-color-selected) !important; + background: var(--red-ui-workspace-button-background-active); } .button-group &:not(:first-child) { border-left: none; @@ -108,23 +108,23 @@ } &:focus { - outline: 1px solid $workspace-button-color-focus-outline; + outline: 1px solid var(--red-ui-workspace-button-color-focus-outline); outline-offset: 1px; } &.primary { - border-color: $workspace-button-background-primary; - color: $workspace-button-color-primary !important; - background: $workspace-button-background-primary; + border-color: var(--red-ui-workspace-button-background-primary); + color: var(--red-ui-workspace-button-color-primary) !important; + background: var(--red-ui-workspace-button-background-primary); &.disabled, &.ui-state-disabled { background: none; - color: $workspace-button-color !important; - border-color: $form-input-border-color; + color: var(--red-ui-workspace-button-color) !important; + border-color: var(--red-ui-form-input-border-color); } &:not(.disabled):not(.ui-button-disabled):hover { - border-color: $workspace-button-background-primary-hover; - background: $workspace-button-background-primary-hover; - color: $workspace-button-color-primary !important; + border-color: var(--red-ui-workspace-button-background-primary-hover); + background: var(--red-ui-workspace-button-background-primary-hover); + color: var(--red-ui-workspace-button-color-primary) !important; } } &.secondary { @@ -151,7 +151,7 @@ margin-bottom: 1px; &.selected:not(.disabled):not(:disabled) { border-bottom-width: 2px; - border-bottom-color: $form-input-border-selected-color; + border-bottom-color: var(--red-ui-form-input-border-selected-color); margin-bottom: 0; cursor: default; } @@ -166,7 +166,7 @@ padding: 6px 14px; margin-right: 8px; &:not(.disabled):hover { - //color: $workspace-button-color; + //color: var(--red-ui-workspace-button-color); } &.disabled { background: none; @@ -187,8 +187,8 @@ } @mixin component-footer { - border-top: 1px solid $primary-border-color; - background: $primary-background; + border-top: 1px solid var(--red-ui-primary-border-color); + background: var(--red-ui-primary-background); text-align: right; position: absolute; bottom: 0; @@ -231,7 +231,7 @@ } @mixin component-shadow { - box-shadow: 1px 1px 4px $shadow; + box-shadow: 1px 1px 4px var(--red-ui-shadow); } @@ -241,7 +241,7 @@ left: 0; bottom: 0; right: 0; - background: $shade-color; + background: var(--red-ui-shade-color); z-index: 5; } .red-ui-shade { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/notifications.scss b/packages/node_modules/@node-red/editor-client/src/sass/notifications.scss index 52e8509b3..efae432b2 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/notifications.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/notifications.scss @@ -27,10 +27,10 @@ position: relative; padding: 8px 18px 0px; margin-bottom: 4px; - box-shadow: 0 1px 1px 1px $shadow; - background-color: $secondary-background; - color: $primary-text-color; - border: 1px solid $notification-border-default; + box-shadow: 0 1px 1px 1px var(--red-ui-shadow); + background-color: var(--red-ui-secondary-background); + color: var(--red-ui-primary-text-color); + border: 1px solid var(--red-ui-notification-border-default); border-left-width: 16px; overflow: hidden; .ui-dialog-buttonset { @@ -50,13 +50,13 @@ } .red-ui-notification-success { - border-color: $notification-border-success; + border-color: var(--red-ui-notification-border-success); } .red-ui-notification-warning { - border-color: $notification-border-warning; + border-color: var(--red-ui-notification-border-warning); } .red-ui-notification-error { - border-color: $notification-border-error; + border-color: var(--red-ui-notification-border-error); } .red-ui-notification-compact { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/palette-editor.scss b/packages/node_modules/@node-red/editor-client/src/sass/palette-editor.scss index 34fbd3e07..ca387782b 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/palette-editor.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/palette-editor.scss @@ -27,7 +27,7 @@ left:0; padding: 0; box-sizing:border-box; - background: $secondary-background; + background: var(--red-ui-secondary-background); .red-ui-editableList-container { border: none; @@ -37,27 +37,27 @@ li { // border: none; - // border-top: 1px solid $primary-border-color; + // border-top: 1px solid var(--red-ui-primary-border-color); padding: 0px; .red-ui-button { min-width: 60px; } .disabled { - // background: $secondary-background-inactive;//f3f3f3; + // background: var(--red-ui-secondary-background-inactive;//f3f3f3); .red-ui-palette-module-name { font-style: italic; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); } .red-ui-palette-module-version { - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); } .red-ui-palette-module-errors .fa-warning { opacity: 0.5; } ul.red-ui-palette-module-error-list li { - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); } @@ -66,7 +66,7 @@ padding: 12px 16px; } &:last-child { - // border-bottom: 1px solid $primary-border-color; + // border-bottom: 1px solid var(--red-ui-primary-border-color); } } @@ -79,14 +79,14 @@ bottom:0 } .red-ui-palette-editor-toolbar { - background: $primary-background; + background: var(--red-ui-primary-background); box-sizing: border-box; padding: 8px 10px; - border-bottom: 1px solid $primary-border-color; + border-bottom: 1px solid var(--red-ui-primary-border-color); text-align: right; } .red-ui-palette-module-shade-status { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } .red-ui-palette-module-updated { margin-left: 10px; @@ -98,7 +98,7 @@ .red-ui-palette-module-description { margin-left: 20px; font-size: 0.9em; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } .red-ui-palette-module-link { } @@ -120,7 +120,7 @@ } } .red-ui-palette-module-set { - border:1px solid $secondary-border-color; + border:1px solid var(--red-ui-secondary-border-color); border-radius: 0; padding: 5px; position: relative; @@ -138,7 +138,7 @@ } .red-ui-palette-module-type { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); padding-left: 5px; font-size: 0.9em; @include enable-selection; @@ -150,8 +150,8 @@ border-radius: 3px; vertical-align: middle; margin-right: 5px; - background: $primary-background; - border: 1px dashed $secondary-border-color; + background: var(--red-ui-primary-background); + border: 1px dashed var(--red-ui-secondary-border-color); } .red-ui-palette-module-set-button-group { position: absolute; @@ -160,35 +160,35 @@ } .red-ui-palette-module-set-disabled { - background: $list-item-background-disabled; + background: var(--red-ui-list-item-background-disabled); .red-ui-palette-module-type { - color: $secondary-text-color-disabled-active; + color: var(--red-ui-secondary-text-color-disabled-active); } } .red-ui-palette-module-more { padding: 0 !important; margin-top: 10px; margin-bottom: 10px; - background: $tab-background-inactive; + background: var(--red-ui-tab-background-inactive); a { display: block; text-align: center; padding: 12px 8px; - color: $text-color-code; + color: var(--red-ui-text-color-code); &:hover { text-decoration: none; - background: $tab-background-hover; + background: var(--red-ui-tab-background-hover); } } } } .red-ui-palette-module-meta { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); position: relative; &.disabled { - color: $secondary-text-color-disabled; + color: var(--red-ui-secondary-text-color-disabled); } .fa { @@ -198,7 +198,7 @@ } } .red-ui-palette-module-name { - color: $primary-text-color; + color: var(--red-ui-primary-text-color); white-space: nowrap; @include enable-selection; } @@ -216,7 +216,7 @@ } } .red-ui-palette-module-meta .fa-warning { - color: $text-color-warning; + color: var(--red-ui-text-color-warning); } ul.red-ui-palette-module-error-list { display: inline-block; @@ -264,9 +264,9 @@ button.red-ui-palette-editor-upload-button { right: 0; top: 44px; padding: 20px; - background: $secondary-background; - border-bottom: 1px $secondary-border-color solid; - box-shadow: 1px 1px 4px $shadow; + background: var(--red-ui-secondary-background); + border-bottom: 1px var(--red-ui-secondary-border-color solid); + box-shadow: 1px 1px 4px var(--red-ui-shadow); .placeholder-input { width: calc(100% - 180px); diff --git a/packages/node_modules/@node-red/editor-client/src/sass/palette.scss b/packages/node_modules/@node-red/editor-client/src/sass/palette.scss index 800f34079..0d123918a 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/palette.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/palette.scss @@ -20,7 +20,7 @@ top: 0px; bottom: 0px; left:0px; - background: $primary-background; + background: var(--red-ui-primary-background); width: 180px; text-align: center; @include disable-selection; @@ -55,26 +55,26 @@ .red-ui-palette-search { position: relative; overflow: hidden; - background: $secondary-background; + background: var(--red-ui-secondary-background); text-align: center; height: 35px; padding: 3px; - border-bottom: 1px solid $primary-border-color; + border-bottom: 1px solid var(--red-ui-primary-border-color); box-sizing:border-box; } .red-ui-palette-category { - border-bottom: 1px solid $secondary-border-color; + border-bottom: 1px solid var(--red-ui-secondary-border-color); } .red-ui-palette-content { - background: $palette-content-background; + background: var(--red-ui-palette-content-background); padding: 3px; } .red-ui-palette-header { position: relative; - background: $palette-header-background; - color: $palette-header-color; + background: var(--red-ui-palette-header-background); + color: var(--red-ui-palette-header-color); cursor: pointer; text-align: left; padding: 9px; @@ -83,7 +83,7 @@ overflow: hidden; user-select: none; &:hover { - background: $palette-header-background !important; + background: var(--red-ui-palette-header-background) !important; } } .red-ui-palette-header > i { @@ -106,7 +106,7 @@ clear: both; } .red-ui-palette-label { - color: $node-label-color; + color: var(--red-ui-node-label-color); font-size: 13px; margin: 4px 0 4px 32px; line-height: 20px; @@ -121,11 +121,11 @@ .red-ui-palette-node { // display: inline-block; cursor: move; - background: $secondary-background; + background: var(--red-ui-secondary-background); margin: 10px auto; height: 25px; border-radius: 5px; - border: 1px solid $node-border; + border: 1px solid var(--red-ui-node-border); background-position: 5% 50%; background-repeat: no-repeat; width: 120px; @@ -141,7 +141,7 @@ } .red-ui-palette-node:hover { border-color: transparent; - box-shadow: 0 0 0 2px $node-selected-color; + box-shadow: 0 0 0 2px var(--red-ui-node-selected-color); } .red-ui-palette-port { position: absolute; @@ -149,11 +149,11 @@ left: -5px; box-sizing: border-box; -moz-box-sizing: border-box; - background: $node-port-background; + background: var(--red-ui-node-port-background); border-radius: 3px; width: 10px; height: 10px; - border: 1px solid $node-border; + border: 1px solid var(--red-ui-node-border); } .red-ui-palette-port-output { left:auto; @@ -161,7 +161,7 @@ } .red-ui-palette-node:hover .red-ui-palette-port { - background-color: $node-port-background-hover; + background-color: var(--red-ui-node-port-background-hover); } .red-ui-palette-icon-container { position: absolute; @@ -170,14 +170,14 @@ bottom:0; left:0; width: 30px; - border-right: 1px solid $node-icon-background-color; - background-color: $node-icon-background-color; + border-right: 1px solid var(--red-ui-node-icon-background-color); + background-color: var(--red-ui-node-icon-background-color); } .red-ui-palette-icon-container-right { left: auto; right: 0; border-right: none; - border-left: 1px solid $node-icon-background-color; + border-left: 1px solid var(--red-ui-node-icon-background-color); } .red-ui-palette-icon { display: inline-block; @@ -198,7 +198,7 @@ background: none; } .red-ui-palette-icon-fa { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); font-size: 18px; } } @@ -249,12 +249,12 @@ // width: 30px; // height: 25px; border-radius: 3px; - border: 1px solid $node-border; + border: 1px solid var(--red-ui-node-border); background-position: 5% 50%; background-repeat: no-repeat; background-size: contain; position: relative; - background-color: $node-icon-background-color; + background-color: var(--red-ui-node-icon-background-color); text-align: center; .red-ui-palette-icon { @@ -278,7 +278,7 @@ background: none; } .red-ui-palette-icon-fa { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); font-size: 16px; } } @@ -318,5 +318,5 @@ .red-ui-node-label { white-space: nowrap; margin-left: 4px; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/panels.scss b/packages/node_modules/@node-red/editor-client/src/sass/panels.scss index e820992b4..c782b341b 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/panels.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/panels.scss @@ -35,12 +35,12 @@ .red-ui-panels-separator { flex: 0 0 auto; - border-top: 1px solid $secondary-border-color; - border-bottom: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); + border-bottom: 1px solid var(--red-ui-secondary-border-color); height: 7px; box-sizing: border-box; cursor: ns-resize; - background-color: $primary-background; + background-color: var(--red-ui-primary-background); &:before { content: ''; @@ -55,7 +55,7 @@ mask-position: center; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; - background-color: $grip-color; + background-color: var(--red-ui-grip-color); } } @@ -80,14 +80,14 @@ vertical-align: top; border-top: none; border-bottom: none; - border-left: 1px solid $secondary-border-color; - border-right: 1px solid $secondary-border-color; + border-left: 1px solid var(--red-ui-secondary-border-color); + border-right: 1px solid var(--red-ui-secondary-border-color); height: 100%; width: 7px; display: inline-block; cursor: ew-resize; - background-color: $primary-background; - + background-color: var(--red-ui-primary-background); + &:before { content: ''; display: block; @@ -101,7 +101,7 @@ mask-position: 50% 50%; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; - background-color: $grip-color; + background-color: var(--red-ui-grip-color); } } } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/popover.scss b/packages/node_modules/@node-red/editor-client/src/sass/popover.scss index 2eb167ef4..7e504b59b 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/popover.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/popover.scss @@ -25,7 +25,7 @@ color: var(--red-ui-popover-color); border-radius: 4px; z-index: 1000; - font-family: $primary-font; + font-family: var(--red-ui-primary-font); font-size: 14px; line-height: 1.4em; @include component-shadow; @@ -146,7 +146,7 @@ .red-ui-popover-key { font-size: 11px; - font-family: $monospace-font; + font-family: var(--red-ui-monospace-font); margin-left: 3px; border: 1px solid var(--red-ui-popover-color); border-radius:3px; @@ -163,42 +163,42 @@ color: var(--red-ui-popover-color) !important; } a:focus { - outline: 1px solid $form-input-focus-color; + outline: 1px solid var(--red-ui-form-input-focus-color); } } .red-ui-popover a.red-ui-button, .red-ui-popover button.red-ui-button { &:not(.primary) { - border-color: $popover-button-border-color; + border-color: var(--red-ui-popover-button-border-color); background: var(--red-ui-popover-background); color: var(--red-ui-popover-color) !important; } &:not(.primary):not(.disabled):not(.ui-button-disabled):hover { - border-color: $popover-button-border-color-hover; + border-color: var(--red-ui-popover-button-border-color-hover); } &.primary { - border-color: $popover-button-border-color; + border-color: var(--red-ui-popover-button-border-color); } &.primary:not(.disabled):not(.ui-button-disabled):hover { - border-color: $popover-button-border-color-hover; + border-color: var(--red-ui-popover-button-border-color-hover); } } .red-ui-popover code { border: none; background: none; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); } .red-ui-popover-panel { @include component-shadow; - font-family: $primary-font; - font-size: $primary-font-size; + font-family: var(--red-ui-primary-font); + font-size: var(--red-ui-primary-font-size); position: absolute; box-sizing: border-box; - border: 1px solid $primary-border-color; - background: $secondary-background; + border: 1px solid var(--red-ui-primary-border-color); + background: var(--red-ui-secondary-background); z-index: 2000; } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/projects.scss b/packages/node_modules/@node-red/editor-client/src/sass/projects.scss index 681e7b3f9..0019ba516 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/projects.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/projects.scss @@ -29,7 +29,7 @@ overflow-y: scroll; } .red-ui-sidebar-vc-shade { - background: $primary-background; + background: var(--red-ui-primary-background); } .red-ui-projects-edit-form form { @@ -37,7 +37,7 @@ .form-row { margin-bottom: 15px; label { - color: $primary-text-color; + color: var(--red-ui-primary-text-color); width: 100%; display: block; &.red-ui-projects-edit-form-inline-label { @@ -57,7 +57,7 @@ } .red-ui-projects-edit-form-sublabel { - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); text-align: right; margin-bottom: -15px; font-weight: normal; @@ -76,7 +76,7 @@ font-size: 1.4em; padding: 10px; min-height: 40px; - color: $primary-text-color; + color: var(--red-ui-primary-text-color); } .red-ui-projects-dialog-screen-start-body { min-height: 300px; @@ -132,21 +132,21 @@ margin-left: -1px; padding: 15px; margin-top: -15px; - border: 1px solid $secondary-border-color; + border: 1px solid var(--red-ui-secondary-border-color); border-radius: 3px; } .red-ui-projects-dialog-credentials-box-left { width: 220px; > div { padding: 7px 8px 3px 8px; - border: 1px solid $secondary-border-color; + border: 1px solid var(--red-ui-secondary-border-color); border-radius: 4px; border-top-right-radius: 0; border-bottom-right-radius: 0; - border-right-color: $form-background; + border-right-color: var(--red-ui-form-background); &.disabled { - border-color: $form-background; - border-right-color:$secondary-border-color; + border-color: var(--red-ui-form-background); + border-right-color:var(--red-ui-secondary-border-color); } i { font-size: 1.4em; @@ -173,7 +173,7 @@ } .red-ui-projects-dialog-project-list-container { - border: 1px solid $secondary-border-color; + border: 1px solid var(--red-ui-secondary-border-color); border-radius: 2px; display: flex; flex-direction: column; @@ -197,43 +197,43 @@ .red-ui-projects-dialog-project-list-entry { padding: 12px 0; - color: $list-item-color; - background: $list-item-background; - border-left: 3px solid $list-item-background; - border-right: 3px solid $list-item-background; + color: var(--red-ui-list-item-color); + background: var(--red-ui-list-item-background); + border-left: 3px solid var(--red-ui-list-item-background); + border-right: 3px solid var(--red-ui-list-item-background); &.projects-list-entry-current { &:not(.selectable) { - color: $form-text-color; - background: $list-item-background-selected; - border-left-color:$list-item-border-selected; - border-right-color:$list-item-border-selected; + color: var(--red-ui-form-text-color); + background: var(--red-ui-list-item-background-selected); + border-left-color:var(--red-ui-list-item-border-selected); + border-right-color:var(--red-ui-list-item-border-selected); } i { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } } &.selectable { cursor: pointer; &:hover:not(.selected) { - color: $form-text-color; - background: $list-item-background-hover; - border-left-color:$list-item-background-hover; - border-right-color:$list-item-background-hover; + color: var(--red-ui-form-text-color); + background: var(--red-ui-list-item-background-hover); + border-left-color:var(--red-ui-list-item-background-hover); + border-right-color:var(--red-ui-list-item-background-hover); } } .red-ui-projects-dialog-project-list-entry-icon { i { - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); font-size: 2em; } } &.selected { - color: $form-text-color; - background: $list-item-background-selected; - border-left-color:$list-item-border-selected; - border-right-color:$list-item-border-selected; + color: var(--red-ui-form-text-color); + background: var(--red-ui-list-item-background-selected); + border-left-color:var(--red-ui-list-item-border-selected); + border-right-color:var(--red-ui-list-item-border-selected); } span { display: inline-block; @@ -249,7 +249,7 @@ float: right; margin-right: 20px; font-size: 0.9em; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); padding-top: 4px; } .red-ui-projects-dialog-project-list-entry-tools { @@ -257,7 +257,7 @@ top: 16px; right: 30px; display: none; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } &:hover { .red-ui-projects-dialog-project-list-entry-tools { @@ -274,7 +274,7 @@ width: 1000px; overflow: hidden; padding: 5px 20px; - background: $secondary-background; + background: var(--red-ui-secondary-background); transition: left 0.4s; white-space: nowrap; > span { @@ -289,7 +289,7 @@ position: relative; } .red-ui-projects-dialog-screen-create-type.red-ui-button.toggle.selected:not(.disabled):not(:disabled) { - color: $secondary-text-color-active !important; + color: var(--red-ui-secondary-text-color-active) !important; } .red-ui-projects-dialog-screen-input-status { text-align: right; @@ -298,7 +298,7 @@ right: 8px; width: 70px; height: 30px; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } .red-ui-sidebar-vc { @@ -338,17 +338,17 @@ } .red-ui-palette-module-unused { & > * { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } } .red-ui-palette-module-unknown { - border: 1px dashed $secondary-border-color; - background: $secondary-background-inactive; + border: 1px dashed var(--red-ui-secondary-border-color); + background: var(--red-ui-secondary-background-inactive); } .red-ui-palette-module-not-installed { - border: 1px dashed $text-color-warning; + border: 1px dashed var(--red-ui-text-color-warning); i.fa-warning { - color: $text-color-warning; + color: var(--red-ui-text-color-warning); } } } @@ -365,11 +365,11 @@ } .red-ui-sidebar-vc { .red-ui-editableList-container { - background: $tertiary-background; + background: var(--red-ui-tertiary-background); padding: 0; li { padding:0; - background: $secondary-background; + background: var(--red-ui-secondary-background); } } .red-ui-editableList-border { @@ -384,7 +384,7 @@ box-sizing: border-box; transition: height 0.2s ease-in-out; &:first-child { - // border-bottom: 1px solid $primary-border-color; + // border-bottom: 1px solid var(--red-ui-primary-border-color); } } .red-ui-sidebar-vc-merging { @@ -399,7 +399,7 @@ right:0; height:0; transition: height 0.2s ease-in-out; - background: $tertiary-background; + background: var(--red-ui-tertiary-background); box-sizing: border-box; overflow: hidden; &.red-ui-sidebar-vc-slide-box-top { @@ -408,20 +408,20 @@ left: auto; width: 100%; max-width: 280px; - border-left: 1px solid $primary-border-color; - border-right: 1px solid $primary-border-color; - border-bottom: 1px solid $primary-border-color; - box-shadow: 1px 1px 4px $shadow; + border-left: 1px solid var(--red-ui-primary-border-color); + border-right: 1px solid var(--red-ui-primary-border-color); + border-bottom: 1px solid var(--red-ui-primary-border-color); + box-shadow: 1px 1px 4px var(--red-ui-shadow); - color: $primary-text-color; - background: $tertiary-background; + color: var(--red-ui-primary-text-color); + background: var(--red-ui-tertiary-background); padding: 10px; box-sizing: border-box; } &.red-ui-sidebar-vc-slide-box-bottom { bottom: 0px; - border-top: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); } textarea { @@ -437,15 +437,15 @@ .red-ui-projects-branch-list { position: relative; .red-ui-searchBox-container { - border-top: 1px solid $secondary-border-color; - border-left: 1px solid $secondary-border-color; - border-right: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); + border-left: 1px solid var(--red-ui-secondary-border-color); + border-right: 1px solid var(--red-ui-secondary-border-color); border-top-left-radius: 2px; border-top-right-radius: 2px; overflow: hidden; } .red-ui-editableList { - border: 1px solid $secondary-border-color; + border: 1px solid var(--red-ui-secondary-border-color); border-bottom-left-radius: 2px; border-bottom-right-radius: 2px; & > .red-ui-editableList-border { @@ -456,7 +456,7 @@ padding: 0; li { padding: 0; - background: $secondary-background; + background: var(--red-ui-secondary-background); } } } @@ -483,23 +483,23 @@ .red-ui-sidebar-vc-branch-list-entry { padding: 5px 8px; margin: 0 1px; - color: $list-item-color; - background: $list-item-background; - border-left: 2px solid $list-item-background; - border-right: 2px solid $list-item-background; + color: var(--red-ui-list-item-color); + background: var(--red-ui-list-item-background); + border-left: 2px solid var(--red-ui-list-item-background); + border-right: 2px solid var(--red-ui-list-item-background); cursor: pointer; &.selected { - border-left-color:$list-item-border-selected; - border-right-color:$list-item-border-selected; + border-left-color:var(--red-ui-list-item-border-selected); + border-right-color:var(--red-ui-list-item-border-selected); } i { width: 16px; text-align: center} &.input-error { cursor: default; } &:not(.input-error):hover { - background: $list-item-background-hover; - border-left-color:$list-item-border-selected; - border-right-color:$list-item-border-selected; + background: var(--red-ui-list-item-background-hover); + border-left-color:var(--red-ui-list-item-border-selected); + border-right-color:var(--red-ui-list-item-border-selected); } span { margin-left: 5px; @@ -507,7 +507,7 @@ span.current { float: right; font-size: 0.8em; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); } } @@ -542,7 +542,7 @@ } &.red-ui-help-info-node { text-align: center; - background: $list-item-background; + background: var(--red-ui-list-item-background); white-space: normal; height: auto; } @@ -556,63 +556,63 @@ overflow: hidden; cursor: pointer; &:hover { - background: $secondary-background-hover; + background: var(--red-ui-secondary-background-hover); } } .red-ui-sidebar-vc-commit-more { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); text-align: center; padding: 10px; font-style: italic; } .red-ui-sidebar-vc-commit-sha { float: right; - font-family: $monospace-font; - color: $vcCommitShaColor; + font-family: var(--red-ui-monospace-font); + color: var(--red-ui-vcCommitShaColor); display: inline-block; font-size: 0.85em; margin-left: 5px; } .red-ui-sidebar-vc-commit-subject { - color: $primary-text-color; + color: var(--red-ui-primary-text-color); } .red-ui-sidebar-vc-commit-refs { min-height: 22px; } .red-ui-sidebar-vc-commit-ref { - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); font-size: 0.7em; - border: 1px solid $tertiary-border-color; + border: 1px solid var(--red-ui-tertiary-border-color); border-radius: 10px; padding: 2px 5px; margin-right: 5px; } .red-ui-sidebar-vc-commit-date { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); font-size: 0.85em; } .red-ui-sidebar-vc-commit-user { float: right; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); font-size: 0.85em; } .red-ui-sidebar-vc-commit-head { } .red-ui-sidebar-vc-change-header { - color: $primary-text-color; - background: $tertiary-background; + color: var(--red-ui-primary-text-color); + background: var(--red-ui-tertiary-background); padding: 4px 10px; height: 30px; box-sizing: border-box; - border-top: 1px solid $secondary-border-color; - border-bottom: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); + border-bottom: 1px solid var(--red-ui-secondary-border-color); i { transition: all 0.2s ease-in-out; } } .red-ui-sidebar-vc-repo-toolbar { - color: $primary-text-color; - background: $tertiary-background; + color: var(--red-ui-primary-text-color); + background: var(--red-ui-tertiary-background); padding: 10px; box-sizing: border-box; } @@ -637,7 +637,7 @@ .red-ui-projects-file-listing-container > .red-ui-editableList > .red-ui-editableList-border { border-radius: 0; border: none; - border-top: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); } .red-ui-editableList-container .red-ui-projects-dialog-file-list { @@ -654,39 +654,39 @@ } .red-ui-projects-dialog-file-list-entry { padding: 3px 0; - border-left: 2px solid $list-item-background; - border-right: 2px solid $list-item-background; - background: $list-item-background; + border-left: 2px solid var(--red-ui-list-item-background); + border-right: 2px solid var(--red-ui-list-item-background); + background: var(--red-ui-list-item-background); &.projects-list-entry-current { &:not(.selectable) { - background: $list-item-background-selected; + background: var(--red-ui-list-item-background-selected); } i { - color: $secondary-text-color-selected; + color: var(--red-ui-secondary-text-color-selected); } } &.selectable { cursor: pointer; &:hover { - background: $list-item-background-hover; - border-left-color:$list-item-border-selected; - border-right-color:$list-item-border-selected; + background: var(--red-ui-list-item-background-hover); + border-left-color:var(--red-ui-list-item-border-selected); + border-right-color:var(--red-ui-list-item-border-selected); } } &.unselectable { - color: $secondary-text-color-disabled; + color: var(--red-ui-secondary-text-color-disabled); } i { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); width: 16px; text-align: center; } &.selected { - background: $list-item-background-selected; - border-left-color:$list-item-border-selected; - border-right-color:$list-item-border-selected; + background: var(--red-ui-list-item-background-selected); + border-left-color:var(--red-ui-list-item-border-selected); + border-right-color:var(--red-ui-list-item-border-selected); } span { display: inline-block; @@ -696,7 +696,7 @@ margin: 0 10px 0 0px; .fa-angle-right { - color: $primary-text-color; + color: var(--red-ui-primary-text-color); transition: all 0.2s ease-in-out; } @@ -747,7 +747,7 @@ div.red-ui-projects-dialog-ssh-public-key { padding: 10px 5px; cursor: pointer; &:hover { - background: $list-item-background-hover; + background: var(--red-ui-list-item-background-hover); } } } @@ -756,7 +756,7 @@ div.red-ui-projects-dialog-ssh-public-key { position: relative; .red-ui-editableList-container { padding: 1px; - background: $tertiary-background; + background: var(--red-ui-tertiary-background); li:last-child { border-bottom: none; } @@ -775,7 +775,7 @@ div.red-ui-projects-dialog-ssh-public-key { text-align: center; min-width: 30px; vertical-align: top; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } .entry-name { min-width: 250px; @@ -784,7 +784,7 @@ div.red-ui-projects-dialog-ssh-public-key { font-weight: bold; } .entry-detail { - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); font-size: 0.9em; } @@ -802,9 +802,9 @@ div.red-ui-projects-dialog-ssh-public-key { position: relative; margin-top: 10px; margin-bottom: 20px; - background: $secondary-background; + background: var(--red-ui-secondary-background); border-radius: 4px; - border: 1px solid $secondary-border-color; + border: 1px solid var(--red-ui-secondary-border-color); .red-ui-projects-edit-form-sublabel { margin-top: -8px !important; margin-right: 50px; @@ -819,7 +819,7 @@ div.red-ui-projects-dialog-ssh-public-key { .red-ui-projects-dialog-list-dialog-header { font-weight: bold; - background: $primary-background; + background: var(--red-ui-primary-background); margin-top: 0 !important; padding: 5px 10px; margin-bottom: 10px; @@ -830,5 +830,5 @@ div.red-ui-projects-dialog-ssh-public-key { padding: 8px 20px 20px; } .red-ui-settings-section-description { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/radialMenu.scss b/packages/node_modules/@node-red/editor-client/src/sass/radialMenu.scss index deeabc4fc..3348e945a 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/radialMenu.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/radialMenu.scss @@ -15,8 +15,8 @@ **/ .red-ui-editor-radial-menu { - font-size: $primary-font-size; - font-family: $primary-font; + font-size: var(--red-ui-primary-font-size); + font-family: var(--red-ui-primary-font); position: absolute; top: 0; left:0; @@ -29,8 +29,8 @@ border-radius: 80px; width: 160px; height: 160px; - background: $shadow; - border: 1px solid $primary-border-color; + background: var(--red-ui-shadow); + border: 1px solid var(--red-ui-primary-border-color); } } @@ -39,20 +39,20 @@ border-radius: 20px; width: 50px; height: 50px; - background: $secondary-background; - border: 2px solid $primary-border-color; + background: var(--red-ui-secondary-background); + border: 2px solid var(--red-ui-primary-border-color); text-align: center; line-height:50px; &.selected { - background: $workspace-button-background-hover; + background: var(--red-ui-workspace-button-background-hover); } } .red-ui-editor-radial-menu-opt-disabled { - border-color: $tertiary-border-color; - color: $tertiary-border-color; + border-color: var(--red-ui-tertiary-border-color); + color: var(--red-ui-tertiary-border-color); } .red-ui-editor-radial-menu-opt-active { - background: $secondary-background-hover; + background: var(--red-ui-secondary-background-hover); } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/search.scss b/packages/node_modules/@node-red/editor-client/src/sass/search.scss index cce1e69e4..ed4eee8cb 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/search.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/search.scss @@ -22,9 +22,9 @@ left: 50%; margin-left: -250px; top: 0px; - border: 1px solid $primary-border-color; - box-shadow: 0 0 10px $shadow; - background: $secondary-background; + border: 1px solid var(--red-ui-primary-border-color); + box-shadow: 0 0 10px var(--red-ui-shadow); + background: var(--red-ui-secondary-background); .red-ui-searchBox-container { display: inline-block; @@ -47,7 +47,7 @@ .red-ui-search-container { border-top-left-radius: 5px; border-top-right-radius: 5px; - border: 1px dashed $primary-border-color; + border: 1px dashed var(--red-ui-primary-border-color); border-bottom: none; padding: 0; width: 100%; @@ -56,8 +56,8 @@ display: none; height: 150px; .red-ui-editableList-container { - border: 1px dashed $primary-border-color; - border-top: 1px solid $secondary-border-color; + border: 1px dashed var(--red-ui-primary-border-color); + border-top: 1px solid var(--red-ui-secondary-border-color); } } .red-ui-search-result { @@ -73,7 +73,7 @@ } } .red-ui-search-result-separator { - border-bottom: 3px solid $secondary-border-color; + border-bottom: 3px solid var(--red-ui-secondary-border-color); } .red-ui-search-result-node { position: relative; @@ -89,7 +89,7 @@ height: 7px; top:4px; left:-4px; - background: $node-port-background; + background: var(--red-ui-node-port-background); box-sizing: border-box; } .red-ui-search-result-node-output{ @@ -107,26 +107,26 @@ margin-left:8px; } .red-ui-search-result-node-label { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } } .red-ui-search-container { padding: 3px; - background: $form-input-background; - border-bottom: 1px solid $secondary-border-color; + background: var(--red-ui-form-input-background); + border-bottom: 1px solid var(--red-ui-secondary-border-color); } .red-ui-search-results-container { position:relative; height: 300px; padding: 5px; - background: $primary-background; + background: var(--red-ui-primary-background); .red-ui-search-results-list { } .red-ui-editableList-container { padding: 0; - background: $primary-background; + background: var(--red-ui-primary-background); li { padding: 0; } @@ -137,21 +137,21 @@ display: flex; align-items: start; cursor: pointer; - color: $list-item-color; - background: $list-item-background; - border-left: 3px solid $list-item-background; - border-right: 3px solid $list-item-background; + color: var(--red-ui-list-item-color); + background: var(--red-ui-list-item-background); + border-left: 3px solid var(--red-ui-list-item-background); + border-right: 3px solid var(--red-ui-list-item-background); li.selected & { - background: $list-item-background-selected; - border-left-color: $list-item-border-selected; - border-right-color: $list-item-border-selected; + background: var(--red-ui-list-item-background-selected); + border-left-color: var(--red-ui-list-item-border-selected); + border-right-color: var(--red-ui-list-item-border-selected); } &:hover { text-decoration: none; - color: $form-text-color; - background: $list-item-background-hover; - border-left-color:$list-item-background-hover; - border-right-color:$list-item-background-hover; + color: var(--red-ui-form-text-color); + background: var(--red-ui-list-item-background-hover); + border-left-color:var(--red-ui-list-item-background-hover); + border-right-color:var(--red-ui-list-item-background-hover); } &:after { content: ""; @@ -165,7 +165,7 @@ float:left; height: 25px; border-radius: 3px; - border: 1px solid $node-border; + border: 1px solid var(--red-ui-node-border); background-position: 5% 50%; background-repeat: no-repeat; background-size: contain; @@ -182,28 +182,28 @@ flex-grow: 1; } .red-ui-search-result-node-label { - color: $primary-text-color; + color: var(--red-ui-primary-text-color); } .red-ui-search-result-node-type { font-style: italic; font-size: 0.9em; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); } .red-ui-search-result-node-flow { float:right; font-size: 0.8em; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); } .red-ui-search-result-node-id { display:none; font-size: 0.8em; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); } .red-ui-search-empty { padding: 10px; text-align: center; font-style: italic; - color: $form-placeholder-color; + color: var(--red-ui-form-placeholder-color); } .red-ui-search-history { button { @@ -229,12 +229,12 @@ } .red-ui-search-result-action { - color: $primary-text-color; + color: var(--red-ui-primary-text-color); } .red-ui-search-result-action-key { position: absolute; top: 9px; right: 0; margin-right: 10px; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss b/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss index 18b186bad..21a57d29d 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss @@ -20,7 +20,7 @@ right: 0px; bottom: 0px; width: 315px; - background: $primary-background; + background: var(--red-ui-primary-background); box-sizing: border-box; z-index: 10; @include component-border; @@ -32,7 +32,7 @@ #red-ui-sidebar-content { position: absolute; - background: $secondary-background; + background: var(--red-ui-secondary-background); top: 35px; right: 0; bottom: 25px; @@ -47,7 +47,7 @@ bottom:10px; width: 7px; // z-index: 11; - background-color: $primary-background; + background-color: var(--red-ui-primary-background); cursor: col-resize; &:before { @@ -63,7 +63,7 @@ mask-position: 50% 50%; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; - background-color: $grip-color; + background-color: var(--red-ui-grip-color); } } @@ -82,11 +82,11 @@ .sidebar-header, /* Deprecated -> red-ui-sidebar-header */ .red-ui-sidebar-header { - color: $primary-text-color; + color: var(--red-ui-primary-text-color); text-align: right; padding: 8px 10px; - background: $primary-background; - border-bottom: 1px solid $secondary-border-color; + background: var(--red-ui-primary-background); + border-bottom: 1px solid var(--red-ui-secondary-border-color); white-space: nowrap; } @@ -138,9 +138,9 @@ button.red-ui-sidebar-header-button-toggle { top: calc(50% - 26px); padding:15px 8px; - border:1px solid $primary-border-color; - background:$primary-background; - color: $secondary-text-color; + border:1px solid var(--red-ui-primary-border-color); + background:var(--red-ui-primary-background); + color: var(--red-ui-secondary-text-color); text-align: center; cursor: pointer; } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/style-custom-theme.scss b/packages/node_modules/@node-red/editor-client/src/sass/style-custom-theme.scss new file mode 100644 index 000000000..1202d9fb7 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/sass/style-custom-theme.scss @@ -0,0 +1,18 @@ +/** +* Copyright JS Foundation and other contributors, http://js.foundation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +@import "colors"; +@import "variables"; \ No newline at end of file diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss index 5c8d0ba94..c8e44e26e 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss @@ -16,7 +16,7 @@ .red-ui-sidebar-node-config { position: relative; - background: $secondary-background; + background: var(--red-ui-secondary-background); height: 100%; overflow-y:auto; @include disable-selection; @@ -40,11 +40,11 @@ ul.red-ui-sidebar-node-config-list { &.selected { border-color: transparent; - box-shadow: 0 0 0 2px $node-selected-color; + box-shadow: 0 0 0 2px var(--red-ui-node-selected-color); } &.highlighted { border-color: transparent; - outline: dashed $node-selected-color 4px; + outline: dashed var(--red-ui-node-selected-color 4px); } } .red-ui-palette-label { @@ -58,7 +58,7 @@ ul.red-ui-sidebar-node-config-list { .red-ui-palette-icon-container { font-size: 12px; line-height: 30px; - background-color: $node-icon-background-color; + background-color: var(--red-ui-node-icon-background-color); border-top-right-radius: 4px; border-bottom-right-radius: 4px; a { @@ -67,10 +67,10 @@ ul.red-ui-sidebar-node-config-list { bottom: 0; left: 0; right: 0; - color: $node-port-label-color; + color: var(--red-ui-node-port-label-color); &:hover { text-decoration: none; - background: $node-port-background-hover; + background: var(--red-ui-node-port-background-hover); } } } @@ -78,12 +78,12 @@ ul.red-ui-sidebar-node-config-list { .red-ui-palette-node-config { width: 160px; height: 30px; - background: $node-config-background; - color: $primary-text-color; + background: var(--red-ui-node-config-background); + color: var(--red-ui-primary-text-color); cursor: pointer; } ul.red-ui-sidebar-node-config-list li.red-ui-palette-node-config-type { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); text-align: right; padding-right: 3px; &:not(:first-child) { @@ -91,21 +91,21 @@ ul.red-ui-sidebar-node-config-list li.red-ui-palette-node-config-type { } } .red-ui-palette-node-config-none { - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); text-align:right; padding-right: 3px; } .red-ui-palette-node-config-unused,.red-ui-palette-node-config-disabled { - border-color: $primary-border-color; - background: $secondary-background-inactive; + border-color: var(--red-ui-primary-border-color); + background: var(--red-ui-secondary-background-inactive); border-style: dashed; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); } .red-ui-palette-node-config-disabled { opacity: 0.6; font-style: italic; i { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); margin-right: 5px; } } @@ -116,8 +116,8 @@ ul.red-ui-sidebar-node-config-list li.red-ui-palette-node-config-type { height: 38px; line-height: 38px; padding: 0 8px; - background: $palette-header-background; + background: var(--red-ui-palette-header-background); font-size: 0.8em; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); font-weight: normal; } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-context.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-context.scss index b98d452c1..fc4c78afb 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tab-context.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-context.scss @@ -63,12 +63,12 @@ .red-ui-sidebar-context-updated { text-align: right; font-size: 11px; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); padding: 1px 3px; } .red-ui-sidebar-context-property-storename { display: block; font-size: 0.8em; font-style: italic; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-help.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-help.scss index fe4f9fb84..f82b97116 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tab-help.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-help.scss @@ -2,7 +2,7 @@ // height: calc(100% - 39px); } .red-ui-help-search { - border-bottom: 1px solid $secondary-border-color; + border-bottom: 1px solid var(--red-ui-secondary-border-color); } .red-ui-sidebar-help-toc { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-info.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-info.scss index 9c63f2119..57dc7d6e3 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tab-info.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-info.scss @@ -32,7 +32,7 @@ display: inline-block; margin-left: 5px; } - border-bottom: 1px solid $secondary-border-color; + border-bottom: 1px solid var(--red-ui-secondary-border-color); } table.red-ui-info-table { font-size: 14px; @@ -40,8 +40,8 @@ table.red-ui-info-table { width: 100%; } table.red-ui-info-table tr:not(.blank) { - border-top: 1px solid $secondary-border-color; - border-bottom: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); + border-bottom: 1px solid var(--red-ui-secondary-border-color); } .red-ui-help-property-expand { font-size: 0.8em; @@ -57,7 +57,7 @@ table.red-ui-info-table tr.blank { th { text-align: left; font-weight: 500; - color: $primary-text-color; + color: var(--red-ui-primary-text-color); padding: 6px 3px 3px; } >* { @@ -69,9 +69,9 @@ table.red-ui-info-table tr.blank { a { display: block; - color: $primary-text-color; + color: var(--red-ui-primary-text-color); &:hover,&:focus { - color: $primary-text-color; + color: var(--red-ui-primary-text-color); text-decoration: none; } &:not(.expanded) { @@ -103,36 +103,36 @@ table.red-ui-info-table tr.blank { } .red-ui-help-info-none { font-style: italic; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); } table.red-ui-info-table tr:not(.blank) td:first-child{ - color: $header-text-color; + color: var(--red-ui-header-text-color); vertical-align: top; width: 90px; padding: 3px 3px 3px 6px; - background:$tertiary-background; - border-right: 1px solid $secondary-border-color; + background:var(--red-ui-tertiary-background); + border-right: 1px solid var(--red-ui-secondary-border-color); } table.red-ui-info-table tr:not(.blank) td:last-child{ padding: 3px 3px 3px 6px; - color: $primary-text-color; + color: var(--red-ui-primary-text-color); overflow-y: hidden; } div.red-ui-info-table { margin: 5px; } .red-ui-help { - font-size: $primary-font-size; + font-size: var(--red-ui-primary-font-size); line-height: 1.5em; a { - color: $text-color-link; + color: var(--red-ui-text-color-link); text-decoration: none; } a:hover, a:focus { - color: $text-color-link; + color: var(--red-ui-text-color-link); text-decoration: underline; } @@ -143,7 +143,7 @@ div.red-ui-info-table { line-height: 1.3em; margin: 8px auto; &.red-ui-help-title { - border-bottom: 1px solid $tertiary-border-color; + border-bottom: 1px solid var(--red-ui-tertiary-border-color); } } h2 { @@ -168,24 +168,24 @@ div.red-ui-info-table { & > span > p:first-child { } dl.message-properties { - border: 1px solid $secondary-border-color; + border: 1px solid var(--red-ui-secondary-border-color); border-radius: 2px; margin: 5px auto 10px; &>dt { padding: 0px 3px 2px 3px; - font-family: $monospace-font; + font-family: var(--red-ui-monospace-font); font-weight: normal; margin: 5px 3px 1px; - color: $text-color-code; + color: var(--red-ui-text-color-code); white-space: nowrap; &.optional { font-style: italic; } .property-type { - font-family: $primary-font; - color: $primary-text-color; + font-family: var(--red-ui-primary-font); + color: var(--red-ui-primary-text-color); font-style: italic; font-size: 11px; float: right; @@ -204,7 +204,7 @@ div.red-ui-info-table { ol.node-ports { margin: 0; li { - border: 1px solid $secondary-border-color; + border: 1px solid var(--red-ui-secondary-border-color); border-radius: 2px; list-style-position: inside; padding: 3px; @@ -224,7 +224,7 @@ div.red-ui-info-table { transition: transform 0.2s ease-in-out; margin-right: 4px; } - color: $header-text-color; + color: var(--red-ui-header-text-color); &:hover, &:focus { text-decoration: none; } @@ -242,7 +242,7 @@ div.red-ui-info-table { overflow : hidden; } table thead tr { - background-color: var(--red-ui-primary-background); //$primary-text-color; + background-color: var(--red-ui-primary-background); //var(--red-ui-primary-text-color); border-bottom: 1px solid var(--red-ui-secondary-border-color); color: var(--red-ui-header-text-color); text-align: left; @@ -252,7 +252,7 @@ div.red-ui-info-table { padding: 6px 8px; } table tbody tr:nth-of-type(even) { - background-color: var(--red-ui-tertiary-background); //$primary-background; + background-color: var(--red-ui-tertiary-background); //var(--red-ui-primary-background); } } .red-ui-sidebar-info-stack { @@ -273,10 +273,10 @@ div.red-ui-info-table { height: 0; transition: height 0.2s, padding 0.2s; box-sizing: border-box; - border-top: 1px solid $secondary-border-color; - background-color: $secondary-background; + border-top: 1px solid var(--red-ui-secondary-border-color); + background-color: var(--red-ui-secondary-background); padding: 0; - box-shadow: 0 5px 20px 0px $shadow; + box-shadow: 0 5px 20px 0px var(--red-ui-shadow); overflow-y: auto; } .red-ui-sidebar-info.show-tips { @@ -305,7 +305,7 @@ div.red-ui-info-table { font-size: 16px; text-align: center; line-height: 1.9em; - color : $tertiary-text-color; + color : var(--red-ui-tertiary-text-color); @include disable-selection; cursor: default; } @@ -314,14 +314,14 @@ div.red-ui-info-table { top: 4px; right: 6px; a { - color: $secondary-text-color; - border-color: $secondary-border-color !important; + color: var(--red-ui-secondary-text-color); + border-color: var(--red-ui-secondary-border-color) !important; margin-left: 4px; } } .node-info-property-config-node { - border: 1px solid $secondary-border-color; + border: 1px solid var(--red-ui-secondary-border-color); border-radius: 4px; padding: 2px 4px 2px; } @@ -346,7 +346,7 @@ div.red-ui-info-table { } .red-ui-info-outline-project { - border-bottom: 1px solid $secondary-border-color; + border-bottom: 1px solid var(--red-ui-secondary-border-color); } } .red-ui-info-outline, @@ -380,13 +380,13 @@ div.red-ui-info-table { background: none; } .red-ui-palette-icon-fa { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); font-size: 18px; } } &.red-ui-info-outline-item-empty { font-style: italic; - color: $form-placeholder-color; + color: var(--red-ui-form-placeholder-color); } } @@ -414,7 +414,7 @@ div.red-ui-info-table { white-space: nowrap; } .red-ui-search-result-node-label { - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } } @@ -439,16 +439,16 @@ div.red-ui-info-table { right: 1px; padding: 1px 2px 0 1px; text-align: right; - background: $list-item-background; + background: var(--red-ui-list-item-background); .red-ui-treeList-label:hover & { - background: $list-item-background-hover; + background: var(--red-ui-list-item-background-hover); } .red-ui-treeList-label.focus & { - background: $list-item-background-hover; + background: var(--red-ui-list-item-background-hover); } .red-ui-treeList-label.selected & { - background: $list-item-background-selected; + background: var(--red-ui-list-item-background-selected); } @@ -510,7 +510,7 @@ div.red-ui-info-table { } .red-ui-info-outline-item-label { font-style: italic; - color: $secondary-text-color-disabled; + color: var(--red-ui-secondary-text-color-disabled); } .red-ui-icons-flow { opacity: 0.4; @@ -538,7 +538,7 @@ div.red-ui-info-table { -webkit-mask-size: contain; mask-repeat: no-repeat; -webkit-mask-repeat: no-repeat; - background-color: $icons-flow-color; + background-color: var(--red-ui-icons-flow-color); // filter: brightness(2.5); } @@ -549,8 +549,8 @@ div.red-ui-info-table { text-align: left; // padding-left: 9px; // box-sizing: border-box; - // background: $palette-header-background; - // border-bottom: 1px solid $secondary-border-color; + // background: var(--red-ui-palette-header-background); + // border-bottom: 1px solid var(--red-ui-secondary-border-color); .red-ui-searchBox-container { position: absolute; @@ -558,7 +558,7 @@ div.red-ui-info-table { right: 8px; width: calc(100% - 130px); max-width: 250px; - background: $palette-header-background; + background: var(--red-ui-palette-header-background); } } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss b/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss index a5d3003ae..595423888 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss @@ -16,7 +16,7 @@ .red-ui-tabs { position: relative; - background: $tab-background; + background: var(--red-ui-tab-background); overflow: hidden; height: 35px; box-sizing: border-box; @@ -39,18 +39,18 @@ display: block; height: 35px; box-sizing:border-box; - border-bottom: 1px solid $primary-border-color; + border-bottom: 1px solid var(--red-ui-primary-border-color); white-space: nowrap; @include disable-selection; li { box-sizing: border-box; display: inline-block; - border-left: 1px solid $primary-border-color; - border-top: 1px solid $primary-border-color; - border-right: 1px solid $primary-border-color; - border-bottom: 1px solid $primary-border-color; - background: $tab-background-inactive; + border-left: 1px solid var(--red-ui-primary-border-color); + border-top: 1px solid var(--red-ui-primary-border-color); + border-right: 1px solid var(--red-ui-primary-border-color); + border-bottom: 1px solid var(--red-ui-primary-border-color); + background: var(--red-ui-tab-background-inactive); margin: 3px 3px 0 3px; height: 32px; line-height: 29px; @@ -73,7 +73,7 @@ padding-left: 12px; width: 100%; height: 100%; - color: $tab-text-color-inactive; + color: var(--red-ui-tab-text-color-inactive); } a:hover { text-decoration: none; @@ -83,27 +83,27 @@ } &:not(.active) a:hover+a.red-ui-tab-close { - background: $tab-background-hover; + background: var(--red-ui-tab-background-hover); } &.highlighted { - box-shadow: 0px 0px 4px 2px $node-selected-color; - border: dashed 1px $node-selected-color; + box-shadow: 0px 0px 4px 2px var(--red-ui-node-selected-color); + border: dashed 1px var(--red-ui-node-selected-color); } &.active { - background: $tab-background-active; + background: var(--red-ui-tab-background-active); font-weight: bold; - border-bottom: 1px solid $tab-background-active; + border-bottom: 1px solid var(--red-ui-tab-background-active); z-index: 2; a { - color: $tab-text-color-active; + color: var(--red-ui-tab-text-color-active); } a.red-ui-tab-close { - color: $workspace-button-color; - background: $tab-background-active; + color: var(--red-ui-workspace-button-color); + background: var(--red-ui-tab-background-active); &:hover { - background: $workspace-button-background-hover !important; - color: $workspace-button-color-hover; + background: var(--red-ui-workspace-button-background-hover) !important; + color: var(--red-ui-workspace-button-color-hover); } } img.red-ui-tab-icon { @@ -111,24 +111,24 @@ } .red-ui-tabs-fade { - background-image: linear-gradient(to right, change-color($tab-background-active, $alpha: 0.001), $tab-background-active); + background-image: linear-gradient(to right, var(--red-ui-tab-background-active-alpha), var(--red-ui-tab-background-active)); } } &.selected { &:not(.active) { - background: $tab-background-selected; + background: var(--red-ui-tab-background-selected); .red-ui-tabs-fade { - background-image: linear-gradient(to right, change-color($tab-background-selected, $alpha: 0.001), $tab-background-selected); + background-image: linear-gradient(to right, var(--red-ui-tab-background-selected-alpha), var(--red-ui-tab-background-selected)); } .red-ui-tabs-badge-selected { - background: $tab-background-selected; + background: var(--red-ui-tab-background-selected); } } font-weight: bold; .red-ui-tabs-badge-selected { display: inline; - background: $tab-background; + background: var(--red-ui-tab-background); } .red-ui-tabs-badge-changed { display: none; @@ -136,10 +136,10 @@ } &:not(.active) a:hover { - color: $workspace-button-color-hover; - background: $tab-background-hover; + color: var(--red-ui-workspace-button-color-hover); + background: var(--red-ui-tab-background-hover); &+.red-ui-tabs-fade { - background-image: linear-gradient(to right, change-color($tab-background-hover, $alpha: 0.001), $tab-background-hover); + background-image: linear-gradient(to right, var(--red-ui-tab-background-hover-alpha), var(--red-ui-tab-background-hover)); } } } @@ -182,9 +182,9 @@ &.red-ui-tabs-vertical { box-sizing: border-box; height: 100%; - border-right: 1px solid $primary-border-color; + border-right: 1px solid var(--red-ui-primary-border-color); margin: 0; - background: $tertiary-background; + background: var(--red-ui-tertiary-background); overflow: visible; .red-ui-tabs-scroll-container { @@ -203,13 +203,13 @@ display: block; margin: 0; border: none; - border-right: 1px solid $primary-border-color; + border-right: 1px solid var(--red-ui-primary-border-color); height: auto; &:not(:first-child) { - border-top: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); } &:last-child { - border-bottom: 1px solid $secondary-border-color; + border-bottom: 1px solid var(--red-ui-secondary-border-color); } a.red-ui-tab-label { @@ -217,7 +217,7 @@ } &.active { - border-right: 1px solid $tab-background-active; + border-right: 1px solid var(--red-ui-tab-background-active); } } } @@ -238,8 +238,8 @@ top: 0; right: 0; height: 35px; - background: $tab-background; - border-bottom: 1px solid $primary-border-color; + background: var(--red-ui-tab-background); + border-bottom: 1px solid var(--red-ui-primary-border-color); z-index: 2; a { @@ -261,8 +261,8 @@ top: 0; right: 0; height: 35px; - background: $tab-background; - border-bottom: 1px solid $primary-border-color; + background: var(--red-ui-tab-background); + border-bottom: 1px solid var(--red-ui-primary-border-color); z-index: 2; a { @include workspace-button-toggle; @@ -272,7 +272,7 @@ margin: 4px 3px 3px; z-index: 2; &.red-ui-tab-link-button-menu { - border-color: $tab-background; + border-color: var(--red-ui-tab-background); } &:not(.single):not(.selected) { margin-top: 4px; @@ -286,27 +286,27 @@ height: 35px; width: 21px; display: block; - color: $workspace-button-color; + color: var(--red-ui-workspace-button-color); font-size: 22px; text-align: center; margin:0; border-left: none; border-right: none; border-top: none; - border-bottom: 1px solid $primary-border-color; + border-bottom: 1px solid var(--red-ui-primary-border-color); line-height: 34px; } } .red-ui-tab-scroll-left { left:0; a { - border-right: 1px solid $primary-border-color; + border-right: 1px solid var(--red-ui-primary-border-color); } } .red-ui-tab-scroll-right { right: 0px; a { - border-left: 1px solid $primary-border-color; + border-left: 1px solid var(--red-ui-primary-border-color); } } @@ -341,7 +341,7 @@ top: 0; right: 0; width: 15px; - background-image: linear-gradient(to right, change-color($tab-background-inactive, $alpha: 0.001), $tab-background-inactive); + background-image: linear-gradient(to right, var(--red-ui-tab-background-inactive-alpha), var(--red-ui-tab-background-inactive)); pointer-events: none; } @@ -365,7 +365,7 @@ i.red-ui-tab-icon { mask-position: center; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; - background-color: $tab-icon-color; + background-color: var(--red-ui-tab-icon-color); } } .red-ui-tabs-badges { @@ -379,7 +379,7 @@ i.red-ui-tab-icon { line-height: 28px; text-align: center; padding:0px; - color: $tab-badge-color; + color: var(--red-ui-tab-badge-color); } .red-ui-tabs-badges i { @@ -415,7 +415,7 @@ i.red-ui-tab-icon { } .red-ui-tab-close { display: none; - background: $tab-background-inactive; + background: var(--red-ui-tab-background-inactive); opacity: 0.8; position: absolute; right: 0px; @@ -425,10 +425,10 @@ i.red-ui-tab-icon { line-height: 28px; text-align: center; padding: 0px; - color: $workspace-button-color; + color: var(--red-ui-workspace-button-color); &:hover { - background: $workspace-button-background-hover !important; - color: $workspace-button-color-hover; + background: var(--red-ui-workspace-button-background-hover) !important; + color: var(--red-ui-workspace-button-color-hover); opacity: 1; } } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tourGuide.scss b/packages/node_modules/@node-red/editor-client/src/sass/tourGuide.scss index f8d175d44..a22c07f7f 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tourGuide.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tourGuide.scss @@ -97,7 +97,7 @@ color: var(--red-ui-primary-text-color) !important; } &:not(.primary):not(.disabled):not(.ui-button-disabled):hover { - border-color: $popover-button-border-color-hover; + border-color: var(--red-ui-popover-button-border-color-hover); } } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/checkboxSet.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/checkboxSet.scss index a606bba8c..47929378b 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/checkboxSet.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/checkboxSet.scss @@ -16,7 +16,7 @@ .red-ui-checkboxSet { width: 15px; display: inline-block; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); cursor: pointer; input { display:none !important; @@ -24,6 +24,6 @@ &.disabled { pointer-events: none; - color: $secondary-text-color-disabled; + color: var(--red-ui-secondary-text-color-disabled); } } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/editableList.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/editableList.scss index a5873f684..00b79b54a 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/editableList.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/editableList.scss @@ -14,10 +14,10 @@ * limitations under the License. **/ .red-ui-editableList-border { - border: 1px solid $form-input-border-color; + border: 1px solid var(--red-ui-form-input-border-color); border-radius: 4px; .red-ui-editableList-header { - border-bottom: 1px solid $form-input-border-color; + border-bottom: 1px solid var(--red-ui-form-input-border-color); padding: 2px 16px 2px 4px; font-size: 0.9em; } @@ -32,22 +32,22 @@ margin: 0; } .red-ui-editabelList-item-placeholder { - border: 2px dashed $secondary-border-color !important; + border: 2px dashed var(--red-ui-secondary-border-color) !important; } li { box-sizing: border-box; position: relative; - background: $secondary-background; + background: var(--red-ui-secondary-background); margin:0; padding:8px 0px; - border-bottom: 1px solid $secondary-border-color; + border-bottom: 1px solid var(--red-ui-secondary-border-color); min-height: 20px; .red-ui-editableList-item-handle { position: absolute; top: 50%; left: 2px; margin-top: -7px; - color: $tertiary-text-color; + color: var(--red-ui-tertiary-text-color); cursor: move; } .red-ui-editableList-item-remove { @@ -57,7 +57,7 @@ margin-top: -9px; } &.ui-sortable-helper { - border-top: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); } //.red-ui-editableList-item-content { outline: 1px solid red} @@ -68,7 +68,7 @@ margin-right: 28px; } &.red-ui-editableList-item-deleting { - background: $secondary-background-inactive; + background: var(--red-ui-secondary-background-inactive); } } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/nodeList.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/nodeList.scss index 01a7a4802..44745e87c 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/nodeList.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/nodeList.scss @@ -21,9 +21,9 @@ margin: 0; white-space: nowrap; border: none; - background: $secondary-background; + background: var(--red-ui-secondary-background); &:hover { - background: $secondary-background-hover; + background: var(--red-ui-secondary-background-hover); } i.fa-angle-right { @@ -44,12 +44,12 @@ } } .red-ui-editableList-item-content.disabled { - color: $secondary-text-color-disabled; + color: var(--red-ui-secondary-text-color-disabled); } &.red-ui-editableList-section-header { - background: $primary-background; + background: var(--red-ui-primary-background); .red-ui-editableList-item-content.disabled { - color: $secondary-text-color-disabled; + color: var(--red-ui-secondary-text-color-disabled); } } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/searchBox.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/searchBox.scss index b925e5212..8788ed6a7 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/searchBox.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/searchBox.scss @@ -21,7 +21,7 @@ position: absolute; top: 9px; font-size: 10px; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } i.fa-search { pointer-events: none; @@ -41,8 +41,8 @@ margin: 0; } a.red-ui-searchBox-opts:hover { - color: $workspace-button-color-hover; - background: $workspace-button-background-hover; + color: var(--red-ui-workspace-button-color-hover); + background: var(--red-ui-workspace-button-background-hover); } input.red-ui-searchBox-input { border-radius: 0; @@ -76,8 +76,8 @@ position: absolute; right: 18px; top: 4px; - background: $primary-background; - color: $secondary-text-color; + background: var(--red-ui-primary-background); + color: var(--red-ui-secondary-text-color); padding: 1px 8px; font-size: 9px; border-radius: 4px; @@ -97,12 +97,12 @@ .red-ui-searchBox-compact { input:focus.red-ui-searchBox-input { - outline: 1px solid $form-input-focus-color; + outline: 1px solid var(--red-ui-form-input-focus-color); } input.red-ui-searchBox-input,input:focus.red-ui-searchBox-input { - border: 1px solid $secondary-border-color; + border: 1px solid var(--red-ui-secondary-border-color); border-radius: 3px; font-size: 12px; height: 26px; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/stack.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/stack.scss index a32bfad70..82d697b33 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/stack.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/stack.scss @@ -15,9 +15,9 @@ **/ .red-ui-stack { - background: $secondary-background; + background: var(--red-ui-secondary-background); .red-ui-palette-category { - background: $secondary-background; + background: var(--red-ui-secondary-background); &:last-child { border-bottom: none; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/treeList.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/treeList.scss index 5f1d27037..f076a533e 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/treeList.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/treeList.scss @@ -24,9 +24,9 @@ width: 100%; height: 100%; position: relative; - background: $tertiary-background; + background: var(--red-ui-tertiary-background); - border: 1px solid $form-input-border-color; + border: 1px solid var(--red-ui-form-input-border-color); border-radius: 4px; box-sizing: border-box; @@ -71,7 +71,7 @@ padding: 6px 0; display: flex; align-items: center; - color: $list-item-color; + color: var(--red-ui-list-item-color); text-decoration: none; cursor: pointer; vertical-align: middle; @@ -79,26 +79,26 @@ position: relative; &:hover, &:hover .red-ui-treeList-sublabel-text { - background: $list-item-background-hover; - color: $list-item-color; + background: var(--red-ui-list-item-background-hover); + color: var(--red-ui-list-item-color); text-decoration: none; } &:focus, &:focus .red-ui-treeList-sublabel-text { - background: $list-item-background-hover; + background: var(--red-ui-list-item-background-hover); outline: none; - color: $list-item-color; + color: var(--red-ui-list-item-color); text-decoration: none; } &.focus, &.focus .red-ui-treeList-sublabel-text { - background: $list-item-background-hover; - outline: 1px solid $form-input-focus-color !important; + background: var(--red-ui-list-item-background-hover); + outline: 1px solid var(--red-ui-form-input-focus-color) !important; outline-offset: -1px; - color: $list-item-color; + color: var(--red-ui-list-item-color); } &.selected, &.selected .red-ui-treeList-sublabel-text { - background: $list-item-background-selected; + background: var(--red-ui-list-item-background-selected); outline: none; - color: $list-item-color; + color: var(--red-ui-list-item-color); } input.red-ui-treeList-checkbox, @@ -121,9 +121,9 @@ padding: 0 10px 0 5px; line-height: 32px; font-size: 0.9em; - color: $list-item-secondary-color; + color: var(--red-ui-list-item-secondary-color); position: absolute; - background: $list-item-background; + background: var(--red-ui-list-item-background); } @@ -143,5 +143,5 @@ mask-position: 50% 50%; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; - background-color: $spinner-color; + background-color: var(--red-ui-spinner-color); } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss index 76345e980..1a421fac5 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss @@ -15,7 +15,7 @@ **/ .red-ui-typedInput-container { - border: 1px solid $form-input-border-color; + border: 1px solid var(--red-ui-form-input-border-color); border-radius: 5px; height: 34px; line-height: 14px; @@ -28,7 +28,7 @@ position: relative; &[disabled] { input, button { - background: $secondary-background-inactive; + background: var(--red-ui-secondary-background-inactive); pointer-events: none; cursor: not-allowed; } @@ -50,7 +50,7 @@ } &.red-ui-typedInput-focus:not(.input-error) { - border-color: $form-input-focus-color !important; + border-color: var(--red-ui-form-input-focus-color) !important; } .red-ui-typedInput-value-label { flex-grow: 1; @@ -61,42 +61,42 @@ overflow: hidden; text-overflow: ellipsis; .red-ui-typedInput-value-label-inactive { - background: $secondary-background-disabled; - color: $secondary-text-color-disabled; + background: var(--red-ui-secondary-background-disabled); + color: var(--red-ui-secondary-text-color-disabled); } } } .red-ui-typedInput-options { @include component-shadow; - font-family: $primary-font; - font-size: $primary-font-size; + font-family: var(--red-ui-primary-font); + font-size: var(--red-ui-primary-font-size); position: absolute; max-height: 350px; overflow-y: auto; - border: 1px solid $primary-border-color; + border: 1px solid var(--red-ui-primary-border-color); box-sizing: border-box; - background: $secondary-background; + background: var(--red-ui-secondary-background); white-space: nowrap; z-index: 2000; a { padding: 6px 18px 6px 6px; display: flex; align-items: center; - border-bottom: 1px solid $secondary-border-color; - color: $form-text-color; + border-bottom: 1px solid var(--red-ui-secondary-border-color); + color: var(--red-ui-form-text-color); &:hover { text-decoration: none; - background: $workspace-button-background-hover; + background: var(--red-ui-workspace-button-background-hover); } &:focus { text-decoration: none; - background: $workspace-button-background-active; + background: var(--red-ui-workspace-button-background-active); outline: none; } &:active { text-decoration: none; - background: $workspace-button-background-active; + background: var(--red-ui-workspace-button-background-active); } input[type="checkbox"] { margin: 0 6px 0 0; @@ -111,7 +111,7 @@ mask-position: center; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; - background-color: $primary-text-color; + background-color: var(--red-ui-primary-text-color); height: 14px; width: 12px; } @@ -128,11 +128,11 @@ button.red-ui-typedInput-option-trigger border-top-left-radius: 4px; border-bottom-left-radius: 4px; padding: 0 1px 0 5px; - background: $form-button-background; + background: var(--red-ui-form-button-background); height: 32px; line-height: 30px; vertical-align: middle; - color: $form-text-color; + color: var(--red-ui-form-text-color); white-space: nowrap; i.red-ui-typedInput-icon { margin-left: 1px; @@ -142,7 +142,7 @@ button.red-ui-typedInput-option-trigger &.disabled { cursor: default; > i.red-ui-typedInput-icon { - color: $secondary-text-color-disabled; + color: var(--red-ui-secondary-text-color-disabled); } } .red-ui-typedInput-type-label,.red-ui-typedInput-option-label { @@ -161,21 +161,21 @@ button.red-ui-typedInput-option-trigger mask-position: center; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; - background-color: $primary-text-color; + background-color: var(--red-ui-primary-text-color); } } &:not(.disabled):hover { text-decoration: none; - background: $workspace-button-background-hover; + background: var(--red-ui-workspace-button-background-hover); } &:focus { text-decoration: none; outline: none; - box-shadow: inset 0 0 0 1px $form-input-focus-color; + box-shadow: inset 0 0 0 1px var(--red-ui-form-input-focus-color); } &:not(.disabled):active { - background: $workspace-button-background-active; + background: var(--red-ui-workspace-button-background-active); text-decoration: none; } &.red-ui-typedInput-full-width { @@ -208,8 +208,8 @@ button.red-ui-typedInput-option-trigger { line-height: 32px; display: inline-flex; .red-ui-typedInput-option-label { - background:$form-button-background; - color: $form-text-color; + background:var(--red-ui-form-button-background); + color: var(--red-ui-form-text-color); flex-grow: 1; padding: 0 0 0 8px; display:inline-block; @@ -231,6 +231,6 @@ button.red-ui-typedInput-option-trigger { box-shadow: none; } &:focus .red-ui-typedInput-option-caret { - box-shadow: inset 0 0 0 1px $form-input-focus-color; + box-shadow: inset 0 0 0 1px var(--red-ui-form-input-focus-color); } } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/userSettings.scss b/packages/node_modules/@node-red/editor-client/src/sass/userSettings.scss index 5cf0570c2..36ab67e3f 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/userSettings.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/userSettings.scss @@ -20,7 +20,7 @@ left: 0; bottom: 0; width: 120px; - background: $tertiary-background; + background: var(--red-ui-tertiary-background); } .red-ui-settings-tabs-content { position: absolute; @@ -30,7 +30,7 @@ bottom: 0; padding: 0; h3:not(:first-child) { - border-top: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); margin-top: 15px; margin-bottom: 10px; padding-top: 20px; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/variables.scss b/packages/node_modules/@node-red/editor-client/src/sass/variables.scss index aadc2231e..abd31629c 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/variables.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/variables.scss @@ -1,4 +1,5 @@ :root { + --red-ui-primary-font: #{$primary-font}; --red-ui-primary-font-size: #{$primary-font-size}; --red-ui-monospace-font: #{$monospace-font}; @@ -40,7 +41,6 @@ --red-ui-text-color-link: #{$text-color-link}; - --red-ui-primary-border-color: #{$primary-border-color}; --red-ui-secondary-border-color: #{$secondary-border-color}; --red-ui-tertiary-border-color: #{$tertiary-border-color}; @@ -50,20 +50,38 @@ --red-ui-border-color-success: #{$border-color-success}; --red-ui-form-background: #{$form-background}; - --red-ui-form-placeholder-color: #{$form-placeholder-color}; --red-ui-form-text-color: #{$form-text-color}; --red-ui-form-text-color-disabled: #{$form-text-color-disabled}; + --red-ui-form-input-focus-color: #{$form-input-focus-color}; --red-ui-form-input-border-color: #{$form-input-border-color}; - --red-ui-form-input-border-color-focus: #{$form-input-focus-color}; - --red-ui-form-input-border-color-selected: #{$form-input-border-selected-color}; - --red-ui-form-input-border-color-error: #{$form-input-border-error-color}; + --red-ui-form-input-border-selected-color: #{$form-input-border-selected-color}; + --red-ui-form-input-border-error-color: #{$form-input-border-error-color}; --red-ui-form-input-background: #{$form-input-background}; --red-ui-form-input-background-disabled: #{$form-input-background-disabled}; --red-ui-form-button-background: #{$form-button-background}; --red-ui-form-tips-background: #{$form-tips-background}; + + --red-ui-text-editor-color: #{$text-editor-color}; + --red-ui-text-editor-background: #{$text-editor-background}; + --red-ui-text-editor-color-disabled: #{$text-editor-color-disabled}; + --red-ui-text-editor-background-disabled: #{$text-editor-background-disabled}; + --red-ui-text-editor-gutter-background: #{$text-editor-gutter-background}; + --red-ui-text-editor-gutter-color: #{$text-editor-gutter-color}; + --red-ui-text-editor-gutter-active-line-background: #{$text-editor-gutter-active-line-background}; + --red-ui-text-editor-active-line-background: #{$text-editor-active-line-background}; + --red-ui-text-editor-selection-background: #{$text-editor-selection-background}; + + --red-ui-event-log-background: #{$event-log-background}; + --red-ui-event-log-color: #{$event-log-color}; + --red-ui-event-log-active-line-background: #{$event-log-active-line-background}; + --red-ui-event-log-selection-background: #{$event-log-selection-background}; + + + + --red-ui-list-item-color: #{$list-item-color}; --red-ui-list-item-secondary-color: #{$list-item-secondary-color}; --red-ui-list-item-background: #{$list-item-background}; @@ -72,8 +90,120 @@ --red-ui-list-item-background-selected: #{$list-item-background-selected}; --red-ui-list-item-border-selected: #{$list-item-border-selected}; + --red-ui-tab-text-color-active: #{$tab-text-color-active}; + --red-ui-tab-text-color-inactive: #{$tab-text-color-inactive}; + --red-ui-tab-text-color-disabled-active: #{$tab-text-color-disabled-active}; + --red-ui-tab-text-color-disabled-inactive: #{$tab-text-color-disabled-inactive}; + + --red-ui-tab-badge-color: #{$tab-badge-color}; + --red-ui-tab-background: #{$tab-background}; + --red-ui-tab-background-active: #{$tab-background-active}; + --red-ui-tab-background-active-alpha: #{$tab-background-active-alpha}; + --red-ui-tab-background-selected: #{$tab-background-selected}; + --red-ui-tab-background-selected-alpha: #{$tab-background-selected-alpha}; + --red-ui-tab-background-inactive: #{$tab-background-inactive}; + --red-ui-tab-background-inactive-alpha: #{$tab-background-inactive-alpha}; + --red-ui-tab-background-hover: #{$tab-background-hover}; + --red-ui-tab-background-hover-alpha: #{$tab-background-hover-alpha}; + + --red-ui-palette-header-background: #{$palette-header-background}; + --red-ui-palette-header-color: #{$palette-header-color}; + --red-ui-palette-content-background: #{$palette-content-background}; + + + --red-ui-workspace-button-background: #{$workspace-button-background}; + --red-ui-workspace-button-background-hover: #{$workspace-button-background-hover}; + --red-ui-workspace-button-background-active: #{$workspace-button-background-active}; + + --red-ui-workspace-button-color: #{$workspace-button-color}; + --red-ui-workspace-button-color-disabled: #{$workspace-button-color-disabled}; + --red-ui-workspace-button-color-focus: #{$workspace-button-color-focus}; + --red-ui-workspace-button-color-hover: #{$workspace-button-color-hover}; + --red-ui-workspace-button-color-active: #{$workspace-button-color-active}; + --red-ui-workspace-button-color-selected: #{$workspace-button-color-selected}; + + --red-ui-workspace-button-color-primary: #{$workspace-button-color-primary}; + --red-ui-workspace-button-background-primary: #{$workspace-button-background-primary}; + --red-ui-workspace-button-background-primary-hover: #{$workspace-button-background-primary-hover}; + + --red-ui-workspace-button-color-focus-outline: #{$workspace-button-color-focus-outline}; + --red-ui-shade-color: #{$shade-color}; + --red-ui-popover-background: #{$popover-background}; + --red-ui-popover-border: #{$popover-border}; + --red-ui-popover-color: #{$popover-color}; + --red-ui-popover-button-border-color: #{$popover-button-border-color}; + --red-ui-popover-button-border-color-hover: #{$popover-button-border-color-hover}; + + + + --red-ui-diff-text-header-color: #{$diff-text-header-color}; + --red-ui-diff-text-header-background: #{$diff-text-header-background}; + --red-ui-diff-state-color: #{$diff-state-color}; + --red-ui-diff-state-prefix-color: #{$diff-state-prefix-color}; + --red-ui-diff-state-added: #{$diff-state-added}; + --red-ui-diff-state-deleted: #{$diff-state-deleted}; + --red-ui-diff-state-changed: #{$diff-state-changed}; + --red-ui-diff-state-changed-background: #{$diff-state-changed-background}; + --red-ui-diff-state-unchanged: #{$diff-state-unchanged}; + --red-ui-diff-state-unchanged-background: #{$diff-state-unchanged-background}; + + --red-ui-diff-state-conflicted: #{$diff-state-conflicted}; + --red-ui-diff-state-moved: #{$diff-state-moved}; + --red-ui-diff-state-conflict: #{$diff-state-conflict}; + --red-ui-diff-state-conflict-background: #{$diff-state-conflict-background}; + + --red-ui-diff-state-added-background: #{$diff-state-added-background}; + --red-ui-diff-state-added-border: #{$diff-state-added-border}; + --red-ui-diff-state-added-header-background: #{$diff-state-added-header-background}; + --red-ui-diff-state-added-header-border: #{$diff-state-added-header-border}; + + --red-ui-diff-state-deleted-background: #{$diff-state-deleted-background}; + --red-ui-diff-state-deleted-border: #{$diff-state-deleted-border}; + --red-ui-diff-state-deleted-header-background: #{$diff-state-deleted-header-background}; + --red-ui-diff-state-deleted-header-border: #{$diff-state-deleted-header-border}; + + --red-ui-diff-merge-header-color: #{$diff-merge-header-color}; + --red-ui-diff-merge-header-background: #{$diff-merge-header-background}; + --red-ui-diff-merge-header-border-color: #{$diff-merge-header-border-color}; + + --red-ui-menuBackground: #{$menuBackground}; + --red-ui-menuDivider: #{$menuDivider}; + --red-ui-menuColor: #{$menuColor}; + --red-ui-menuActiveColor: #{$menuActiveColor}; + --red-ui-menuActiveBackground: #{$menuActiveBackground}; + --red-ui-menuDisabledColor: #{$menuDisabledColor}; + --red-ui-menuHoverColor: #{$menuHoverColor}; + --red-ui-menuHoverBackground: #{$menuHoverBackground}; + --red-ui-menuCaret: #{$menuCaret}; + + --red-ui-view-navigator-background: #{$view-navigator-background}; + + --red-ui-view-lasso-stroke: #{$view-lasso-stroke}; + --red-ui-view-lasso-fill: #{$view-lasso-fill}; + + --red-ui-view-background: #{$view-background}; + --red-ui-view-grid-color: #{$view-grid-color}; + + --red-ui-node-label-color: #{$node-label-color}; + --red-ui-node-port-label-color: #{$node-port-label-color}; + --red-ui-node-border: #{$node-border}; + --red-ui-node-border-unknown: #{$node-border-unknown}; + --red-ui-node-border-placeholder: #{$node-border-placeholder}; + --red-ui-node-background-placeholder: #{$node-background-placeholder}; + + --red-ui-node-port-background: #{$node-port-background}; + --red-ui-node-port-background-hover: #{$node-port-background-hover}; + --red-ui-node-icon-color: #{$node-icon-color}; + --red-ui-node-icon-background-color: #{$node-icon-background-color}; + --red-ui-node-icon-background-color-fill: #{$node-icon-background-color-fill}; + --red-ui-node-icon-background-color-opacity: #{$node-icon-background-color-opacity}; + --red-ui-node-icon-border-color: #{$node-icon-border-color}; + --red-ui-node-icon-border-color-opacity: #{$node-icon-border-color-opacity}; + + --red-ui-node-config-background: #{$node-config-background}; + --red-ui-node-link-port-background: #{$node-link-port-background}; --red-ui-node-status-error-border: #{$node-status-error-border}; @@ -81,18 +211,87 @@ --red-ui-node-status-changed-border: #{$node-status-changed-border}; --red-ui-node-status-changed-background: #{$node-status-changed-background}; - --red-ui-node-border: #{$node-border}; - --red-ui-node-port-background:#{$node-port-background}; + @each $current-color in red green yellow blue grey gray { + --red-ui-node-status-colors-#{"" + $current-color}: #{map-get($node-status-colors, $current-color)}; + } + - --red-ui-node-label-color: #{$node-label-color}; --red-ui-node-selected-color: #{$node-selected-color}; --red-ui-port-selected-color: #{$port-selected-color}; - --red-ui-popover-background: #{$popover-background}; - --red-ui-popover-border: #{$popover-border}; - --red-ui-popover-color: #{$popover-color}; + --red-ui-link-color: #{$link-color}; + --red-ui-link-link-color: #{$link-link-color}; + --red-ui-link-disabled-color: #{$link-disabled-color}; + --red-ui-link-link-active-color: #{$link-link-active-color}; + --red-ui-link-unknown-color: #{$link-unknown-color}; + + --red-ui-clipboard-textarea-background: #{$clipboard-textarea-background}; + + + --red-ui-deploy-button-color: #{$deploy-button-color}; + --red-ui-deploy-button-color-active: #{$deploy-button-color-active}; + --red-ui-deploy-button-color-disabled: #{$deploy-button-color-disabled}; + --red-ui-deploy-button-background: #{$deploy-button-background}; + --red-ui-deploy-button-background-hover: #{$deploy-button-background-hover}; + --red-ui-deploy-button-background-active: #{$deploy-button-background-active}; + --red-ui-deploy-button-background-disabled: #{$deploy-button-background-disabled}; + --red-ui-deploy-button-background-disabled-hover: #{$deploy-button-background-disabled-hover}; + + + --red-ui-header-background: #{$header-background}; + --red-ui-header-button-background-active: #{$header-button-background-active}; + --red-ui-header-menu-color: #{$header-menu-color}; + --red-ui-header-menu-color-disabled: #{$header-menu-color-disabled}; + --red-ui-header-menu-heading-color: #{$header-menu-heading-color}; + --red-ui-header-menu-sublabel-color: #{$header-menu-sublabel-color}; + --red-ui-header-menu-background: #{$header-menu-background}; + --red-ui-header-menu-item-hover: #{$header-menu-item-hover}; + --red-ui-header-menu-item-border-active: #{$header-menu-item-border-active}; + --red-ui-headerMenuItemDivider: #{$headerMenuItemDivider}; + --red-ui-headerMenuCaret: #{$headerMenuCaret}; + + --red-ui-vcCommitShaColor: #{$vcCommitShaColor}; + + --red-ui-dnd-background: #{$dnd-background}; + --red-ui-dnd-color: #{$dnd-color}; + + --red-ui-notification-border-default: #{$notification-border-default}; + --red-ui-notification-border-success: #{$notification-border-success}; + --red-ui-notification-border-warning: #{$notification-border-warning}; + --red-ui-notification-border-error: #{$notification-border-error}; + + --red-ui-debug-message-background: #{$debug-message-background}; + --red-ui-debug-message-background-hover: #{$debug-message-background-hover}; + + --red-ui-debug-message-text-color: #{$debug-message-text-color}; + --red-ui-debug-message-text-color-meta: #{$debug-message-text-color-meta}; + --red-ui-debug-message-text-color-object-key: #{$debug-message-text-color-object-key}; + --red-ui-debug-message-text-color-msg-type-other: #{$debug-message-text-color-msg-type-other}; + --red-ui-debug-message-text-color-msg-type-string: #{$debug-message-text-color-msg-type-string}; + --red-ui-debug-message-text-color-msg-type-null: #{$debug-message-text-color-msg-type-null}; + --red-ui-debug-message-text-color-msg-type-meta: #{$debug-message-text-color-msg-type-meta}; + --red-ui-debug-message-text-color-msg-type-number: #{$debug-message-text-color-msg-type-number}; + + --red-ui-debug-message-border: #{$debug-message-border}; + --red-ui-debug-message-border-hover: #{$debug-message-border-hover}; + --red-ui-debug-message-border-warning: #{$debug-message-border-warning}; + --red-ui-debug-message-border-error: #{$debug-message-border-error}; + + --red-ui-group-default-fill: #{$group-default-fill}; + --red-ui-group-default-fill-opacity: #{$group-default-fill-opacity}; + --red-ui-group-default-stroke: #{$group-default-stroke}; + --red-ui-group-default-stroke-opacity: #{$group-default-stroke-opacity}; + --red-ui-group-default-label-color: #{$group-default-label-color}; --red-ui-tourGuide-border: #{$tourGuide-border}; --red-ui-tourGuide-heading-color: #{$tourGuide-heading-color}; + --red-ui-grip-color: #{$grip-color}; + + --red-ui-icons-flow-color: #{$icons-flow-color}; + + --red-ui-spinner-color: #{$spinner-color}; + + --red-ui-tab-icon-color: #{$tab-icon-color}; + } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss b/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss index d594337de..24e156b1e 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss @@ -47,12 +47,12 @@ } .red-ui-workspace-chart-background { - fill: $view-background; + fill: var(--red-ui-view-background); } .red-ui-workspace-chart-grid line { fill: none; shape-rendering: crispEdges; - stroke: $view-grid-color; + stroke: var(--red-ui-view-grid-color); stroke-width: 1px; } .red-ui-workspace-select-mode { @@ -94,11 +94,11 @@ a { font-style: italic; - color: $tab-text-color-disabled-inactive !important; + color: var(--red-ui-tab-text-color-disabled-inactive) !important; } &.active a { font-weight: normal; - color: $tab-text-color-disabled-active !important; + color: var(--red-ui-tab-text-color-disabled-active) !important; } .red-ui-workspace-disabled-icon { display: inline; @@ -112,17 +112,17 @@ bottom: 0; right:0; z-index: 101; - border-left: 1px solid $primary-border-color; - border-top: 1px solid $primary-border-color; - background: $view-navigator-background; - box-shadow: -1px 0 3px $shadow; + border-left: 1px solid var(--red-ui-primary-border-color); + border-top: 1px solid var(--red-ui-primary-border-color); + background: var(--red-ui-view-navigator-background); + box-shadow: -1px 0 3px var(--red-ui-shadow); } .red-ui-navigator-border { stroke-dasharray: 5,5; pointer-events: none; - stroke: $secondary-border-color; + stroke: var(--red-ui-secondary-border-color); stroke-width: 1; - fill: $view-background; + fill: var(--red-ui-view-background); } .red-ui-component-footer { @@ -182,7 +182,7 @@ button.red-ui-footer-button-toggle { #red-ui-loading-progress { position: absolute; - background: $primary-background; + background: var(--red-ui-primary-background); top: 0; bottom: 0; right: 0; @@ -196,7 +196,7 @@ button.red-ui-footer-button-toggle { width: 300px; height:80px; text-align: center; - color: $secondary-text-color; + color: var(--red-ui-secondary-text-color); } } @@ -204,13 +204,13 @@ button.red-ui-footer-button-toggle { box-sizing: border-box; width: 300px; height: 30px; - border: 2px solid $primary-border-color; + border: 2px solid var(--red-ui-primary-border-color); border-radius: 4px; > span { display: block; height: 100%; - background: $secondary-border-color; + background: var(--red-ui-secondary-border-color); transition: width 0.2s; width: 10%; } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/workspaceToolbar.scss b/packages/node_modules/@node-red/editor-client/src/sass/workspaceToolbar.scss index 2a734eb43..d0b0370ab 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/workspaceToolbar.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/workspaceToolbar.scss @@ -17,7 +17,7 @@ #red-ui-workspace-toolbar { display: none; - color: $workspace-button-color; + color: var(--red-ui-workspace-button-color); font-size: 12px; line-height: 18px; position: absolute; @@ -27,8 +27,8 @@ padding: 7px; height: 40px; box-sizing: border-box; - background: $secondary-background; - border-bottom: 1px solid $secondary-border-color; + background: var(--red-ui-secondary-background); + border-bottom: 1px solid var(--red-ui-secondary-border-color); white-space: nowrap; transition: right 0.2s ease; overflow: hidden; @@ -61,23 +61,23 @@ } } .button.active { - background: $workspace-button-background-active; + background: var(--red-ui-workspace-button-background-active); cursor: default; } } .spinner-value { width: 25px; - color: $workspace-button-color; + color: var(--red-ui-workspace-button-color); padding: 0 5px; display: inline-block; text-align: center; - border-top: 1px solid $secondary-border-color; - border-bottom: 1px solid $secondary-border-color; + border-top: 1px solid var(--red-ui-secondary-border-color); + border-bottom: 1px solid var(--red-ui-secondary-border-color); margin: 0; height: 24px; font-size: 12px; - background: $form-input-background; + background: var(--red-ui-form-input-background); line-height: 22px; box-sizing: border-box; } diff --git a/scripts/build-custom-theme.js b/scripts/build-custom-theme.js index e16b35e8b..3a904fea8 100644 --- a/scripts/build-custom-theme.js +++ b/scripts/build-custom-theme.js @@ -83,7 +83,7 @@ while((match = ruleRegex.exec(colorsFile)) !== null) { const result = sass.renderSync({ outputStyle: "expanded", - file: path.join(workingDir,"style.scss"), + file: path.join(workingDir,"style-custom-theme.scss"), }); const css = result.css.toString() From ad0a08ea0e7dde0684e97545a0fd52c33c7a6316 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Mon, 20 Jun 2022 00:44:20 +0900 Subject: [PATCH 021/237] Add Japanese translations in action list for project feature --- .../@node-red/editor-client/locales/ja/editor.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index ac09a1e87..2849070bf 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -1331,6 +1331,10 @@ "zoom-reset": "ズームリセット", "toggle-navigator": "ナビゲータ表示切替", "show-system-info": "システム情報", - "split-wires-with-junctions": "分岐点によりワイヤーを分割" + "split-wires-with-junctions": "分岐点によりワイヤーを分割", + "new-project": "新しいプロジェクト", + "open-project": "プロジェクトを開く", + "show-project-settings": "プロジェクト設定を表示", + "show-version-control-tab": "バージョンコントロールタブを表示" } } From 3fbbfce17cd9ef011b3dcd37a9376f08821963a9 Mon Sep 17 00:00:00 2001 From: Dennis Neufeld Date: Mon, 20 Jun 2022 13:28:10 +0200 Subject: [PATCH 022/237] Fix typo in german translation --- .../node_modules/@node-red/editor-client/locales/de/editor.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/de/editor.json b/packages/node_modules/@node-red/editor-client/locales/de/editor.json index dd931a812..ba3007f51 100755 --- a/packages/node_modules/@node-red/editor-client/locales/de/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/de/editor.json @@ -1106,7 +1106,7 @@ "no-resource": "Repository nicht gefunden", "cant-get-ssh-key-path": "Fehler! Der ausgewählte SSH-Schlüsselpfad kann nicht abgerufen werden.", "unexpected_error": "unerwarteter_Fehler", - "clearContext": "Kontextdaten löscshen beim Projektwechsel" + "clearContext": "Kontextdaten löschen beim Projektwechsel" }, "delete": { "confirm": "Sind Sie sicher, dass dieses Projekt gelöscht werden soll?" From 9729c89f5dc30181765aef276a6daf151f687482 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Mon, 20 Jun 2022 18:25:41 +0100 Subject: [PATCH 023/237] ensure link-call cache is updated when link-in is modified fixes #3694 depends on node-red-node-test-helper@0.3.0 --- package.json | 2 +- .../@node-red/nodes/core/common/60-link.js | 17 +++----- test/nodes/core/common/60-link_spec.js | 43 +++++++++++++++++++ 3 files changed, 51 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 1343af50c..715d09a5b 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "marked": "4.0.17", "minami": "1.2.3", "mocha": "9.2.2", - "node-red-node-test-helper": "^0.2.7", + "node-red-node-test-helper": "^0.3.0", "nodemon": "2.0.16", "proxy": "^1.0.2", "sass": "1.52.3", diff --git a/packages/node_modules/@node-red/nodes/core/common/60-link.js b/packages/node_modules/@node-red/nodes/core/common/60-link.js index 24572f8b1..08bad7a62 100644 --- a/packages/node_modules/@node-red/nodes/core/common/60-link.js +++ b/packages/node_modules/@node-red/nodes/core/common/60-link.js @@ -109,16 +109,13 @@ module.exports = function(RED) { }, remove(node) { const target = generateTarget(node); - const tn = this.getTarget(target.name, target.flowId); - if (tn) { - const targs = this.getTargets(tn.name); - const idx = getIndex(targs, tn.id); - if (idx > -1) { - targs.splice(idx, 1); - } - if (targs.length === 0) { - delete registry.name[tn.name]; - } + const targs = this.getTargets(target.name); + const idx = getIndex(targs, target.id); + if (idx > -1) { + targs.splice(idx, 1); + } + if (targs.length === 0) { + delete registry.name[tn.name]; } delete registry.id[target.id]; }, diff --git a/test/nodes/core/common/60-link_spec.js b/test/nodes/core/common/60-link_spec.js index 63733455c..be7ffc39f 100644 --- a/test/nodes/core/common/60-link_spec.js +++ b/test/nodes/core/common/60-link_spec.js @@ -17,6 +17,7 @@ var should = require("should"); var linkNode = require("nr-test-utils").require("@node-red/nodes/core/common/60-link.js"); var helper = require("node-red-node-test-helper"); +var clone = require("clone"); describe('link Node', function() { @@ -319,6 +320,48 @@ describe('link Node', function() { linkCall.receive({ payload: "hello", target: "double payload" }); }); }) + it('should not raise error after deploying a name change to a duplicate link-in node', async function () { + this.timeout(400); + const flow = [ + { id: "tab-flow-1", type: "tab", label: "Flow 1" }, + { id: "link-in-1", z: "tab-flow-1", type: "link in", name: "duplicate", wires: [["link-out-1"]] }, + { id: "link-in-2", z: "tab-flow-1", type: "link in", name: "duplicate", wires: [["link-out-1"]] }, //duplicate name + { id: "link-out-1", z: "tab-flow-1", type: "link out", mode: "return" }, + { id: "link-call", z: "tab-flow-1", type: "link call", linkType: "dynamic", links: [], wires: [["n4"]] }, + { id: "n4", z: "tab-flow-1", type: "helper" } + ]; + + await helper.load(linkNode, flow) + + const linkIn2before = helper.getNode("link-in-2"); + linkIn2before.should.have.property("name", "duplicate") // check link-in-2 has been deployed with the duplicate name + + //modify the flow and deploy change + const newConfig = clone(flow); + newConfig[2].name = "add" // change nodes name + await helper.setFlows(newConfig, "nodes") // deploy "nodes" only + + const helperNode = helper.getNode("n4"); + const linkCall2 = helper.getNode("link-call"); + const linkIn2after = helper.getNode("link-in-2"); + linkIn2after.should.have.property("name", "add") // check link-in-2 no longer has a duplicate name + + //poke { payload: "hello", target: "add" } into the link-call node and + //ensure that a message arrives via the link-in node named "add" + await new Promise((resolve, reject) => { + helperNode.on("input", function (msg) { + try { + msg.should.have.property("target", "add"); + msg.should.not.have.property("error"); + resolve() + } catch (err) { + reject(err); + } + }); + linkCall2.receive({ payload: "hello", target: "add" }); + }); + + }) it('should allow nested link-call flows', function(done) { this.timeout(500); var flow = [/** Multiply by 2 link flow **/ From 202102ebf7374e6697d9c807457f9bc0c31099cd Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 20 Jun 2022 20:51:31 +0100 Subject: [PATCH 024/237] Fix clicking on node in workspace to hide context menu --- .../editor-client/src/js/ui/contextMenu.js | 3 ++- .../@node-red/editor-client/src/js/ui/view.js | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js index 9920789f5..0b388aff3 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js @@ -170,6 +170,7 @@ RED.contextMenu = (function() { } return { - show: show + show: show, + hide: disposeMenu } })() diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 522b86cbc..b55244d0f 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -988,6 +988,7 @@ RED.view = (function() { if (RED.view.DEBUG) { console.warn("canvasMouseDown", { mouse_mode, point: d3.mouse(this), event: d3.event }); } + RED.contextMenu.hide(); if (mouse_mode === RED.state.SELECTING_NODE) { d3.event.stopPropagation(); return; @@ -1779,6 +1780,9 @@ RED.view = (function() { } var i; var historyEvent; + if (d3.event.button === 2) { + return + } if (mouse_mode === RED.state.PANNING) { resetMouseVars(); return @@ -2903,6 +2907,7 @@ RED.view = (function() { function portMouseDown(d,portType,portIndex, evt) { if (RED.view.DEBUG) { console.warn("portMouseDown", mouse_mode,d,portType,portIndex); } + RED.contextMenu.hide(); evt = evt || d3.event; if (evt === 1) { return; @@ -3411,6 +3416,7 @@ RED.view = (function() { function nodeMouseDown(d) { if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); } focusView(); + RED.contextMenu.hide(); if (d3.event.button === 1) { return; } @@ -3793,6 +3799,7 @@ RED.view = (function() { if (RED.view.DEBUG) { console.warn("linkMouseDown", { mouse_mode, point: d3.mouse(this), event: d3.event }); } + RED.contextMenu.hide(); if (mouse_mode === RED.state.SELECTING_NODE) { d3.event.stopPropagation(); return; @@ -3852,6 +3859,9 @@ RED.view = (function() { } function groupMouseUp(g) { + if (RED.view.DEBUG) { + console.warn("groupMouseUp", { mouse_mode, event: d3.event }); + } if (dblClickPrimed && mousedown_group == g && clickElapsed > 0 && clickElapsed < dblClickInterval) { mouse_mode = RED.state.DEFAULT; RED.editor.editGroup(g); @@ -3867,6 +3877,10 @@ RED.view = (function() { // return // } + if (RED.view.DEBUG) { + console.warn("groupMouseDown", { mouse_mode, point: mouse, event: d3.event }); + } + RED.contextMenu.hide(); focusView(); if (d3.event.button === 1) { return; From 10835968fb3f714d971ea78bc48be41378bc7f9f Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Tue, 21 Jun 2022 21:47:57 +0900 Subject: [PATCH 025/237] add access to previous tours --- .../editor-client/src/js/ui/tab-help.js | 23 +- .../editor-client/src/js/ui/tour/tourGuide.js | 26 ++ .../editor-client/src/tours/2.1/welcome.js | 229 ++++++++++++++++++ .../src/tours/2.2/images/delete-repair.gif | Bin 0 -> 29584 bytes .../src/tours/2.2/images/detach-repair.gif | Bin 0 -> 71182 bytes .../src/tours/2.2/images/slice.gif | Bin 0 -> 83214 bytes .../src/tours/2.2/images/subflow-labels.png | Bin 0 -> 30101 bytes .../editor-client/src/tours/2.2/welcome.js | 156 ++++++++++++ 8 files changed, 430 insertions(+), 4 deletions(-) create mode 100644 packages/node_modules/@node-red/editor-client/src/tours/2.1/welcome.js create mode 100644 packages/node_modules/@node-red/editor-client/src/tours/2.2/images/delete-repair.gif create mode 100644 packages/node_modules/@node-red/editor-client/src/tours/2.2/images/detach-repair.gif create mode 100644 packages/node_modules/@node-red/editor-client/src/tours/2.2/images/slice.gif create mode 100644 packages/node_modules/@node-red/editor-client/src/tours/2.2/images/subflow-labels.png create mode 100644 packages/node_modules/@node-red/editor-client/src/tours/2.2/welcome.js diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js index 154b64364..c6b33dbc7 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js @@ -97,7 +97,10 @@ RED.sidebar.help = (function() { var pendingContentLoad; treeList.on('treelistselect', function(e,item) { pendingContentLoad = item; - if (item.nodeType) { + if (item.tour) { + RED.tourGuide.run(item.tour); + } + else if (item.nodeType) { showNodeTypeHelp(item.nodeType); } else if (item.content) { helpSection.empty(); @@ -198,15 +201,27 @@ RED.sidebar.help = (function() { label: RED._("sidebar.help.nodeHelp"), children: [], expanded: true - } + }; + var tours = RED.tourGuide.list().map(function (item) { + return { + icon: "fa fa-repeat", + label: item.label, + tour: item.path, + }; + }); var helpData = [ { id: 'changelog', label: "Node-RED v"+RED.settings.version, content: getChangelog }, - nodeHelp - ] + nodeHelp, + { + id: "tours", + label: "Tours", + children: tours + }, + ]; var subflows = RED.nodes.registry.getNodeTypes().filter(function(t) {return /subflow/.test(t)}); if (subflows.length > 0) { nodeHelp.children.push({ diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js index 31612cfeb..49b960402 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js @@ -433,9 +433,35 @@ RED.tourGuide = (function() { }) } + function listTour() { + return [ + { + id: "2_3", + label: "3.0.0-beta.3 (latest)", + path: "./tours/welcome.js" + }, + { + id: "2_2", + label: "2.2.0", + path: "./tours/2.2/welcome.js" + }, + { + id: "2_1", + label: "2.1.0", + path: "./tours/2.1/welcome.js" + }, + { + id: "first_flow", + label: "First flow", + path: "./tours/first-flow.js" + }, + ]; + } + return { load: loadTour, run: run, + list: listTour, reset: function() { RED.settings.set("editor.tours.welcome",''); } diff --git a/packages/node_modules/@node-red/editor-client/src/tours/2.1/welcome.js b/packages/node_modules/@node-red/editor-client/src/tours/2.1/welcome.js new file mode 100644 index 000000000..8a4565bab --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/tours/2.1/welcome.js @@ -0,0 +1,229 @@ +export default { + version: "2.1.0", + steps: [ + { + titleIcon: "fa fa-map-o", + title: { + "en-US": "Welcome to Node-RED 2.1!", + "ja": "Node-RED 2.1へようこそ!" + }, + description: { + "en-US": "Let's take a moment to discover the new features in this release.", + "ja": "本リリースの新機能を見つけてみましょう。" + } + }, + { + title: { + "en-US": "A new Tour Guide", + "ja": "新しいツアーガイド" + }, + description: { + "en-US": "

    First, as you've already found, we now have this tour of new features. We'll only show the tour the first time you open the editor for each new version of Node-RED.

    " + + "

    You can choose not to see this tour in the future by disabling it under the View tab of User Settings.

    ", + "ja": "

    最初に、既に見つけている様に、新機能の本ツアーがあります。本ツアーは、新バージョンのNode-REDフローエディタを初めて開いた時のみ表示されます。

    " + + "

    ユーザ設定の表示タブの中で、この機能を無効化することで、本ツアーを表示しないようにすることもできます。

    " + } + }, + { + title: { + "en-US": "New Edit menu", + "ja": "新しい編集メニュー" + }, + prepare() { + $("#red-ui-header-button-sidemenu").trigger("click"); + $("#menu-item-edit-menu").parent().addClass("open"); + }, + complete() { + $("#menu-item-edit-menu").parent().removeClass("open"); + }, + element: "#menu-item-edit-menu-submenu", + interactive: false, + direction: "left", + description: { + "en-US": "

    The main menu has been updated with a new 'Edit' section. This includes all of the familar options, like cut/paste and undo/redo.

    " + + "

    The menu now displays keyboard shortcuts for the options.

    ", + "ja": "

    メインメニューに「編集」セクションが追加されました。本セクションには、切り取り/貼り付けや、変更操作を戻す/やり直しの様な使い慣れたオプションが含まれています。

    " + + "

    本メニューには、オプションのためのキーボードショートカットも表示されるようになりました。

    " + } + }, + { + title: { + "en-US": "Arranging nodes", + "ja": "ノードの配置" + }, + prepare() { + $("#red-ui-header-button-sidemenu").trigger("click"); + $("#menu-item-arrange-menu").parent().addClass("open"); + }, + complete() { + $("#menu-item-arrange-menu").parent().removeClass("open"); + }, + element: "#menu-item-arrange-menu-submenu", + interactive: false, + direction: "left", + description: { + "en-US": "

    The new 'Arrange' section of the menu provides new options to help arrange your nodes. You can align them to a common edge, spread them out evenly or change their order.

    ", + "ja": "

    メニューの新しい「配置」セクションには、ノードの配置を助ける新しいオプションが提供されています。ノードの端を揃えたり、均等に配置したり、表示順序を変更したりできます。

    " + } + }, + { + title: { + "en-US": "Hiding tabs", + "ja": "タブの非表示" + }, + element: "#red-ui-workspace-tabs > li.active", + description: { + "en-US": '

    Tabs can now be hidden by clicking their icon.

    The Info Sidebar will still list all of your tabs, and tell you which ones are currently hidden.', + "ja": '

    アイコンをクリックすることで、タブを非表示にできます。

    情報サイドバーには、全てのタブが一覧表示されており、現在非表示になっているタブを確認できます。' + }, + interactive: false, + prepare() { + $("#red-ui-workspace-tabs > li.active .red-ui-tab-close").css("display","block"); + }, + complete() { + $("#red-ui-workspace-tabs > li.active .red-ui-tab-close").css("display",""); + } + }, + { + title: { + "en-US": "Tab menu", + "ja": "タブメニュー" + }, + element: "#red-ui-workspace-tabs-menu", + description: { + "en-US": "

    The new tab menu also provides lots of new options for your tabs.

    ", + "ja": "

    新しいタブメニューには、タブに関する沢山の新しいオプションが提供されています。

    " + }, + interactive: false, + direction: "left", + prepare() { + $("#red-ui-workspace > .red-ui-tabs > .red-ui-tabs-menu a").trigger("click"); + }, + complete() { + $(document).trigger("click"); + } + }, + { + title: { + "en-US": "Flow and Group level environment variables", + "ja": "フローとグループの環境変数" + }, + element: "#red-ui-workspace-tabs > li.active", + interactive: false, + description: { + "en-US": "

    Flows and Groups can now have their own environment variables that can be referenced by nodes inside them.

    ", + "ja": "

    フローとグループには、内部のノードから参照できる環境変数を設定できるようになりました。

    " + } + }, + { + prepare(done) { + RED.editor.editFlow(RED.nodes.workspace(RED.workspaces.active()),"editor-tab-envProperties"); + setTimeout(done,700); + }, + element: "#red-ui-tab-editor-tab-envProperties-link-button", + description: { + "en-US": "

    Their edit dialogs have a new Environment Variables section.

    ", + "ja": "

    編集ダイアログに環境変数セクションが追加されました。

    " + } + }, + { + element: ".node-input-env-container-row", + direction: "left", + description: { + "en-US": '

    The environment variables are listed in this table and new ones can be added by clicking the button.

    ', + "ja": '

    この表に環境変数が一覧表示されており、ボタンをクリックすることで新しい変数を追加できます。

    ' + }, + complete(done) { + $("#node-dialog-cancel").trigger("click"); + setTimeout(done,500); + } + }, + { + title: { + "en-US": "Link Call node added", + "ja": "Link Callノードを追加" + }, + prepare(done) { + this.paletteWasClosed = $("#red-ui-main-container").hasClass("red-ui-palette-closed"); + RED.actions.invoke("core:toggle-palette",true) + $('[data-palette-type="link call"]')[0].scrollIntoView({block:"center"}) + setTimeout(done,100); + }, + element: '[data-palette-type="link call"]', + direction: "right", + description: { + "en-US": "

    The Link Call node lets you call another flow that begins with a Link In node and get the result back when the message reaches a Link Out node.

    ", + "ja": "

    Link Callノードを用いることで、Link Inノードから始まるフローを呼び出し、Link Outノードに到達した時に、結果を取得できます。

    " + } + }, + { + title: { + "en-US": "MQTT nodes support dynamic connections", + "ja": "MQTTノードが動的接続をサポート" + }, + prepare(done) { + $('[data-palette-type="mqtt out"]')[0].scrollIntoView({block:"center"}) + setTimeout(done,100); + }, + element: '[data-palette-type="mqtt out"]', + direction: "right", + description: { + "en-US": '

    The MQTT nodes now support creating their connections and subscriptions dynamically.

    ', + "ja": '

    MQTTノードは、動的な接続や購読ができるようになりました。

    ' + }, + }, + { + title: { + "en-US": "File nodes renamed", + "ja": "ファイルノードの名前変更" + }, + prepare(done) { + $('[data-palette-type="file"]')[0].scrollIntoView({block:"center"}); + setTimeout(done,100); + }, + complete() { + if (this.paletteWasClosed) { + RED.actions.invoke("core:toggle-palette",false) + } + }, + element: '[data-palette-type="file"]', + direction: "right", + description: { + "en-US": "

    The file nodes have been renamed to make it clearer which node does what.

    ", + "ja": "

    fileノードの名前が変更され、どのノードが何を行うかが明確になりました。

    " + } + }, + { + title: { + "en-US": "Deep copy option on Change node", + "ja": "Changeノードのディープコピーオプション" + }, + prepare(done) { + var def = RED.nodes.getType('change'); + RED.editor.edit({id:"test",type:"change",rules:[{t:"set",p:"payload",pt:"msg", tot:"msg",to:"anotherProperty"}],_def:def, _:def._}); + setTimeout(done,700); + }, + complete(done) { + $("#node-dialog-cancel").trigger("click"); + setTimeout(done,500); + }, + element: function() { + return $(".node-input-rule-property-deepCopy").next(); + }, + description: { + "en-US": "

    The Set rule has a new option to create a deep copy of the value. This ensures a complete copy is made, rather than using a reference.

    ", + "ja": "

    値を代入に、値のディープコピーを作成するオプションが追加されました。これによって参照ではなく、完全なコピーが作成されます。

    " + } + }, + { + title: { + "en-US": "And that's not all...", + "ja": "これが全てではありません..." + }, + description: { + "en-US": "

    There are many more smaller changes, including:

    • Auto-complete suggestions in the msg TypedInput.
    • Support for msg.resetTimeout in the Join node.
    • Pushing messages to the front of the queue in the Delay node's rate limiting mode.
    • An optional second output on the Delay node for rate limited messages.
    ", + "ja": "

    以下の様な小さな変更が沢山あります:

    • msg TypedInputの自動補完提案
    • Joinノードでmsg.resetTimeoutのサポート
    • Delayノードの流量制御モードにおいて先頭メッセージをキューに追加
    • Delayノードで流量制限されたメッセージ向けの任意の2つ目の出力
    " + } + } + ] +} diff --git a/packages/node_modules/@node-red/editor-client/src/tours/2.2/images/delete-repair.gif b/packages/node_modules/@node-red/editor-client/src/tours/2.2/images/delete-repair.gif new file mode 100644 index 0000000000000000000000000000000000000000..c668dfdee52b73e9ae2f21e3d9969986d4b526a5 GIT binary patch literal 29584 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 JgF8Bf{{x*;*Yp4Y literal 0 HcmV?d00001 diff --git a/packages/node_modules/@node-red/editor-client/src/tours/2.2/images/detach-repair.gif b/packages/node_modules/@node-red/editor-client/src/tours/2.2/images/detach-repair.gif new file mode 100644 index 0000000000000000000000000000000000000000..14fe1a423b89eb8203701c9000472bcdaf4adbd1 GIT binary patch literal 71182 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�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

    l}XbBr=h?=B;k=(_14>1vV37Z_y!yXo}Y^ z)qV_vbqM+NM31@bSB)0w;F`{Mo{m2WhALxO{1OSgO=fX$5lJ?FTohEAN!Ib*`-f=Y zk<8hX0Y1)*f|9^!!9FHy0@;jX+fm+x9pxpMrT40XN`U%dqy*dWU1~1vg)l9qQdtk9 zc7T}Sp&!ltwxduX2OGuaG{b@PFxH6Yg0jjRBU$rlkA+I3f=E#yAp|QY3>2;B?g1tu z%-HxdQdBV@R!@R4i>61KFpA9L${>R6GE$D54@`2eH^bE}raUJ#X1fe{71(=izDoD8 zD}i-X9>S7O*iI2sVQ?Jp=>XaxWVX&|9wlIpXm$cfY!!$uvS2}xh&Hho)dc=0YAJA2tO!rs3TxG; zXqKrcA-eBT)lde=fyrd$M=WWZTg7E*k)oyrZ~kEnuw1JRR&tl(l5g2wXex2d23hR# z=739s?@7Hz56<>UZ{S^w+V?;K42ftH(Zv!VYaT36gb6Q?1q~(-%sgpx5G3>TaT)An zIg*Jnq<320yqOBUsG=YgG%aYCd(Y$-SOD_LO#1W@XElxvxQ%>#-LcXruS+0M)+;cP z2L0p(ib{PNPY+&T)(PyIAJum=G0P#U)mnHy~(c_Mu#H$tD^+;bQBM73WKPzUTy}=^abD`6J3d@K8<9SXu^>bn=o3CVn~ZioO|DrV`tOfq&Q=E8DCB79GvlIum`m);%gTB32*n&S!v;)P#i?_*BI7kP+YMUg%Dy_!&Lpl2p2aSCl;?WhK8TPzLWigLXx|#G} z7QQIUd!7y`c}HVl@er;h>oR9dFcB1EB}_FW@w5$rpC$7laxKRG?kbD&cwu?;M}sQZ z$oHIEiHq3VKv<9T)c9Z)gB=%cA_Vefbc(05@8(%!3scY1alDulssp-mpyE)C`LmX} zK|8iaXEG}kdOUwZxmmhVJH(`RY6J^z6F8@;S&ZdZB*Z8>DuSvRok`O!*7zx;@@+x2 zaO9BU@hcl-Bd@kg>y8(eD3Z3};U5-Og+X$5uF*5u?fVUeQ#}q9Q_sO&4Sp&WUtHlz z?`M>0X}51uI>pGmAvtZH9b!3Cju{FGvA{necj;N4QFzbI-o5R-K6IJQ8=m}b7kZo- zq5OTu*P;u3gUPC&R@KUISUb~91z>F2E{d51CCHZpur!L~Ivy1kf_2!BNr6ZW*czsp zKFhK(D~sjSW|npeIFZ_<`5a8S{x!D$^PK)*<^S`X&LcJDmCAtU#BW%P9~c+0;j=|& z??Y55|LZyNx@-~v)!!dm;=i5~CVbQmV{P)U=VZs?`OA;tkuDey|I;boeNOhHGh{*F zcu@>3D&4J;&t8eEl5V@EI-Zz|z>s6_m6j#$&H+<{>4v-(ten$g;Vh8NTi$11i+cU% zISBx^x0}dDAG;0xhI*r)H^N`0)<=!`nuL~qq&SWTMwF3;f632spp7`IQKgw-Nu0OEUy(a&LmvG63a z)_7S-SV1z~E*UFJc&bWGY6B08nR#4q6|ra`U%^nXuplJIvddTjNn^eS-Z7LW9b)+` z+um>wluLVx4B>GnalOlqIlpuwFY=dzg+#i3u!V3$gbtuek@yaQ!a>_0QG7>i=ZU`%Bv?Rp-(7e1q~;Vs|}9pTYa%*|`dZ0KbV@tT5bj^6%|Q zIbON1-qT4;Bw3PJ84Y*8!W~vhXoDXVFI3;0?O;*yb;#HJ;77>hX5q=l7LcM{g#LR_ zeA|F4bRVY5RW*Dy+B5XOqO~5zr~no#EB>kPl(rZnv{2=`5P(IUvwwS{@-q*B*o%4*16I`_ZWu7Lte#$FvnMlVrsJ z3#Ml&2+oE=RelfdpB4=t|GnDqZ4fr5x)AXn3nnufrZp85Ewy^yBVCq>WE@c52*nH_ zT{kZzaR5kxXXwWWjbzPuE|U}kf!#jD)PoqFoj!y?TkL56p|=>R0nPTWEGde$DOqs+ z;~)$z(3Bqu657CzQiv4{z#1gyFfVll=tFdH?{!PML0Ka=ydv<%xsDJMx&AFflinTfjK~zO3q3StoiV6IXd*qYe+WY@E(l zUqgi5H^K2u9gb2ZT5wsaYPcm=TOp3NC+wi<+bBe3FMv&NTkfx9WAsiJrIHWzOg9Mb za%4cXPq|Z0nDWTWA0E+Wilo{r}-lp&%=e>|Zhz+up1bwH|Fd ztu13tkGm37@I;2p)-u-)i6_Q)QitNCGH za^ZVg*%Jp|E#jA5o5@UDWdk>BhQO4>Va^y>xPXeq2dHu?w|`W4(n-4Vx27?_sn7Ke z9Fwq#yB}?0cIIWae2o!f>sVvz$Un+fam?3Q2jH-Q!NitG9(SD+z+j7C*GhNYt^d{$ z?zSZW_s*Y)H^V}RHgPXuX9sdXbW6m91s-zx;y*8NA6g_Q1FGg>86yBR7=Yg%8xZdU z30pwz0lqOmVXdlvd3V#?A-pdEh+VvuR0qJk{V4> ztnziD=lXM0a;ZcN>{Ni9ZoV%8m3pSjRrKiGG>hie#SnLq7VgQ&stPeGYrNEB-t&t$ z@{c8FavSEDNCWL3Mp=efs8lCkwv(cLRUL;D9jKjE?`$+ki~npiMrv<2{!rxBo{;ha zeHZmUkiWCcxZCFCZ%@pl^_j@?I5|hDKGO1X$Q_GUI%6W0OX-)fMO#K2S-Y1ZQ+soi zKDK^!^!)hP$&?t2#6}z(Ldv1qIt4cQPm|hWr0YoS5lvAL9&1QLw^*2baE}E0sX8_B z2-NIwH#X*Uy1@&)_i(pd0ze9ozpf8#KAtKE?`%HK{U5*bmq6Aa>A%>q2@YW-`D6$` zC$ELC#%aOK*qvo`DFOVqW_UKigdU9A*~IO9V1ZiV|I;R}UNgj7nk5_XM*X~49>)rHW9@qeld=g-RQ_=Y)moHQq_SLpKLOc9Ta7wkKq zTYB(Za2(nXk3y^W)A4m{kaw@V2GSSyd#+63Zim#Id$33lf)c089& zZcS6_qGflI=`Q;9FG0`P7`_u{VTs?c86CoalqC+xZq@B>*&0XwY}s-hq^@dqBDC04 zDnz_ExJ-ZuFE?L{)G)VWQ+A>vHQ%c*xi&xAI^8I(QBp;Jwj8l90R;l{nAiaS740lW zK%l~uibTc69`*S=>UrcsMx#h@q9GDgx=?r;;ENlT#TuW?1GW}&vhE_>Rv@%;gDLc& z?o4McF!qiw_MEKo%`^O5W+Ly3pGniR17Q_zIPau0#AI8l%Yn6D7qFk^LEgC965}bV zH?C%R^Lljp@|=Vp(Ec&7cDKB4R5#6)oN2f-P%_jSr^|;iGkW*}(mZ=O;xRxegC+!@=A#`v~!&*{-pJU5Yf&?ChvIWWimD>=N z+5MbCl5+$RfXf<*_%E0BziN$a62NipmIKMj!RG&4YJLB@n8{_}WB$kNv-^_eDwipw z0P^C&V!_IFmaAsSed&+m)qO9{v}@k*K!Z_oO0A5LUyTHOynw1~8tIXI z%(p(M+pJns)fA}_I>*1Zb2s!mypom1lk{Y2QRehGnnR`$W4!@=*^}Ja#kI|9W}A;Y z*im(8TjhzfsyqD&7f;m{rF~hWi3+(Vta57|sGvlO0+rEzT@hAT&X8wO0j$Fi4qvty zmRd=QGW2A1S(IQfwJbsS*k)O@z*S$#Na+$_v!+-zQLfyAs$h>)e&Op)X&GltH+Y5( zolDy z-$`Eg*g3MEFEE8G+}W&QLl<;RG>s7V>wMA9Swm0g|=qi4=QF5iQ#sEFhQFG_k z#`1^Sj0Lw3cb^U|%in1k^*?>9SB0{2r(a!oZzZ4f>!qgjW5R*=CcUf!{`ERJfUux& zklpU@zH2&jKGj2fa>m?iEK(wpaWu!nmMDNhDja9J1SgO%Mv-?X;-l8VO zkP?$0yuOF34j#B-D|Ge!#LW}A7vnCy_}~`lXRt9~(fo%}{6`>4W@Q(%q1MmPJYHKJ zvRMzDs!AKS8(1C`p1YrZ&TKm6`{|BYW689K{tm(~?-aZWHoN?)uIH+%5vNSx*QUv# zTO$vitd10@MqlW{LPUSgoVI(?>Nz!dBxJ4gz?(2(7t#Q6_E`w|qPkE(;PnBCxoxp+ z{#v6YRvoA~hu+Um^ZWV(WIwuZIen$Yd810>Zw==gkWO zrO}9z2vS@#&z0wD`PVL%SvtXkb6u;2E=ymP0Ce68&(~X z?T*5W)cL;m6!`6nj}YjUSf$bQ9UjpB&o2U0A8#!DlzQ@GnsVsr)=&|VE9(5MIQFm9 zpP4);?G>P_qNh9}$m@I>5VM~GI94JdtqnXPgjEF z(-KDq2J!Ffz6|N_c2&0Z(IR401`FC^n8i+!D58-im2SFseLNlSwQt1qpWnFv$=&HQG+#^4pzwOO|OJ2O(BLjeg`u}uQ zQnc4L=Q7OGtTpB$glv_Yr&vE8MAo!y-{A>k!C8;*EW+ZAb}{40{Dgdis1`rZ>JkgL-ZTw6~NDHN~R25{;oXOtOh7k;T`E44Jei zO6mjPqfN4cA=wrd(FW(IK{d-sgQvw1Xs4FBQYAxTLxjVAtP-p$gr+9D2p$^-W3EP z)<{uMh=ibm3qkz}0fdSLBZc;LqhW+7Wo9Wz&Z28CLnlSrO-hgjS*!K6_dw+IZLuE~ zijJ>X(1sCq`CAn*m_D7u9M4m zl?OC)2<=M{)Z6Ew|ByLW%H{)P(5;O`gS7o)D@5MW5kCFI6tiDy(FqY{P)%&AF_w(% z6gclFtqu&^Uw$6H zN!VAnPqy5p@Xc*ZoUf_$d9QQ}&DoYhs`B@`>Q||k_?zhV(O`=JxAEGm_XRV|RLMYF zW8f9JaW9b=rxAO13!uv+QR`(&hX!t0%8UbIYsXaP}Y$djP%{YPw$>Jj)6B#>HVl zfJ>9fs`(~xNFq)2g~E=6W_!m%1NH%>^GIX6cO*2I^v#co>_})})x~9Kx5;3s;zYM; zfH9l`;C^hc;v_HAfKn~4JF8IX|BRhjdoXw(1QJfkGKPlZg}>1EKE zojc~4xDlt*>Cujw(_6Tdl+&!EExg4zcr|K$%}wZ}44|6<@02kFpBKf~MR}(Ao&Fgx z`)j9+v9|75ap&v&Z7L@m?He(e=sgbE^EEV1{FQ8E1gRFg8ieu{Zc3~IEH%|P*$$YKpH-hPQEH=h_nvX~g$-G1%m z_g_s2zsXLV;m>d95z-fJmq>o*%Cet^3yfS>Nbmi4`=+@b+e-2VP)ubfrq}Ow|Ngl6 zvz#+4=t3(|d-O~~<*dN)W|!X9?hIYR2pGsSWb=Aj7gU3u{tVpuQm73TR{^K86>fd4 z{(FYLwO;!&?p=`qKQMaVpbkG?3rYX^*XW&@aNyUh@b+fUfhV8yh9hQqh{(5#mJ%&j zn#m!TF%!dESe84#*TRi{PHjJF+iVcu0%qvH7P<{JP32@H_>_)J1arHbl*v2tYI`f8 z>}iqvxjOA@uP)@zEANv0iGiTlTT$iE-|m zSN`3mum+kGMBvBlPW&zoaX$Ej4RH4=*3o-EbiE@1BW0E2J3QS<;Z!m_zc47-l_X>o z{Z(Gw%OaBRFQ**Prtr_PyjNwLH>rD7(=zyGN4B9u7B8MOAQ&rh0mg?Mf8Q$#v?*k) z=%o_Jhnn`eE9L|^CzxHs@JZf3?Nkfp^jd##q4H%F$4--(vICOrm&17t(-VzQv7yx00RgSv{T$uYUtZ@DTb5cbelGS3djIm!+v%Zo z^=~(xhm~({mFPTL|MX^%av)u-_0rf8Xq4v?!RN?*l$5zTJw#_y=*M*m4Xq~aIk zlT&pU1a}h;f5q?iPfGorZrE{8r0ai|&UN@J-S8LX(c?;wWllmRkc{yAlVqqs6#W%s zsCmERo{+n~6Iv)de<#e+K+(bE=C#nRrNs*JPDTeSfR&S|?ZTbuf2r4Rb4ZDu5Xe z)tjc)C16VOTzd4$&E)Q5#@4OfIA8qJn!@saTq8&sKpp0nXZ#5d+`04p&VG&qEDhD$ z9DjxQXmn$}6or_Gx_ino9_j-Aq>Kh9vJm>)E<32aiM3p3n&_h$(nIN7)6i*yDS)NFP z*x-Qp-5iNXhIuk^w8_b)-xmwl7c0URSd2T&%N$IscWW^{Z==kk2s5zQPlMWW1neLj z#Va+4*H3A+d3Q!HOLpH(&cQUZ_B<0CDFqE-wI_g%a5ry<(sYm}Byl5bj8Xe!3HB(c zu;v)0s725F;l~7CM+>P;7oP*tshxK%Kl_=zX&6W`Q8m!C5+q&Tc>Q7e8xEU#3?zl_ z7u@slN{+0R!Yn^ukhyHuexrA7DPcng|EBdvTDj1qFM$CfwTF-bjx4paGxs%-m}JCa zP-V!k`!La>$)-7qE>GWC1`2Xh2^!nve-vz}ZwT%y9^mF+0*fvd3=!;=8X8L%?$z3v=1awsyx4)q#-`*3SL7B%mmv{%y?@q@82ZSVjU>)@l-PkkQZuZ%*HA z8t8o9VHrCs*pO%{yOLygVfmi3RkX<{+l?n`qcyHNlxkBsD~F^XF?@QPEHWx>BWb~! zuDzeDE%f|%cfRvhHCE1jqgU@VnG7L}F*&6W$%(I3_`w>xvqDf6QwcUFM6G>-)p)tk zzG@ALE-ALCA?wR$ieeILo#1MHHE!#*j<@}txTH(BeQ(}kSx$EeoP7=z^{w-@z2)}O z3VVmYT7NZ4DSY9_;h#CV_@MhrN8hMfGIHc&(On-hmF)+a6Tdgx|)boan zcL84P;bIB@=ke_SVeJ3^!#@E9I1mTeZTGbNjT8ZZIsb2_Cf=otN)tQ10iW7KAM*P1 zRQ8=QmH#3!Sh!`Q(&~Ef)cz9n%R-RJ%tiAGiOe)7H^&~q!v*?bEc@>(J}1?r^JBlFE8PgI{k!UX_YYf$BS2y#&w2_E%p{ zo%tAiPAOG)!>|8Ic)!?|@Hr9#kKVKP>*4sLE5BEk&b_M*9ewWBaL(XNF_rY87U{hG z>3QFSFIRqjeWmC0`%(Bj!!%E+4Hd01TyEs*{V12Xc0Mg=|K5@aGi!xq(-8VjSbec9QMPum z9Kb02l_N}7IWb&x=*KL3WW1nbcf7OvOf07Is@pP0byc#zXy{sXYDvV8?Ot}HXh;7Y zUdK+3kef}$1%54isj6gIp|+-0_SQ<{K;z`{E$7xzr0)RXn@@r7Q1!{em%1m1i#uO% z0gz?&fIGESSB3nmh0{is9>q`SnBCG_RtP*dygnBAz+%l>s`)tI*D8&f_)E(V`>H;~ z#$Pz!cU@KQ(z&4`#COwo_crW)zB1gHn)~=*@``e!O%V4~O$l5orM6~87uBFm8hSsJ zs?*Y!gt;(vgiTwdqT}r_VS6Pd`?MQgJz{^eFI`7gI6C;D;>Xf8KJ>ZIpNqiFOPF|} zv`P6tA-%9&` zuIEWSFZMAORUcUC7{*`Ql=wcMb6_ows~?iG`cy*hcBP5Tx!I)#V^f0*9nPoj>-4Y& zsf$^UUvqw7FybTGy-Izb_DDSMdT-d%5{;UNdU~_BweKxo61}VO-l^{4$2#^(5jn?a zB_D|eKGEG9Qz3vi(PVpTW6G*~Z|0k4SphAW%fjtR8o0azTkqSHnp~A`leN=0m9fwc59Z0|J@tq-dSeB=#-cIU+ck6gwhZOxqU3x4?}vF89Su zbf#$;&5Y0`0w0zI{xYSAwwEVN|18^#oiI(>>=0f2$;)OP(usQ5kBKZo(MlFORfd>S zM8Oy(9HLP7GYjR-m=}hJn!%lRCgnrG+n=+iH22&YNlj3$v~g)*)oY60laW#>{V6d) z-KCAqmdT=k|72`(aZRQ3oEfX7d6&AsMf89$FQ%Z{!qh7^)PlgmUtb^RHraSJw6jw9 zfgD_Ieo&yK6Iq4OvLf6WPN_aE_IpCm4Fcn@6K~h`mXA%Ua4? z#(V8Y$nD*Am7iT3%{<_hH=UDaJ8zFpTSXg^&V z)o*>b%IbM7a9SpPRhLIceyh*ZP^JlwF@jNeah`&b#$7_EsJw4z}=JDADw3n755s$y!XDa)x~Sezo9&Px0HArV!Q;^CCqO+u#iqaaH^? zV*JO90N;EmsRDkhg?haLR0o|esBdszg-h=bZ*`tm+r1kVXpSNcp#U!`@+Ils)w2VP zv+p(!>Q$*nzq!FM=m>i<`Yt%|YhF>v%0gNWZ>-FhvQyvPv`2_shjXsOj^s&B^f`!r z;lG*C@x}C%?j1}lU|{t>McF%L*t53!)$4A{?+b0wO{Jy48?Q&w@b}AX^HHH5 zP$eb-Mx)flDb@vWg!{Ud`8OyHELpiuI_CL)x`6}hOn<$q(;;rRo~+{i-rwE?J8K`y zRVgXHws`U)r|xXY+x1%s9}2r?WK=KdupYiV`|LW)vllI|f3H1|Y~$$6SjTrtVD?Xd z4X^C4&U&}mdg;>Y(TWA-jP|34o7x}E%N+CB1`7$M{O6bKUoxz(b>(yThHcTI}Al09S4L-tRw#eG#nfZx|BQ>jEgD?Aj zzE65BE;QR5Vkmy$>H2g)35>G&Yeh+2rovmE=*c_z4M#^#j%J-BWm=kJ&EE{~+%;nrBmlYHlaF$ilY84O^N5t^_b1wMO7l*eT^ z2Fq7$WMdT|Htw)XTp0sV zPDdTv++iYn;p2T=;!lRgpTwg{46q7=MUxqS55!S}fpjzVqj^r(&#^V0!`#0YJ8}wx zpJOjzAkvtK_|U|}O+>6kVv=uS_BIKgMrQ|1O*t+}JEo@mq(W*^Q7AzAK)5ktR%jeG z1kRLt2+<;X3uo}WBH^Vs=c~DdVRMXwPhtXzLd~Z%LgmZTshK-bi}YHV^WqwDG^u> z_z1^p22VB<(c+Rq^To6%r1OZ3l7zRWk2i3)>z8ER|iE|y4r|C(V1E2Y6vOw7FkUN0~vWjYO%&3 zn0p4?uoq4*<=m=ArT5 z&fdrf2U|CqS~vSxmsoNMPe>dmK-@8zRY`>}>I-kX=1bI|#K|C8I_&=T_srzp{FJ49 z?5q5DNVGB&R7ES{EM*h*V{5T5NvX+CT`Cf; z1-~don$*LO7f`B_Ft2owuXV0tbx{0t@JwkcHVG-NgUZJtvR`F2;t&a-f<6xxPAprq z2ixcTeEGtBiAxCShlzGRmgufKTjsg2&zz*bmcP1zS1LdZduwlW0q$Nasi+)z%0>5wuzu2KJVTmIHxDnP8MXY81zzPhw$` zSUCu&cNr|G43sDhM8l%rk|EFxxV8r<4%;A93U0^Uj#Dgtxf~h~Ql55e^n>2XZIS8+ z?L87o8OLzEv*$o@-Ft^YrEX)&{CltM@dpcG(Q#yu4FN^PK#@#@6&aL9K#hB_VC+%6 z_25A|d#eSymsVfKM9b1Z?KrR~9W|K`M= z#jw#bU{?0`QKb8>N+4e0U%>;WOoys~D{IdtNat zoWfayXeMFSGLWZSsXCN0i9%|!2T04FgI*6#zXf5(As>U#vJB8z1`CV?e~UwLV!#W` zhb})sW7|v?C<#6eLcg^{X)!?MI5rv+%#XS6A6|X8$c1v>t499OYx792Zf9+8#9j)e z^~0k~kHq!>s;+Q3H@1aJ1_SqQUK$AI(F9>4Mt*|Ze;_zAK%!VMf(czHWrbkCA)xAZ zEE`V-NSFYi(pi7`gFdjM`LST8Qb8jLVOWZMK?=SpQ@N>xe=QU%yt&UaJFR&WP9t7Hv(rz?tDicl>DYmwku7&bp9OF5lAD2(;B{4-H|mJrPSJb;Qtf*&WbKE|Rd zNUS3EEUpApZ$?)$nVm*}7?K}kCb0+NAg;8iu!`G8D;;Xz5}e?Lm(eSC9>0T9KrhNtR6~1adnMS z9~H+&2cfeTi>Sc<4Wu=*uW~fx=|*_MTvUze0Q`e)*#-%fiK>-SxGt8IQh^)%(wjB1 zl9^Y^Hm-kttS)QHX3)WpZAN5pt~LLnKKeK&qlw)mM&G}wEz#VM?Opf|70YpQ;P53H z_Ek|;M*3C+VE-GM{xv+ElJe`A1?Kds5w#}{@Bfp(TvLX<| z*LYSj?AaML-)(yLT^s1d~slKTz=5vI02c*d`bTO@>JN> zsG}3)K(+`HGV$R=%A1L_-xC?4ud)nY<)B|R+P_Q_d68c0s*ynG5i&+8S!aUL95yr43^5*h(9e+M+{^Qma;JW7cYG zuWE}@qsUD^-_O0j-#z!-``3N@&)eagoOj+w9Io|8 z$?53N@57@N0Vx0SYlYgj7kSfOpK=#{J`O>CUHQJm%WXtSF> zHymvfom@3t3uA>dz-9T3E0bTC?PM){@eAK-B?l}6R(6{d0yLHR#u8!?pv%tF= zUIFfNL3jOvBb@KucpMtOaQ{(gSlDQo%xoCKH#|Nn!nh|QIx12D6A5U4XzKpRAsCZ< zH^wh2Cc!VxEg??tetdRt;%$6l_(oz@N-{Jv`DTBrJ0>k3mzMS*BQys0Fb4;V%_+~$ z$?43=yO-yGnD?}(!1K7^T6SS!T2V#OjudqI?uzFlcsHzNWuPiF4%C4(QS*?mas&2@wX)UbH z$f`?vQdg8!A751e_E~*%eSLjXy+vw6Om#z9Q-e+^p``Ey?$rxS)ywvmFJJzB>6!g1 z=|ywftLB=P=D?0OO($=<-nL$Q-S(=Z?fTpH$Y1SQHE&1WzJ1&IHlwS<{Yz(edsqL@ zo~Q5o;!gU8+ux7&y(b^-gZ)Lr15Vw8pN0nmh6i(g4A%4ym3;V6_u<2f(GQ6uBYh(y zBcmgcqa$Slqir8YOFoTyoQ#gW|2Wl0ocl;D`9vHUBc_fMBgcs`6U6L^vD(S8kcsif zQ{(;<6XX4p-BXhtbJIa{(~lND&rHwG&&|y(%%v{Ojjhhj|D9W2m@i+LZ~QWk`?3(f z_%(R?+xpi<^6~CyacFsYX?3~we7Rv}WoLEe@%hS+_0@#+)tuF}@$7w2Uc=Q|e{wHFu7q>BO4#RTbMnMC?VB9X`!KR=1~`ZXJCU2ThN@-hf8 z@ZYyiC@5(FB!JYvH^;xX2>?n0JU_W)NWg19+Fj&z^g;0VCP3MA95(}&yOY^z-9}Uc z1PwB{v>jO0MIx2*`=<(P#)d4xx{>Xl5ppJ<+(A3%zWAX zjCHfN`@Y{QA3dGcyi~Bg_&Q;p{bLd5UH6WR6a7C|xB@)8LuD_094RDt^_MUu*qs&n zZ@qZFkskV%JIH^iQGB3TBGxyN_Evv)jJyhH4;r@F7n6B;->;4AuOw7vD$T5Aby zeuGNs;rE%B{@*@!v>*Li>`fu@nRmWD`Mo(&>+-3y${uXsQbpswf$(0WeqSaZ02hz(QIbz2&efW(VJgS!BdAvA{#dzZfml6cxeas9l zD*jAIpYV&MN%HlJm8T+N4BeBsaf$AUe0f3r8AyouYNj5$?`o#+8B`y>3Rgs@u;{BX zs}17iAl$bpm<=yEhHiOsqvORz3LNr$*9(0r!$m}HHE*py4(J!(C=LP1L*yBTUkk+j z4P-TCPxjy(!DHEtH`Cz)<(p-x%G;aeDb500&vNYiwkis|YPN(i6gYv04~(f@4c*1Y zO=RnK_J<|gUTtsJwy%=Lw`E>Ux21?TYGjWzy#2E+*+`_2yhZ4^tIn$+DfBCNzjK)?2&XP|-R<_jl!;fc9Vg!g4mhr`uCHIX6-=uD&f`4(J5S zO6?C)UAYz1c6Ww(Z&=BeZnxv5Uhv0}v4WsqBMK|Q=2z-=1rXgrRQ0<dK~z)Fhv^^M?9 znjedo6}8SsG%aLn-jX64MY@nHuRptT$W&jBNX$xzsEXE3&4ik&=0NADei@pA5Ne5T;5m~y_KXz#; zt!o65(Pf4rAsXX*18WE6BZ$)-_cXD$n+n&Z-tI2H`5ry+5FyO%(?h>QdDj6%+j6%k zNO{&!a8z-85*NQx&fJK8MVX~5D?d>XUsg3ns~jeP2KvWeb(3R=+7#q0JAMyp-RPL^ zxb7jR%SoiMOQi7;Kv89D3kXqSAq5EP{9^!^J2U!gZWk6-01_kY0^uk)P=2t7iW{bD zk+=kwZ*hf?x(L-;j&9ro=t;TCQs|=7W|GCT0)a{04S2~8~Yj1$D+~9{yEZL5gWjZWJ05nExze>w{vN#|S zltud{TGHKL52Y|mO!e-I=C~CAkbD(Lf9s-*nL+iv(nD8*BoYqvY{AkgoPRi*7=xyx0eG}r-ib* zj13xGeOsFBcrebY+>0`1(#L8xz!^THdVpUy^{H$?ObokyN}cuyfk^%m9H@`=4gw>D zi7DBCg@n+|etLe#w#J6d1B478Hj2VT6^{36_YOPwzS_@yomzj(yS@A-NutNnwvWQV zHM%i)Q;@G06(_S_)Z~$z$`%+F4cH`7QfZVi+(%OIK8!}0Zw{mN411{71F&?CW$bti z=IX3{io6hjZZTI|vk72pWcIp-9^eVo`3nK@Xg?{i682|%t#?m3 z>sb8Ju~W(z+ShZbS9(S&kyhT5n~p}32)R;a84dSX24uhzRNDYCUN$!_eL`^ZtUpLn zgTtQ+Ad&2T{IQ@MGy^+K)|xsKYy8j-BwytRB((J$3L`1>Ybnn`oX8ugK%O(}>$^|x z!APxc+|z`3Ks^(kJa(DmvT%XsPi=a?UH6nuf`NR*&}*z^4+#i|_R#PIQ2EU)0%XIw zfq4OGrm9Hrt=;nO)VXCI0urM4wvR78pj~aSi_*ZtkNG0tijf>YZE;KAr8`vdkjQSL zX`-LRCt{+NpgEIofE(tnm_&;jk?#n_tGK_%(s2^YUDwMo^!^Q@fTMn$@#%~4&DXy}biurv==g0L zSDHK6-b+(RUNv-9$clW4$92)Uu{)O!Q?UZD21EDS>w(E7=C?Oq%ou1oSo%MB+Og&P zJbV@xA8a8l?GoJhz$m)u_stz$uBOc^1d_~)uE%wBqi*f%-ty8np51f-n7GRkwfmdR zp6tJRdDeEYE1t-4dD*?IZ$obV4e@)~!9#=d&o)nGMkdOCtmM6T+HXPa4Ab@LGrZVi ztc=72>mETL#2!31Ki|GdI-U8{`G=HAAMwh0yD?v#lsnTEuLCWEZJKNGOfx1Nw3E&^ zIVgnJwH_JGyu0}G>D|TAJCe@MZxVnL15(9MpfC_s`gbafOB|le%#r4SjKi7e*UHhX zs?qNdH05Yh?w081FQU0o{+C;#*~0YEp){|j{VZ}Yw3`^Vo*4T*mdhNmax;vIsMvD? z<6oGmd`oP%N9-9UHq1R%M-{6w6Zm)|qAOnuw$B3IW4XSGy?`<9Ei#&;;_RTTR)%pm zTH+3O8TJr-V#j*k!Pvzk*v+aqi*BrfCF2nse!dqUz?pEj0QMWs7#fxkR*_54;=>ZB`Qs7`V9BV?40cls1Eu~!Wys1& zy9WrPV<20{=|8K|nbkymEV9_`v*?o`Z=cH~A*jB2r$m9$Mvj?%5s=6LoLC`Fq7^4K zi<3FQiM3KaAY^UJQxzViydIW9?3{wlei zGxD~xd70dgRAIft9U|lR$K3tBs}z;LjJ=c*eWMYjqu;G#!1wg+QD*|v4s4+nV#5gf z0FG3^oP0F$THOIil*Qo;+4>lhXjaX0Fc81ERLr5 zrUn(mgTw=X{CIF^W*$b!5bI$`iJ(gEGfbzjz_GgIh8PvTGAcPRDpfSDuyMeJTcq{q znd#j$EPD3R^JeR$SQ;7+oy!%20oBozm+=5G0;Mq?V2pq=A;JEH3V~!02cE)q5eUJ9 z1?PZF7GPU61q~jYfCQW319ClVvO?@n53cVint#7%ZdGA^h6SVnAW#ft1%L)@YoSYO zGfoM*<~fytQ?Je$wzzdDW*&j}-vWvgsp9bz90+K97Zooa9E%5V`BB-TDgE&PGc+`T z0HMWGL;*^)!hsS*s?Y$a5(4N&q|jos*`5Mi6BgfLwKtivQJr(f+uGik@}>Q3d+K{_ z0#*%sojFrf?`~1Opk7@-e3puU zmuv+~yF>=dMF^pOaCtG~h!?NpG%~E}E!y(dA{sc*kXN%nIg8p@5)mv%0LWQT(jpu6 zTLC+5WxiZMIV3c;3%XeZvdsiyh)&<$Q!~OmkT%X6p0s$pvpJjXFJZ$yHe(w~n!|f8 zX%Td`eNU(H%4XUOTx&GLFD)HF{&4wX;5OX1*V7nFS5jT=w*_4HqrywPj3ok9(3C6$ z2o?#IKmxrGz*dJ+2%6%&jZ$kC$hg=Txd^-xVf7Veo4N^y;W@DxZ^u|u<1!>(Y& zd@e7#Aqq{w1Zd%afkTl%RstS5Pi07cl^22hB)}^y3=F|T9>Pjl@t_-X6rq_Qte2q- z9P9!YPnq_~VE0u+_~wRE=jZu8j<;pZq{NH(;f(y?wa^!peu87LZeqJet3|+bSOf^) zAMAH)94Z_R8U2u}-_;pPpsZGh>RV90jsQ#GL1yrmp=e4uJlH>=F@6^x8_+371WUlc zeqB_dixk)MZNF8>%>SVbIJ!;kMqiocTFbkwi7m3m6S!DaDRkIdDLesvV*J;_?TmDlh1QQH!?Rs8Y0sd(pr{BAKNjcJ~HD{Mj_=ON<5p~ic= z9ja7(6io+_t|V$pJ;+j>S#`CIZf(2P3lEW8q-fU z!Rdl;vb>`jGk`J*7gFBc`SkvT*_ShgXD?Z7X=;#Xnq^;<<#w^kdFuVt312gqyc5f9 zO_u1HP)}P4uac>L|9D4w9QP6*4wY1R{CR1Kag|Vi#>0deJf?9e03;+yU#|IK^+N=K~7 z%9r`Ik~xptQ(N`lDECCauBSESie&~ZLOaDyM(61+=D_@87t#xYmJ*TK616WR8bOjT z%4omFNG|Khz@N}<2rLr`lC(b;0T&rFk#Q@LygQye>5q zu>AMSqSn>XsbbN69oautj;B4c7dZVza1`~*%1G3L!QNR{u=rY!tKet=hbxz?Q}*Ss7dO4 z0u`OvZ$(Q5C0kOkQcI;$N`h?Y!;P`b4d%}2LAB2?o=x$eetx~mB5W$b)++b2RjO7P zBNc*{z+1SB<-)75K}=s=F{G4`dfHK#7pqnTMwI+kD^*ah2v)DwHLX`bZ1#q{{Jq*J zwHA4G%^b*hePlSl*X{~GD_g3@aIeP4a@j38pCD>LAL54$+yZbJd$n|2@}%RrvKUfaGN?<7WfFkd!W7kaA&(@hlWpL zbvG3I#e%jFlAI}Kug`jKUhGEBwVRXITv+wppV7QW>ks5S;8@pZgHZ+P8H8dDB5Z(B zHi#|)XoGb1H?20q8=EQfk>RdOd%l3jxAsHxYfQMmnE=72Jrq0reXAw|rXMNH#`Vmm zv1W69W|oU)5|y*4M>FExo~8IA^+%Ss-&?wd%EE=MggmV(4fe+fR?>%7a)&gEp4O@r z)*7A&`$tC(@4gPg==f4Jgsbc4xewBo?cDip1l!)QPro7N={mD^10#&W9_r^}tdnhz zD0P1)t1Yl-Gx$BW)Te29kU`g$i|#nQ*~4Z~ZlZZ~RIw*+(w`oiOWRi#JFa`8 zMRwguc3$57G<|rlcaW}9l{;$0J zTbgOwAh6FUVDG>kI08#;1WcJGy6PHh-=YiGyIr8HtQ|vWT_3UMN&bBKRM38W)d;t{ zSn3gOjU{6!(~!;#VZPW-E>4(*5Y2VAvRU8jU#o_L$&1 zoK7;jRQV&XbiBpwy+n0&aO(LN>k1l&&WOXGUj&WJ*qTOfUU^XI+;RH7v+3ohug>j% zfBcdT{7m;r9&z`ZacQaUfT?|;^+Hd0ZGSP3lx>mP&5dtb+<&I~b&a~7tP2pIflZ7v zpILwcuf4i*nk+-llc$vQzi5i9MSQPUc@nlWb>AiO=rI55 z!>Mb#UnrMsZJeWBhsaE_o5hjk(&WN4*eCY=tr?$I=Q!xK8qe1=u52JxlPm$WCKRjx$ zJjQYjZi+;;Dteg}As=r?a@&;Kl-e{pj1DXLeJ-@M_GA0Ywt;%=8D2>r&Mq<2>^c@v z@;6l4vR!{qV5&Ap*81J`ts+VdTY^LHEo%2`k@7z4o;ZXJpJSSwKn<<`$bXMt(BKqpTCa2 zeG{}W8f&moNND^29u1}~+uy612l{?HdP;i2(B!Q7_j$DK)5yTlfp39SBKeEfbULdH zjxbO`(L;K>uuMr#FY;)>8P80U0Kc>8ng|zAAjUFtZ!W}GPsOH6)@N2NCDu>+tfVO= zAD5=W(+mFBXz(n{{TaUnmO`GIU2QNm%L8rzGOEam1~3oikw=5Id}mOZC%ho%KchkQ zdeN2DuSBn?M(I4Nj`e67w|eWx_5}N-o&a-8pdMJA)-i*jUo} z4E6*+4d(a=fW*;+$uSTYA>ww6@A)0U49;~()`lfnD4v0qva5&31BsDOojgN_zYETy0>h^@F&pnJ_#hY7QsC89DkbUh--+`}q| zDXU8F{z(tVB<8U~Wtg#j*K*Dzn3IPAMrBQ*4Pd0`rn$^SMMVeXv9dMa=zvay;_<>d_GC)5AY3EGb3t|26?s*N339L`un^KsnU1f67IX@TG{o zRMp)WdWNt#<%Tg6XPX}2^Okt!(|eSx$xF;KNF+#)IHK8xVM!kb3a}6XNO=G(eYlH> zpcYpY!(W&op-n9ko^;<~{HdW?ja7b;4MxHkPoY!=`kj^GS0}(h*MS6Bl(+n?&u-_?~;OPpX znP=4%;yVPg!(QW(xP3&ljAn-&?DV^#SOIq7G=058&r`sn1W$6~eeDC6y2M^u3S@$e z>LSyDaXKd*}pzAo#Tu;?b}!()Jb`$ZfA4Hj6X7@)HUPcL^eKu`OdCu0;rko~Kw zuw2Vt%lMN#7YkIk!c1Tq4Fx|-BO7@Wz(>!0wY$`|#=!&4j5Qen0=Y7yp|H%jb!H4@ zFrf?VfiVOL0n=4GuoQ#`NuIcDAR`P&Yd7o^H08!Uo*92qx0Ebb)fm+*nf`ZLSWAGL zhQp()Pu8Op8MzS4J-L{`oaCC}EtORZEXRV8Ff5zuBEux18|b_n1D0CihE8{(&g0?? za`{PAG8U!ffItc%6bd4v3W5h^V$}fA?WV*P;jaLSO)q{bDYQVwq{XHN{y> z9-r}}ZT@cP)K3|4UyY%dp{_7)Ej#}7>RDR8ot7Xepnej31`W-?_alnBEGwkAPl!|sdZp=6$VN`z z@_tF4(5|$5<(K9*;~UGm(?WEQ?f%UJPsU`8FpO4x3>4j}Owd{35f7vnU&O$ra`EkD z9+MBv@_LpGV*9Z*M~epXgSY1zPQTmDB@>#TWYu~QAG>t5s64ywKu|lLpGA|d%(kJr zX>53rSfAhfJmaVrF`sT*$r_S{KkouHc zKP7Buher2ZcHEb4%O1_Eu;0(j?M8*XdDSETLh9|i6i&008*)To$_+0$7ZjhyI~r1x z%B`B8q$d-ch^3nGJa*e$(H~PvjQ2lgeBV8Qwq?eqewBmAaC8#6R{4L)y6Fk-*D=mq ziZ?%7zC=16H1FKcCnek>uKZ4>I^LoD$}H9tO&>7W`G>aq{O1GG->(U=B)FLNL!$!H z*{1os(@!oJI+M%iyBY7!Hq1$<^WQE$9t~(bF?&FH_)Yu}Pe*ARP96;?k9`TdzED(n z2LeM~g?RzjgsDceR$-XKoH<(_y-0wQ?lXb+$hXlL0o53xDh;8q7{O!ZGjz<|i5QvV z7>O#4^UN?_F~!M^M=C8m7fXC}mW+Ea#w(my@@Sx?iq#jxUQ3MLSY!B!$6i0iDyuSS zaALW-W6$^alvGrXEEs}^zvUS#@kRn@@O#26i*!( zfA2W%=U$xWCWH4(g3o6B^jJbPDq&_XAucT8u2@1clrO%8_co~@F>of{j58tt7^Q^r z%@6Zc3xhcwvzDM(o*v&VHRQXL7^^!F%`TVBQ<}`xooq1(Ptc8h%o*3nsrN)t;3+oc zF{@~kVbZ2NeM^^-+P+cmrjeSJ@L)meFsG2w3!#b@p28WX3jjlkE~V+cw3!)^xt6qr zL6PVHNT5K3L#pBImScT z+Tv2W;#p&2QH+5(Cib~@;@M_f3eMI!%CqdDZnzsdwhI#Pf%-7m@dIS*PrkY`%BfJspz6uiz0EOqB81ie*%tKPn#J42*bD#Lm2XU>Z)O;kWsFV(cq9X?w&oXLup>F{q;DpF}0GZ*=&9?^IK4g zlbIh6(MJRH|A+Yl%JjRSVgW#XBE@AiK%MwReD{s;PL z5YQ+x^auQd{Qsj+eQ6Z4FyygCb1f>#+MfU%fLUf6%7 z|1$Ea8M>Cs2&hgrcXep6AJhv?btj@WDA(=Yy&G4A&AuMoi1xG=&jU)uqmo~t(#KH) zg<=yJYK>L?Qh+abO*pH$p`C>$%B^&FSg`Q5eTsnjSwZE?IV$4-;D_Y8HirUR7?>6Y zwj}`Uh*V-~PvZmX$a?~oyeE)N908TEpzORy!3B531AsCBnsFN%PYi&}D=}nVd2c;! z3y`XG!V`Qlm#^QXarq$5x{AC>OSu2aT)*Ze^E;7nJ)JRE8HtB8pfvp!{fxcw!~ZVp}Njbx;I2 zsf$V|6?ZfL?q5hqUNl{ReU?3jI(IAI*&eq!n89?GeWf*9D!hP<`kmWA8g%Q?cE*rj z_fxL>LIwy=3z}qLue=6ifHqgOJo{;3&K2^nI>U+7P&C{ukZ7g!&&-lA%FDNcC>189 zet4_!A&)XB=W1l`Lt?g=RMkv-(V6Oi56@Nqy1Z3D{>UX7I}IkA!h*!e{wR{ExH(tU zMP_#FLauq>U{dj5%E(|MM?{8HuPAWfwsw}G{ZL-yP}U3eyv?D@D7}`V_9ILf$xZ#}eN+?%;r}y};JU{>YKRy2#O^NGJ;p)Uo%dW%wWG zV+f_y5Wj%YnYW`03!`7pMi+NROVvL5ca0tyJn9bpxJgH|efF?9^AWeQLazds2NWSDDU!&9y*ROy2dF) zkBOCz;WThiH&H9l=@?z{xa4U*_NbV3hySuf@;zVO;7Hv-DSzru0;h#Bhmp=l;T{ zlE87Nxk=40;JtYk-dsBO&`MF+12RpiU`&X=$9JWv76Sqi3atVc31uq-o} zj@4Z*|4ne+^KaE1U$>oLLDIs%q!++6-_1-$95Gh1eI(A!-PM|!ah!&YcAapHy?RS8TTQt0j})fuIy8;d}~EH zKu{&5oW_oduJq-K#=~V-r;OD5T?aXkQaJ3{d0y&@YNnvt5mN1+;IA+iuc+->t=%gGimk`#laKS1IhuEpKj z`uk-W4rC}LsE(SLPuD`@+~`*ktU4YZ9uay*A)aOjdX|d8cGznT3}{~peK$RQHnhIa z>NS6gAW7tw_&8ibo1q&IJ+0b0^r-R0+drQEUVzzYsM)EQw5aZ5tj!7TTr{pp2nQ;d zybdvGePz;dVA8E<`d-M@qGZ?PHp};^?-j{5^+m;CC6`|@7BufIo`;0jikeU%EZOc^ za=o_XJ+$OMv^y)?70Z~U2MS|)d^aw3e`BdjN$Fx1*Y^JE*xJR}R=%>GxJ>WDX6J5e z=WPr0eQn1;v>RQNUFt9E_i0ao4^@k2kN;eteZ!&p( z=S^E{xx6$bt*7wG`4^juvWv@6u1hDu9$FQ0XQ-@3oY-QG_=8%T!L=vLo$ z7KsK}Sed?GlJ3{|xx)Sl7XuJI6fy>tJD;>Clkb=G$NX z++I??qsd3Vk$30&^qtLS={@DUFP= zckm5&rr+AOZ)7ZJqgf=K4^UIv1I5z5UTK9dzmGO5^$zp=eIfOsqyb~~tm^wzwRL-_ z$(V67!cV`+J^m_Y0 zEh{-}BP^RZIvhgwaxBq-P1jJU;yrzy;2cUBdp*;%J`(1PI51`Ew)y12v1O2vNdCg} zry}`Sd1K+D*#u*$+?|2phlw`V2bnrN7)JPX-4aW4myi!)g9-`ial-(NhXHRsbP})AqM* zLqB^yxc%p|cWhcHs^LsqpXqnz)bxx;;owa`0oJp0U+`h015$| zQy*}^{?{o007#DeQ^==CT3Cz)N&Yf?2+z#xNL z+szb}vC^F)a?Q?0JZnuipTSioM~!LC;4yz-`eZB4+vKTibU&+lwvSo1XqJwmU#6FJ zpxnQ#K-F#n?ju=ns+`op*xI~+W$z+*WOuaCMWlIKEAoXHvQ%C z=bzW#K05rfc4_9Wc~=4@he+|vu6bVun~E3DY@o$pzVMC8;@N$Rk*7-c*LmiGEs4*s z<%^We9av7)+rJAvXFa?%``Y!}<&g)0zlPqaQ1NL--W$Y0vn5?VzHKF6gykkruXp@E zoc@#J{X27ZDFb%z=HEP6{1xY3oY36)<9Ex%fM~-jt%&-gEvj#r3%0~8NvKHGVhogQ z^06?PO*9rJP;U1SP$Y`>7;G6IPT;dr<&R@Zhw>+iH7^fieP3-Zry%;pR#Fu|s`hjG zV!)Odt_zOo8>{eOcW&$}SC30d%sTGyBq8R(YzuwywHzBr%up`Mt9tE!oIX0$l|Mu4 zG>qS%+oRMlru!9_DYga9DH_Wt;J#53wqCvQ6wX zgXtx|1^p}cpVj_s+i4~{Ke1o3@zvzGy1&Ck3+_krTHX~}#GlRIChs;n{dc>+57!c2 z9!xt(N1xd~Ym_VqC38Hd!Gv@_=e)JKYTfGo?QUrP&d(mPmvg%vDIe7X-!DA*Pr0v4 z*Y;;sLE-Arg2Kgh<#WJ%$)pNLiBj*Y*7o&BGiLnD**P)x-zH*>O3YRL$|S z^pM-jD<6j9UjX!G>&U(SQ!@#9eZzqsw`T30hvuG6(%50O7G6#Mt@J3x~ECHzv4ZLn09M~sm(el zjF$X| zla~HA?nNyrRsd~qW;b9R9#5ADr_D#fIZcQdldwH7x&Q>??!wAP0zmmNU7!$x5|IN3 z=1WdOD_nc7h`VzqL;D^Z_6_Q*?`LHSSt3}YsWr(D4P>^sGDMfQ_}v=i(mtSMO5vgAQYSqhpbL7@HDaO&r~0BXCkzem@kI;JZkj$<|2*_DwC#7vZt8Q5 zHTxk=Z2?#ZChiSjfRtgos^ig>$jxjP4p3HH;R;-&yu`ChbK6lc?`>Ed__%f2kz^H_DBY$0Er<70xp16W^A& z8|QjT4YNjf18@McmSYOJ2D?~^MF1aAOvNJB*XccMR4WP@9>eqe`Y$Z?HT``;qS-B> zSXW__B9^D8sa#X>O*Af>WiH@*9FohsARw`nmLZHnK;I<68QueYLhM)QFZwUY>q#T` zFmFhCU6ilGbb*~{T8qKOLKJ>W@xktR!Fm%Qd`g<7N4$zD55^UzS4!h#_+|SlMSQxF zp9iq=KWV+IX!$NI1P6Ovj|POBc7Yd)FpPDR7UQ`1#4LVIM;qA(KU$rW*OVbQ zP`wXAi{d4Tm?W3Mo`-*0sFbS47@Y}aoPWX~t;5&eEvf-1v{BLEpGf*kJd2dFssJ{H zB>;VhcFHzQ>C@p%Fgt3u&Y-}N(xn2XU_e%*~&d!M(@jmB=xmfVeFL=p1czfPK@bqRQnEF2G3-X2IU1aFs9jtJ&1b7)dc&)aLEJ)X9Q;RI8hQ_;CK-)AdsLlitf}9}@mC zJm0>q_dkX&D47kF^Pd|-vy(tiT+*U0S-4ru4Bz@9?~pyu#@{EMO7wc=Id{~U1oibXMH0koDwxz&i0NI^)8MUp3j62uxw24gDZY~cJoZ4GKk2`5 zzfh%Yl+on!-$llEq~muk$yTZVar$GGUyP?ASJ&Jc6XZiEX;BAd9C{<~F3ja~%l6^$1YGSMsCnG~?th0Npuq*RHSGb`Z z)>w|Qb0ADojPufGW?mIZNjPlAOd9`x0DjqrD$?S=aX;DVzi(3gJWk&#NGrU5Kkhi@MHP%3 z-Y==9uWhF99A^MdGJZibHpMbN#RUDS3Tmp#WL}~nGyIw=lvyo{)jo?oJd3k1i(4%V zGK840%Aie>WuVTABxH$%Hjy*NzgZRHPt0-j&CXiUn)Ok8 zG!8X;p*9$nQ!40_gQX@n_g)q_C$4QRcn}8>Xl4QC_)l+7RzHs2;(xt8 z0!j|-S^m@8rvmb0F(5?@h!z1#2We+EsVNCt4@GZl@J;kx&G9(Pg~izI=V@L)R(2!a9KL{mWU6tOU%5&^?*bfTqs^CGeF|7GM@M z#4yt`IDq=y6o79{^v#;6*B>BP7xGRYfN$FBy|8spuD%KzYL%2Dsy}>Pg}EBXU2W`8 z-7@$rG>C4UqC%7lqy$eV19>drf0KCf0}4b67DUaOA^A}QXuNn9Z)RnAG6gN7##7ie zIMdGG*7WW@YIgXI5YHQ7o~{q3Zamz-aXMRde;*|oLtTpTP7)HX$gZC|h2Ivye>LV$ zjjP7KuNL6~kr#w0JOvXP;vWI5bikSYUmlN4k0k&${HT)Dp#K-i-zWl+eS|B3y2JLS zeDcjUN=Sz=_#T@hvsV>jNAp|sBb{9zuAJ#ycR7I zOc(QQ*xWAOQHQ=E`B63+L$P=eD*{Re@+bs>oZA1xcyuiz8uAb0VW5*?m)a8-pfbAC z6Ak?Xknch>KwO#iUD@MZBVW79{HrIWUnA%RMKmXMbG3ntyjCdpO!7lH*Q3#?cC%`$ zM7OVky5knK-pGbaT3@m;U}dHf>qpT}o{ z;P=D{KjR>7>^X_C43aHK4e5OMk z_@;*OG&#InC zL`fv3Ci4pUIflFu-u( zbb~N-Hv%>#p{SG+D!hO6Jn!qe*Lv6c?mRosj(e^(oBWRd@j1TahmWwu1Q_D!>GArU zx(A3))AlB^$otaW5SSWXjqQ@@7ENIHf+5{WX_-ULm4_^5uoh=mVivOzg}@Q&Nl0u- zrO3<^Kmx;gJZMPEhj3m)Q8@3`lQf?_0a6M6BxFMW-w^w+su4H{Gp?{ zBii{B0L(Ma{|KHx3!cCFHFu^puMC}`wvAHT42^^mn$nIRT2A4-fN95(;(HPk@{Q+v zP`C=sV23QKcP~&SFM`9PR88l2HVJqGbkc?vOUf6)M2pm=OO%UIt`|!m6k|xo^?(`Ag1sD^~cxgLcLqL82n_lBYLT;PR!>lZE~IxiF_Wx(sIK6UGdeH4cR} z{1Zl($yIMUF_Yv;GrrX!4pwmkR@Dqv%_UajrFBDeDDntcAgt;9UXwOkG?QNUd;xQ9 zVTW$9-*huR6yhM>!U^_yU7gI4MYme`GxyZV zn{sBag{IdHv#)ckVCr<8JZ-DE;XFM5i%wLWNBEQ~}v(oj4$Q``a~ox02~JNFi@Lw*EO+$ZfkNWvaOk{B~dKC_psE|+|< zE!o5()n+f%RW8*xyluxKJz_7-!T<7f_8lmchMu+)(U6*@q-Z=S!|znGc_S?Q@uBQN ztL&Fk*)!=q@`y?0M>6i zigH=vibhII_X59`DQWE}>9HysIVck#l}*8TJZT?8u`_qz>KmM&%IRxdo7}TVagwt+ zac2o5jA9z?{5}UE8Z~Vi&(AcPMgB1OXEt&#zTOKUUWMT;=+vCDHu(AI zs=nm=)n??R8pt-sU*(_&ti8f&7P8(mq$Z=k8saNJp=g{U92{X-@mN2)_MJs4{h6O<0I~tvbEF){{0H;!;jWS zY&K_(Hr0-2g(+u2@84@QeiZtGvqbV-*u-8eMp$ypUKZ@2uzH;3=b+x1Tf^QpF>d`%p?XQC9Uj>Rv$cgSi#m53V3sSMUM432eRYUR7wb zopD-eeaLt7-&c-Jj9iFC5rES#ZjUElE2XfS8L&zRup8$nbuU#6Y8Q$> z{6u@eom>}A{^+1=q6yJpIq-vvM0qh+Ad*sEr(!C7Z?f>7>&e^NR-U=PoSyhqo{1tK zdeG|*#ub-+M2&&FzMF9$L*vlQRD0vv00JYCPOIZ+x`lr;S-&lD!+gTy=}?5RnA=!i zzM`8uuZ66E~(z#llO*jd2p=zk$C#o?pOvKp0=i;kRLToPeb|N zrVSGa??9A@HCfXch!`+E=#PHl@SVO#OBW9j$Lj#cQwNS9RU%z}?6 zC~Zk+CRhV~#-FSdYk8{l>$RL{@V2h_X!7@-ooK>`NgrwnwH7(k3(Xs~-g5d=ZSn0a z;W^>aBnZ9e=OSTEx;GZ{GGw`US#WkNRu<)%JV^yFu9E}|G#F+{ z4d+F^WEn2}!e&oD;T3H+Fp?qDHeQi4uA#TAhC=@vrT?Gp@W(0Q(fB@ zV^Xv#iT*|DD)Ijtr4yCeu@eU{+@f@7^dRObbL^l?+)R=0sw;rfmjWk?0p_37ykgY(Yql%(O!GAE+Z|Nos3dSz_T~X%dx-+er@tsYsY2i=T3CtazpJaBg(bA?SGY#|jAnPq#-<(-3Rjo1@7ZSv>bN-cHeG{`EN@ z9odTwd%}*zmHJbeb4!wz!JQmlkzP&;{C%2^kvBel4KN^rb72<*p!6dEr3(Wneg1mo zw0c6Gkgsjg;oB`r|Le4O?WZlsD$PZOM{UNjSGp8V3ri>7umC8%t$Yf8eo;R29Fh2( zpNpLkklG!quEuGC?_|U=+hbyM_F<`L>cNw_%b=_Y4hpJleHUdtT!)AMD}7T{l+m@d z_m`r#9+j{)!6Zu6oHLU}W-PXGs6Cg1D$iQ^l>n(-OBnwzMX#-U4^Z@|Td6(nBvJPH zMuXG>cC2T^uW0d-`0Bl>w25LlwIdMbt4dWK2BN+dp^t?2kTa?ZWpmgR5vhb#uCs~4jQ2JdSu-Iduml3yZGJ=fZ zy+M@rysN;)Ayc!XC>g{zs4qdMrc@B#NK=9+ZBp;Kfp(hokX$qJU`)9xS!5gGc1a+xMiZ_Qc=8*OSjc3dhlM|#^ibQ!` znL=r{inMmPmMaSd!uKmA%_hp?jx;I$mD|rT{v)?HZE+RZ@*8NCq9IX3pvtByUQii1 z8&aN&-#bbIxkW4|hom7v%xF4*z<}INn#C1^>A*x9^cabkLd7Xh+@iCLQX(HypYtg= zE&3OwcL-+)7sW6Iq2YSIVqy4YiZ#J+b^~WAcF*!~857<*!o4HDLH4>!g(plO8&b4I zJpf-LV+6o_7b6<>1xR3iKSG9$8N!^lf$?@7`CFnD0<=>2jwe8JM>Uvilv!fmSatkg zpH+&_)@W~Pc1;^D?JB#B}|6ECAOLC%2% zqPad~%}E))!Sy7CDfy{elH0H;*60wK#-nCt0s$C^u4e4WZ=_c}W1seBL}!?1mJC*OVPD)Qp^#nQd?IoMEvNuzkbgMWV;TyNSJ*HAL2P z)V&Y_#d}VaBq_MsypBUDWw(j1nVo2}Nln#L(uD5Wv`tM0RbVGLf_lf5<5IhTU9}Tm z`wWJIB)TvuVQFYoaUe7^5Ei8-9D?g|ib2jON8Gi)q^Sl3!=5Oh9f_+FS)O6AJDTuS z4odI#*x@aHrv3g*bSoTKLasKA{YVo`-m~4Jz+j<}{VJbqdlX4g&ln?9Jcu)u(CJSA zl82dWAf*C^!H|GKT!N-3cZVS~>V0(b+4e=v;A3wl-W8&gpUM=po8{_x7%;7=D1pNR zd^yGg3LjL2facd5yyUpneR!3`JX-aLzcqA!Hwk-Dbczf<$1i)rl~No6Xzcsm0Uy_T z*={xVYH#6nSQI1X3e_B97`6}p=tI}2YwB_!m|9pXfw_gt?NIn;i+#()BK5v~05Q8b zcB{+IuwR+pxa|Ap1iPKcvBo&_j?kor`@V`auYp-$SNP#_LITa6`15YoV^`Vep1~;` z<@PLcK~sI)^iH8si9>JQ5KeotkNZq_qoz_rSrymUnSXT0h}Qeyf2O)fR1x}A*z9(y zj)!OXs#Zcw=ALG!Ipb+Gzk^EqAAXKhk^LlpJOo5;YUOJC0@uXD#I!aFEeb|z9aorD zfXK}abbQmOH1U1_P`Tlxl`v|?Nfs{HraQmrEy5DKe^>cUmmD{oGh3?ME|IKWd{p++ zB91OQ+1|m@<#^`mY3=-owL&MUfY*g^`SsAFGOx#urNs|xcs}DV9d*CdH>jI*)7;M z-yz*PM-lcP;W9zx@ovtu)MVLM2Y>BK-CQn&U7xti|LNqc2q<$k&EG2TR~hHyH_|;; zfhTnp^gLC)S8D^Ix-B1mD<%za{88SYUN_~1`7$V^POdrp%Bl*o)F-?~0mvK)%|k-F z2tQRK393cShY9i4@54;P$)VxI>x$jZ&X5RaIu|e2J1|nQ2$ua@ogFM6!L1g-(G|g8 z9l>`I)+I)A3UGAWNc667dQ;ff4FcW8NFcd`31#>u7^)NyrDjSeng`R|rxU%1Qm2eQ zw2d+_jsCnCrQJn$3?kIci$3y>aAxvBgh$evvP)guag2*`7Kfe6Atj*^znfz)@5y-O zIb-?9{>){xGYv({i=7`;<1bSSpJ29ciIso`VA$ffBTSPnm@01IOwO?Q3~`_U6Q2_h zpT`+5>ci@az`q+2_n?dTI!(d8#1KH~W=CwbT?zI3Y_=!dC6tN6wygFbu(nTPS6-sX zNx+mH=WcFHeN3zX0LZ<-fT11?hejfkX4aGDE|LJ0zOWAoWlRo6B~EQ|hj7NqN8kYn z+|Mg%vMXuTG-a1EdBr7peIG8(kr*3CoROklzKXm3e8W$iBG-m;s#A74{*kUB~ow!l%Ai8U&Bpk7R#WP=ErIEAs~%HB7-GA zolEi2SIRW`4E7VXbk@rZ9M_ESs5@@uZr=hjVK;(e@GLBQP^N5mTGWYnpSVQ!Ly7#z zOjta7DTaF922g(i^Iv8fsHa95NcZJQPcKV1fWS<<>2&I`^nrF<9d?6%S~M)(V0RQW zsU+t|HP~G*)w4V2CuMGuTE-D?bB7JfQ*OoiK0&6)PZ zf@P?9KEH_7u?V&eW5%yMbdfZ43b}uYcvF%JK*7DxL_yi^8~J~6xW9Vpt(uN3kVF-j z@2FUvDPu6II5?=dJX7%)DfD7h^>t8%AyprXVA*Y`B3HCi%+$hP#;pcE;f*j&U{s$# z<`;CQ-b(2=f30*NK7;r-J_A5;zAawbH$y-`D^2XG$Y5T$#yME`_0NdHYLOAyi)3((gb6UNKV_^biCTDBDCLpuxA1 z6fjOxL5LofNfQ`n0W-u4_@MBIg3F}>CVC74@(%@<(+_0Bpsdgj@-gB1uFsr?hN)!W z6K%jvW_i27cPvd)=MZ(&Q6;XlldM#2DRrtfr2ACC__`b$AmFI-TLSKt1i0u?f2uR| ze+W3xn9-etkd|ObYoz`O&r|`al}s&Swf* z@&rOVI5jwI^|ee^>iK*kRxgz~GNUBA6VmR#hV(r~>z9P`x?xfL>CLdqp+&;c1Q#Gl zpr~hX=7s-b2SO5^ujUZPtdR zxHT2X03bZAS`yY#PLuKv1uwyjIeI!hQ6^J@emx11`Q(jYbjxjOh_rL7zQc6+ICizf zQ`#)S6_m)uCFVPvX^X7$s7>>bNKHb%cr^`4@`BPACNbT3HM7@(OkgQV8xUv^Mic__ zp*N|&uwuH*Gp&vmsLf0lCX5$mi4JDCAi*VtX%McAU}XQCDF)64+QT>Px(DTP8Y$b= z4?&#|6Z8k9Co><7#S#-|%!p_ItOl!;V1BfCF_qUW^C?3}f|KST8{^p4QF7oa=n?g= zC$yHSKw}0IwM~@!Bisi<9|A^H zi$+v4{p5TC$%2UmX8fqNMrl1pLznzzV*};ijxxw63E_>ga*c%;fH&_waw@{2kWNEj z6RET$d3dC+R3~|;C$~~eL`YAxoI_rD1%>U6sNQ4@xJ{%}3npB3gWcehS(dkgdtT9G zf#syzeP5i8rIFaZD={wql z)-ve!$mkkv>>_W({kS-juDLIpglmQg1;cYE((`AQ^Q)VLE3|Dt`sTOM=3ev8GI7qi z8O&imncaOmH5f1hdA@-4V)mQXIQZQH=*7ZK(E=G?8tIcozv>BlFOvMAxxrtS49N?W zztihzp>MR7$ikW9MdBW8EeUKfUDq!X-Tclb#2;m|TBg=#Ey`onEK3wMU;{ci213ik zo{OZ~vt}EXEciqK)h0o&izMS{NaHXGdHKk0MN($*WBr8YlVz@V#K|X|-3qrnyTE6) ze~W9_hxdL6w>jM^#S@Y(%LIODuXZ1vftIxwEo*~Is}Jbb%ZS%3U%XV4fmw&Fp}7jI zDVSxw9Y@Kx#0MgJr=~zBhVWjd=x-EZ^tNd2k#CLayEke)0LgZR;S^WTN}An9 zVqUd~NgERUh1oo@@9l(fKb5`zgik1C)}51xee01ocb6MVR%J@oElW0%NVOVDb!JL| z_(g8ba^ZIi^-C|UXuoTAtK+Hm9d66g6bl|G-2IXH z;G@C@lPu2;3`YgGQhKY_*b{#PPMY(|P}EmZqD@ieOtBXESkX6th5}#h*}wdH2V^4R zh-yNYlN}k6CrDLFQn%2t`^1`tsjk%jL|92(FH{|srvAH0oeF~I8~ZUpOryg0Uw(Z< ziq-1C?}eIANr}26Z>2&NG}8WV1ORM32V>I;<2|2UHS8rmrbeMC zys}oHq+yyzV1$B;?z5Oh@2w}s!gI6D{^8fg=1br$m;OU$9ZGH4FY8$Ft0pVpY5vjf zLq>?w$4Ax&?bctuTOW(s>|Jlm%dW9lzqRDc;l9Jc7Xua`x0g_~zY}8*!0Y}7{w$b- zqKSiQj)Uf^gD$z_6LS11`q9rJForSb&<^%+Fg&sYCwiB!AL){C1K!Bma7kl#&2n;` zMcy*(N!cTjcdu1nyw3SD*95d-RQNmZ5OjCAzxd(40`s7`e@n7wVmvO!lorPxFcY3B z-n@~Ye~o$UNkV*o+vGmrltU)&e+55y*Ux`e>2U?-Hhb{E^6rU?)(LThS2v)PR}m6G zyotpk$YZ_laJ<`Z_zJc;vzxz22@7OhJKyl};p+6^JNFUXn=4w;HGBWYIt&=*e&^2w z%R9eT^2bqu;Hg#lYH|2IO}Vgo`t|Y6rNW0-FEbwXG(D>M_6V_ewU~SnK)6&I_R6#K z%PVw$6r+FG*MldzeuD#F`}qQ*DEMPl;r&g&BnTeO4_qkSeE6Lc8(45ps8H;2p)-Fq z1aAa|-!$+W)8=G01@DD_@QH5ln1Aq8XYgX~&DDqCLxvxzgs`KHOBU0Gjll?XB7W)W zreY!ET8GI9EcdO^L~<^>5!3W-)ifs2Ydcc|TlE+YeP*NXC4^=!rtMhKLCda2?Df}o zpVv}9=$6P|etE;O!Z}c^9!swMdc}3Q;+I`Nt+=SIaozR(vbS!;buSyZ26Up$<`2!< zoVQ0c->iNy@AQ7zGF$tIceeM=>DJq60Hyc%VZ>VP&siac;|Osb-mV|pPo%TyR$9I~ zah%B)HEg&S_}*!uR5_L1>h+oH;!}P52F<*WHqDW?0=_cuyneX9>bCgM@m$K?v$lWm zgrMI-+-&P!>b6xwr^JW7&~l7B?_VDIcoupDr3;53PcCxi<{;3>Bvb>7W_Cy)EJoE1^5m&?b!4^&*<@VZ_b*7Cm zKGF4cYmJEEgzt}Q>R+|GEZ161cYI-!dzi&6)06epvRnDfE?s$bZmm_X?*Te*mzV8O z3}NBpY&>&&moyg5Vy*m^H`95-b$p6vHP#tV6=Qyg_iU@*t2IHho-Jr^u-gZBf;}|W zS{+h(KEKB|YyI}Yhw$^L_4Q9u%rf^Qr6;%6|9yV10-Rj%nLJ<#l@L zqtpFp5`pLGFBU)2y}Rde9lZ9#>^%I{^0~W||F@4=oF#$#WYzhP$DTuMf^hp)X+t8^ zDP|=|RaoevNniibiWRK9elo-`Nl@INJ``IXkiu-i=vn_wM z;;#6j>cGJRQuUpr%>>ca0A+0fkASi#ezW@|)8aq(w>3CBI*4%vdNjs)0>u*`4kFP; zCD;y|lWA&Zf2pYD2^t0=B2A5%4ZZ-vG(<*>LgY--ka&pTBd-xVJr8*dKr_yv&((T3 zGs(eXug%y1+dG+t3sb0d+HeUcvqT+jF~LW@mZxQZ#g!*;kI1p$^7gxQetxkjuRpkK zB{z)E#Um}FBB(U;AvB2at zB!B&hhzl3XkFh!+2vjdnY#wL{5Y%z8kO_|9IX9qjQ4p+!X~(QdtS`F zA}l^vu2}PByA0Kqc0P9(QmSPnIeRD*F74(v{R$=cZonXp-8u_J{Sl$~pdvN|$q41~ zLecdJMq%`AU_nfWM1?kyIKHLs8%OcKRJJ&_Y{9{p;2hHs|If+uLp_trTQJ|3uuwJqYmRf?o}9rsn3aX9&*OJw$k zbj+4H19RzXcG9^)9@l!ZbTX?tbyozfn~@+}(7Iy$6l}EaVvvw+NsMZI)&WTyNwcR? zKCnz@B4UC~kMKC>wc}ifx0d2K9*ga}F42t*E=@GkYN0OzCD26y(qcbZaB{_${C9rS=y$i(a6`KGI&Ljjf@!^=5;h zvuj-Y>w~qexBEJsJ@Zxx2K7JReY!8!_aXUktLx%@U5aPqXK#DsyssZXH)6b6Vn@hA zoiMO&*AUrtV%5U-3PEgu;qKyILiyFadTs380HqHpL2tKzf?ki`rSHi&k7QuF*~^%` zH|LXu@%)ujjKouc``?0NjhN}@y5}Czrv@DU+LQYxu@GDQu=K7Y{Zw(llIMH7%1pD} zjQbk%@xtG$w0<+2&iAZ#&V6qP`u!O}*tA055Fzj&qgzU?IV zZq!Km!QWr9;&Y03|C;nShJwUocwN`JyC zp{}6^L{vB&R#4&7-{|h1u2$LNIg4X3RQ{*D-l-e1g8^i5{D0q^q(>z2K4ZFi3q(}b zo`!t8Q5k*upl9dld)8CkFxbUyd42xo`X_MACNJ#k9?cQtF_<&#c%1gfI0S7SNpOL@ z7DGaWX!|KbVXJN~pm3nPj?WpMuaATVAkW5wNy5Wv10py%k@POiY6ju_>!kYW;Pc!F zkqab`i#YI5Z4eC$f&{Zr61R~-&5p3#^&(Z)!#xGL-urXqw{Sffii}c?@LMB#rf^Su z^A>O71w>g^hm8`el~?l44CyasAZ0kCLm*MFhxj-?;t!!9PF@kp6VRk4uyR~j641l0 z4tvxE4!j6^m=~6X4o6pngCpToO>q2HLE>#eiknQqq;)|u3?b%BMfOOc_-(Ds&iEoq z+m$o2K_ni~-Vk0#{%voN{%LREmY_$XApamvK>BS0bIB!&rlG5NL9b$Ojo-FvjNr6H z8x}D;#XC-scYu89ePM`Kd6KWaSU|a0@U|F|MLbkVHWw+LKmz@w{CLMSC2LvDmMEdz zGy#qB*7h|{L&pJZ-;#f9Uy-Ii4Gti^0Ym!-a)OdB*U-Q~WV-|dJP8#+VWL>1862c1 z%B5$vr59LaOyy+wd~Y|=z`J!b(%gAaOWG*tw!E&cw0DZ-hky`LqH`etB^49r=5Jfu z3&W`d)Vcnj!O?=iO0&vnt;9%30MU zePzE!5_?hQ`e5hsn&ryxWJqIXYQI7PZEdX@OrUq|hsp%9){;#i+zmkQg=l-BnZWac z<6IJ=6Uk>l z^@ZOPk|*)aE=*`58-%CqUKE;!r`UyK+ru)24{}35c@Yv&0IEt!KUX zc)s6JY>qlK(hI5rLyNpgLvO&;Y*Etez{Fw7((GN)q{Y^%lF;(r78^GLO^)R3o{|+@ z(C;kpHEi7L43zL~CxflVv+7%jkQ9z~=c4VSP1F<#y~;H4LURXnWyG!I+qe!%T1Ztv z0OC28jyl>c+=QVsm6ZO#!~tIEzjk#0=ZTI5Jki@#iAH5eKB5C_Z6-U|dp{#3N3yZ<1~ zCXg`#LeFUIh%z?$PHxt2eCwx)nm9hfcFs0&e`)gIBYD^j>|Tf2J*5A$JjNjVG+AB@ zqODG6V@i^T(ZsHZA_5moZCIw8SE7(BrZK2Y5?)zOo%t7Ny1PN~sS;&gVQ|BrjMb2{ z2a><&A%FMCyr9&)UYU%ZEn*d9ZH6S?-FqfWS;6ASF`YnMa)tB_2ungfUm3r3dm|;F zOh=_LC73U%GG9#zPncL9F9}byiFr+_TyyHU8}2@Y@rI`5_c$aLm31QtOxVjT zwA+acV@DT#c6|C`w1S4hN>-V$UHIl9`F z^0RBi&tsCji^#T}Gre(EwVfO-XsrfZ{Ik1+rl(A*=b1%MWgQquXP<%}oOJYjTkUBG zy8k_=hM6r2@OkeBbZFkeA6n?`|JjS8!EFZhjac*zS@cbO?j5J`9y#_Jk@wEBu%6*= z)T--!sYy()Li_3`>6^N4*XriB8(gyGgzgVKoypGqh5=oq;lA@UovVf2mzB^Qw=O2r zf$q9~Zz2Dy-T}lO;VXlh;-LX_X%wa<3XDgj@7>Mdjl#q87l8I}a}ag7cN``5fBf0M zmpFLfKCqB5K=W%L%3_c@c#vLeh*oRhJZQ+DCn84*tU=UI?Ou`z87A**=AtF)NbIEv z9uA6qmT4atk2#X3`hvw{n67?kjfaRasa>Rqf_<}Q%pWGYNGvEgYQkG0i8r_?6@XoY zHNP^VOhjgAn_?kI?i4&`bBq$j8#4}S<{KV+Hb@Z~P8pX@*|;W^FZriRXTO+P4h$}5lv$Kctt-OIIn?`dpZ z*qJD|v{6W=y%Hwc7U3($sm;M@)wk2?TG83&HQMIGf>g{uvtZLB8E73@Mibw=ay*~=mC2d{(P4LXO(e5 z#hoLdj3ao91If%8Wycv;#`){lj5U~R`*{9k{Q}(+ha)`;a7b?+49_Q>xv&;mmBC%N z#Em-QZWiKcU$Qv5F>p>f;TdE`d>CA;{WZrf9r69~?5AN0Hcxym1%tO+d>hRCC^`P! z2>wq){8tC}j*f+^*@JBm)HA@$eFat+05x z@ZzHIT@n#RLlM9Sl(eMNz;;HXj!KfQH9|TBZt~^wPS`XUvPOd?v|t^^yox zE(}NJT9rx^v8-w1Rw6&)yv~(c|Iq0*0lHH^Z@*9G0mgwGa+Yxu-m&|kI9<;vU zbkr8Vx%G;jo|4lA$tWB9Dx0+_o1ZEZz*Ovgr)=m36Zk#|U=aRR7Z&t3Xbx0L&zdnd zP~Y}k-WXIb#?&ZN(uhN794R4=o{z@87^Z)>3_pptS$y+5`8Lo!Q8c}ShP9=nvm2%J zX+-BMrtS&p?D30HcdZYk;VVW72HeWdAMgx?lnw7h8%kD8%X~MK7d29TWTf7H%Vasf zZg5?1+|?%H!Y@INIz~B~#)(b=Ko*@IKMQ7-X>10d*x4qtC*RGK`?t9HCwQ%i%u%#Zem4MhWlRNa#x)l(tg6LL> z-xI-i4_*t}i~8BurP)iZ+F#>3ptr3hb$oLC`YHa)^2i}hB-qhl*U2Qt(R|F2(rOMR zyL%eqG*;}2YI5@I5C*=b30)sv|JutZ_^M3n5=k!igpV#Z$0g;y^WyujHlD6|9dz4I zM2ah2`scoG$$t529oERs+y+2iFnwo^`^UF?c{;mGI)^`!@fJHg2~s^@1F;a3`=mC< z8y(*^Lywk+9w-1O5#bJ7wGR`Q>$T zRs8Kl)mM7&p)`Z9?9Hi^v#+G?Y$N|6EagnSfykKS(Xrek-MjA$?tVA2`EC;S-SqAo z@y4UU_n)ioUfUeo4aGy%(1Bxf_zXaW>a zk5@EbHdys2B+ycwu&Af^YQ4|^C$qKX!tJMuBu)*e94JYwXUx1fbe~Fu+bP`{%dRzM z$mBMzt)rB*(#$LX3P|~y1*Wb2eP@;A^RajXI(R>gU6fmifvm}QqP#bqUYo?j{>j;m zT_`oQ#IerC4+BVy^N*j54^4+-@ynCD)=SMhdN=0K??`-g2)YX0obA2Wv^O8w%Y5G> z{ZMyVUf({L_QbR8VN>~wX#6+UFTCn~MqlFgbRvezjh~Y7r?=^B5t)7Vk@{>Y8+>ut zoeX|)^&;qzzyFWfCj$2@28VfWRK6~VE8L5UX!i@!dD{PZPDNsG?OV^lW4d<#4~?o? z%oYu4hU9@2@fD%4b6_8}CRPQ1>R zEAkft7KCFAaq7cqxc{AzaF7f5>Okm_};Qf8Cog1`}5xX9MN^B4a? z-oiXacA)B9Nv2ldR$DV8k^&n_ypJLs))x4A#FD~Y3&Tj}ZMyKeml<_+ zgQbks=7)wa_N-p+Gi(+mUx%S@xqYuPpPN_>j>}$m*fMkH^I_|P3eT6;LCi8}a{)6( zd{xP{n)cn_;*qs=EfZUx;=%^K<{w^5?qK;d{>wvM1tFPMj8WH%M53(uY@ zM-7ZIOMO{G#PZ!~dujc%X@s)S(sQ1;==BrT+S^=n>Dg}Xb#Ke2wUpEnP7{$`3 zNvrF!)UFkA94VBMdBkr{=kME&>F~bq(n#`n!0?WOl*eV5K$HwKeG)A$jzx!pTI95o z<2CCt1#Q0DWJT_9`eg;M_H?b}anUK?6>+mYN#tGLd`Kudd^C*9GqRqE%cC+*htEcU zgpJ@3yxp9ILHdq{&@?SG<1xs5nLKRr5Oyqy{FP2YU$&2xwy!?5R>$#tR7oKm+d?=~ z$Q~l;twq)-G%yFjiUF7>e^Rk%4u3c5v8TsuFdL*1(vEb1fFA0PZLY^S28)wJpc_Ar zSCP$N{pWm~ifqbx!USqc_}zZYogUk47cWH`x!^I)n=WqxPrMX?4Ctu&`+KyonE3cf z^~g0n$ApLL!}RioF`N-2grS(v6DOOP-bybM*PX$sok;Dn%b2?QK^WrnI~$PF{k-;_ zF^V3Sl%P-Esm9gl)R)z{&EmJL+i|Xy_>r!;@>YoHdpU`rFEV zW)4_nKJ`C#Fd*CJ&DD#Ys`VL+s8*2SHprb?sl2kMr^PbVEz@{;v@*=Fc2H*0+4C|a zE1}L3E7$brAYtQ@GjIyh-Aprj`N^l15NhoTc}(@So>fu$(Syrn$wc&9XK?Bs`HPQyxDpuvFTTHkAJk5;6Ha!)qFjU)6o<~ zwrio^YyMK!ptkDK7lcKf_V7&y@CcdpH1_kz*NsWSSgy!!<9z!Udwxc}rcVObIr{sw+yz_{l7lY;ZUeX>S5KN`!e6B8y^%3-QG zRP5F3^Ztw3#?bTW57)2P*k5^mQ)!>pcW_|BKmFbqK{2!OTgayiBlN*TeES8F6@M7z zTvLqL%qBt5#W3UCQRF*~tyeh#qL6;CxO+2O?wlox|T$dWrL&QbH@p`()Lu- z9pjw+%_*^34tN-bD4%GrjE}i?vZR-j{9ot8?x@*sn!I`;p%05}If@pQyPTHNSxhkZ z{Rm~bY$D5ln!L0JmC75unM9B9Oa$_nzIxH5 zoFFUbXnKsifs=;26$$4@p1C`-V`3*kLf@{##w=kG7(&gY!& zfTh+;N14>ZnV_YZSa?IQ;C&SSObN*RycEa8G!nf45oay10S>tki;*fpS6nY86wO0Y zr6ImWBya9;-spBu=@x&>+4lIbKBP1t+P?)`Vcgc}LQ);39Z|v*Ah@PTh%X|R-4cZ( zy@!G2n|6$61KN&k!XPGxJ+j}%CMi{WnyfMLow(uK=LBdpPsFCEW1)=sOlaQBZ~UXe zbEZ8h1|^6c@<{&KV>H3)A@uqM2%Z4)z%TA>8Cx(;k&+2LQcZr7v0dNi*(BNb?W?uX zIel%_69ebMz(ko1zpfjLO_MGZHJ&Y$L=OS0|BAsfXNOBug`yxBjD?1anUxHo(1|wozIM-G9khxDSF!WE7LU?-{oE{JhtB3qhHuVDW5@Gtlrd`5t;k@xT5@c zoiMdG9=OT5-#7t#_{Wk>A&~tCe-XvDGxT`Z4QCC5JI&uCR-U>o=&lKDwM5Ab<>RUs z?CfV@#;(8-;Dfnt7vpD}Bp3PP5mbEQVI&OwxieIw$^Y6U@}+91pIng67(~NYTrCD~ zdDJJ5G2*e4lMN9b1Z*w1Z^xYz?GG)$bINPYOQ+JF;aRfwsDR*!g zBOx=f0s6;2KWuMN1kzU|*0!lL*4Y%e?FpO94J(Dkp|O#Bt9kgE#?p;JVs928RC}>W zUeV4_dSVpSl}%NfU9ZyD`{AIPP8Xa(5zJkK;=z6D)1Di90O8*2i@{-6SS zwU^SZ? zNZfIr%#sguRHsUMrIsawW`Le&?Tc+~<)3TepZmBb*P<#%(aec5A1k&sn^Hdd{Gp%S zIXs~|k8nHJ#x?N`XI{!>&fR7rI$L56dE&f4VyaQOlQDx&nsG_T7<_eJstBoq(zDLy*rEE(!|9q6P}LEk~M z^)ab3~Q)|^153G^hZn#6ow4+5e&>rn)|OP zeq9zr08Vfe0U^g()yr^XRvdoei0mxhd4s*ii(*yL*+SRA~-b)ilYvSzNrzEf`TzwkhXg`Dd+^XgRUb0 z*gxUA&&2)Aj=Q>yyS_|!T!II%&bnK8UfcjQVu%9pqqMk7+yh}dFuVkq4LV81TC|TV zg6~u0BlaDEsS>`oOXVMk`M0+K;ix*il>cyx|A_fEczQvMb#)>s*L9A|B<$FP)_EoNFO;yTiBZ^TX%U)trLT+hTq8LN6Oi{8jQnD69{P{LTW?v1R0ZMyNWn|pU@!tV;X z6aH+)`K?$4%4&sOQQXSfB6xpqlL4T^1E)EQyP2rY@fOiP!{KUE(AfHe=vh@}s44$? zY4RdU%DzgPZf)v`Z9Fu}hR<;R!VjD8%D-SLm=-EdBNVP#oE|%!_Ma1{C-hUjRRdS( zuTZL?m}pRjbY1YI4IIsxQCmHRV zP3-=VX91i)<9&(ZYB%8hXdo&&NUCLcUN=zJ+y8CHuzJZz-O)&^!iZjaNQ>;hQjPx~ z6~}N82!sbB2HxfIph>3Q%|?WLz!6SHD_AZ{J`74`jtfFX4aQxQ8(_jdA%>Fh(Bcl& zzHTd|bE<17#V=(KX7Va?XLL2&7Vx^7{D7{&w-o}=gGfn1jHd8=tK{UqSOVA}%4-x^ zRs!CE2nek{zpe6R!CfK{TprUbqmCz6ov4-!Dq;jrqDW`bygIT~NcwMTfCPRWK8ObR zb+xyRNpTpxbOaQOS}V?rX*dFM+n8LmRZbwJvt!*)D-ykT4fj(5Ga9MIq069i1`0VS zrJ|V>NUtxx*2pKpGH(scWRr+I^7~#cljzB9ZcB>we1RKcu*;iwV-Yp6St2pCeOqk03Ia zqQ*0`OPQeEcBEXimHUr{6$XNWtUytJpvP?@^Uu#GArPr<+8l}`pb<){_9|0MB*j&j zsZM86NTQc#C(Tc1Rn8>Rw|>i+?x>baYus_RF32{MA*Q?ArhZyFSB&mx^0u3}`fdya zpWAAxt!BAOuUzAa1{u9X_)e|cyQy{_BcD}Ed{J&+*2R1|stiRii4)KBaEa^ltN+uI6pMQd-SoyucR6vFzjBmxXQL#@p@3o>6J{ zv>hy0V$@i_Fi&S*ZL;qSeID5Qro5O4JYBZm@?feV4Vyuyuj_bw*!=-{VfG=V@(ZIc zuPf?+MCQvX`_BI972vD~O7Vt9x4-Z5bPw|2eZ9*AT?c#18gtPX{UJYl5=xgJ4fJza z&9k!5KgJ{%FMb@DpQb|Y3fk46ZqS0v0J~mOCc%r_g)8}=g=-Y&Rvc@NOIcjNcNi4J ze36+gdJpY8BC6L&92YKT9#AfG^=qj#ma&*~CykSNIZ_TCv@s$x__Js+$R!u0` zn_2#@S%vN7Crm%$B!#4tJQZEVQ@s_nhL~diBWi8lidr8H0vh52GN3NEqSj=*`rkUEo2LoYOEoN zDAMBla-Qe={JcN6>vmn&_cwol*JGZ?@pv4^olPAv1`|@(K=vq!8}h!sm~nV2)q8j< zg)K^xJ3N(2d!hIO!#>=V4f*F(iZAxxQz9J(#Rowlk-{oB@nf?D| zx$Fl2`rC0a+KT3!-0JHtt=W$w)T2^W6j4D-JN2oM9=12NWMgLS{%!`O7(5_Yn{gOj zBH5S)mQCO}Znsm!-F0}$t7u}9hf!0vh$yG?gZ_!#Y9iqCs==YJ?Z z1^z_l$5D!$+Bor%z;w(3Zm#fSOXr2nuD-eMvAY~|vBDDTW`8J@zk2{To&L%(5=Q>c zF^+a2f8%1R1!B+R<`jm0`^-endDzC*4P);xMuJ4r!*?QkqUW=}I^R z?~L$a42s9Z5p2CKjvjxRDb;|P(%6HzT3eGdFeCmgj$4EQsvvNOQ1PV9`6MU9LY7cL z){Tf#b~S^lfA4el#%&Mpb1GP|!RgRE{sQy)cHX}ol>E$p92DEde1!a6uOTk69L&Y4 zQJJ#=DVA72QngdhvG3-YHlkUX?}Wxx#JNn^tUvNSSCzzZ?H;81CSzjLQtzsyg1Pq^ z=goOA67f5XsReR=q*?AteyG5W!NzDc`xu40&=x!-pc{6Fu zPlj0ZEVVM3BAwM#8_f~lf6wId-~z-aWaeKN{M$)WE8P zw3ANk4%$BJ6&H!*sCGJ#p6T4UUK<+EZv5vwcP}`;?rieEriZaXwdp!3PkC!evY5ly zk&r4lnxPz7Y5dufe4%5Z^?TF9S3YLH7&3?l)Q#qrVP8LY8RT9Pi&8sBjep{)Sr+qL ziB0`3G+?ZqRNZYIO|J>Qd+Obq!)|Mv4ohtQm3JHAyH6&U9>|~P-m~vubL`QQFZj z&$uoBU{zqq0m_Gq71kAIB&`6U*Uc#S`e+=xo%8DmS`^$P@;!ThI5LFT32ygY9-#eD z?Yg5BNp@O4DSG;K>KQzFuc$Sl42NUZxa10pU8tRhN*!DtEQC!)a8o9*B#;F^X0s*{X$ z@fs%v?F*Db^KBTf<9*|#(;QietQvP7AQ^?L8CDa#8gmTZG4jxhW?&xHyrhNA-}NnR zX`o+02<<(01cnKt634ZZzS{nYuo|NOBjQKmoVO!weD5;2wY_uaed|@H$WPNR63IKxQw^R>%jTwYZXVjtpP2r+ zmh=4Qy2l)B=;GBa)3RB(-nq`UZ>$mLuSGI%e;1|G9~}tvn*RA(^f$_C+VsyCvYS9> z(qAYmyY84jz!lc-?U&bneEINOckUE}lnqfV3MA#wuj8OlCw2dl17wfOC&r!&Kv@B~ z-hBIs%Te%_Xs?cFV#*q;@*x4f8Z2h;fi0$I)kGdna zGr}N@sKs9VqS#p^Mr^`W3|%IMU^bV+S_1e<y#+oG49CKK)^$aXqq-qFumpz)z932JipmPOAUm8;YNEmXj>*d~+Ar4hxCoV%u z3fNcQ=f=23sl3!qEt}`Z^q%}Ve&PTOW5DB>gK*FW97lNqiA!EwL7pc_egMr8U7Hr( za47}J-|xgh_eZe<&9#6|#EVIlE66S=;KcDqniA9+@S59rZM>pRkfInECPT*nyX(Q8 zw6LzrWm}3A0Lp+%J>*$;rP`G%IWDlDoye3>P<9Ve_HI!2-Bu1jF$XOW6B!VIGUXY# z{Fuk5YwqMQUM&6~NOix_{Jk9}gHbJ;e2Bt;)6@VIhIl=9Ni+;+K!HKCO3l@;brB9T zOI_x{UYLK{w=5Og7eIh|JW1g##R%1CyTER!tEeT9cIEV-IsOJ)e~5+5l@lLW3CHER9d)?!d3G6xf(l#{Sr`N&n|SY;k>E(^e@67Bpa^=rPE z1TdTCIso&!t5yfd_&O`< zx>J&9Qs~1|E+)x;8ZUG8$|iFp=Jlem`f+ahNfr8Oissi3_)mF@8hkBf`%6X|od0I< z3uzc&Z+M3rb1zQ5n)}wE+N}X5u?DMCIZ1~y@(Qz2XQS~2w8e`8BtT&vS~MEP8vl)* zN@cVFTPN`=i7i-}?B|+%nl$;cm~vCXl3~*qLs2RVZ7-7H+ z&U1sWkaon`9Q{g+V1O67p1><$`da1eQFjY+jD>WMh1k3WPN@vL#^>#9smfzzvu|mm zZe`eX+p*9Jv}bjE!P?=H^_eDXm+(7hzTI}cgt+kPPQa@>?$)*eI<~=YY(jbv0#e8X z0IuD1H}=)tt8Z+RZkMMlab~LPql)o2ks$ucWSVhwVp;pkwY_kGgsNo@l;p zQTV;w6vsCz_wwG{J2uB=h5Wx$tzLjG3E@6HerRWG;rG9 z5^EYAWca4vMiSl~Pi5eFoFIQ9(vcemocORU>yg{1)Z=LS$bTl`2!d ze%vUxuXJljxor?{*xBs9z3ejvzW?HhNQs25P>5443c};zmL~sE^0pI#{f^INe21*Y z?7iakAEj-=1O-94r#Birq8B~fFf zE-rSQdDyF2JDf2#kwf;`vQ`MbJ07WbaHOT&cyMG={-k%brhoY71-@JUL4TAY67#F7 zLav=V@9m!-CMgmscq|Ak_j8r!+%->V91IGP?-~V!mHwO|uZL=>*5D8lif{7%TUgB} z^buRq<0KU*a34A4NoQonQnjLQn@w`uYjcEBl+vFFW5=AxW zt-@96;-W4X-+5dy0rRx7{>AafhlNQ?8L2g`f3-4=wzChn?Mz+x@^lxT%M70+<$B&J z$PTvUA|(~jDr+k12U}n&v$e;2XyvJ0u%faT_<{b^FTFx9Kjk|HKTW=?6n@K_Bw$}r zXYQc*c)OTbQ!zK#(mn}nlT}c$FO{nIV(_>M5Nddo!soj)rjIfzd#Z`N9m~WDfEYymzUkp)$@TPMK;FwhoXy%F&d*XX=3bl zd!3$yYS%HES+qq6gTSn451XM{oJV_UEy#7M z9(v9rjgB^pYsig;=@S&Pk5}kw?a!qdUA5q?-&u&5=FFYWAwT^7|0+N zZkI4NPh>i`ebQaR$9wU{i>1u+ubHspxPydedlMzcx|k%!M;UQTb5Ihplfajf>}f1+ z^E9ewcsvUTkzn{nnU<7Or;Pts!f2uRKM5n`XEydTQar`BZGOnX$M(b9b z_AAvIWnk5Bk0sz1P~8#t<@w@`+)uKXaXqmAb{HD+ z%JKE*b|3ri&`^i{zoDVJsj_^s)UuND=Y@vG zIsogthm{v#wXf#l_=_h{`M-GrQB$Zl`YdB~`jLkN<;n@aYC?wtgfboFQ+q9^6kQSk zFn~+Q9smYN=m5X~G3f98%rZ67uf5DV8AKtSY(oC57h!Xt6QLapdH(4}sxN^SJt}ZC#wk{e;PPzP ziHwY)n|rFpa*%1Uago!aN8{sSTZI|#$26ZpDQ+^+BJeO(~n#Vz~(IEKRGjG^MY3II$o#_{O{}KJ7dnAnGQn z7b&(~zsWZp$LlLJF;zB3FFo!w+jl$78Kh6GPp?QQ#7_@r9V-9cUi-Gw zE9~J2p$|VszCTO88P5af3+fitl~3UeDC6KejMzMrtlY5vgta;h)99Lf%`R#~#vCBK z2lPx1(=*LLC3E8NP?NZe56@AnFVZrU9@V$kQ zs~L|cZ$CqvGKpN97z)?gGrzRq4a8eMK0bQmY{6y(>!g?Il{X8Vh$bN3;&biE?3l>q zdb0P7gY%WMJ0`qO7goMRA=>DyrhA=&w>Co_W%lu9?Dpa#w^S2@`h?Gg(9O=8t6N=;1s#~A;7y$i%!#Gn5t`j8}JA@NH)^x$vyK;K`&>gUWkjBayD!bZsf zzz9Cro46Q~@6N#S6^OTdFg7v?$~%hu2xV3Y`g>?EAk;br__g4=d+Iqu=*IVv(_Q*4PWg z*rks#D|f>RZk$eOl>IwUvax&h|0hu56#sXig#K5c#0ChI)W(-RN}$==jVQ$FgvNd3 z`@f5ch$ehE_)ktGtskOD56{m}Htew)iGd!Hd6Cz|0FW&4f7jhm7(Akw3PaNh`;&*~lW9^ZO0}s#eT|0`>wWb9s;}X5VxH_jtgm^p z7s*FUjdfxL>T6c}6E?hL{q|&sfJXrz_~i`im}+|LD1$sLC6*rdFCF#$BhaE3t9oe0 z|Mbw%W3?(zkCZ`5^fNzxWZ2EmEbUME@5F^Ukhs7l{hhdYs{fzFMO3!iW{A)k#E1+k#J~DrYE%BF8=cOe)~axn$wf?A z+8dPn#goYxlyaULU+M)M8e{{B6<=t|--iZP-jE#C`05!(X;JF1YMy$5mOW0Izu?-5 znaDJHe%ED4wm~{@Rv_mMF(guHd-=Nd*F%Uo(0GQ{~ZQLk4Viy725t!ct z>A#L%4rM&97Qqx6U3`~kT`~eV9}u&MdYNVJN71wFq8m5RzjmTOszuu)VKd1^ z>&h&BKW?$9N;_DFWvigNT&d@gG_X32$$`caPJhDYt&mwoyXdSb2YB8!#%DDAS!EfvF( zUtv0~$P#p5C{du|+DC+}I?D;nA$#Dc0{V^Ry)}*Nm2s`U^zfojbT)i?JF-igbQuS&6OSh6mz`r@$t`Jk8h33 zm!+IQGgxV=W53;qTujzsBg@yY0l~1U&w=v%_^M{Z6M`ML@3Ki=H$*y-@zi*{dKdnC z$i?4H0V1XMl^&OvrJPn^tX2Y|UU=nps`3R!*SPl&xBw{IP%5|BEu;|fpO##ynTn(SZg*Eu& z4mCRy%+*TQGgtQzQQ=m75wE9XsdqV7Pfw6zMi*dYHp<86sb7}`j{H3s;)Xl*GnfpN z@CJXY_M5@}eTIg2;tZ?04G%L_ii*G%Uq$cmy#E*0)c8|lN&(9NH-Kk`3|7hll0B3i zHl}F&%If^HDC4M9#dkZ#bBZQQ!6vJXCg!6iZuEo5F<=(vVrIHey6x=`=uw-zx!Nrjm(TQ`VXTk*8@9H_VHm0LS5 zSvwuIsdck~N79 zT{XYEYAd^W-7k0EM&Y)bgj6bYOX=Xd5ZiXm5y<-ls9kBAb`$qYpa;(oFi{A)~@3^k-nslA7 zV~<~Tag`$;fN*AE{T4$y8;oA$86iJ^_hZ_{Z`)jq@EJ^qxSc$A5wwf%{As{qTYcNd zw`=`AM;|*6p9-f+z&jxhz8e9JjxQ=_hiEY{u}6XHX@Sx}XNq1BP?&_jF&teQRFE62 zBN?pk8NC1NrM~A&%3QG764EZHGswjW?!gL5X$7SBgvGcZ< z??&`D!#E=(dBtvC-r6iT3?G-eIWGNl9D^CV_vZfU#2C)EQiIQN;*rmUeENuD`TRxYtp19N}lk_olI$D=}K?QU_3PvA!S7?;%Hn<_Zjdh zBHWfw9FJJ>V}<7<&)hU?PJcZ?Q@R}!SD8k}W?98ytyXF)?H+;SE8R7a{Av1MgJXE! zDp$Ab3!c;#Mpx=truipIgiJ3aTPAA0$?tk|ENb6dfK^Kg@4Qks+&{kws9XQ$z$5M&kQCT?DfJBy5IcypfL|8Fu{egciNzytY zc7xZUg%c_#%yV_ksD`P`F5uSydDfDOvULpJuGc^j(llatEdH6^Hk%p^;-YFwh?zI=9*q(d-$m=AMEL@f>*GxJSNkQE&%RN-!jo zEm?m#-#6${wtK&+kiX9pyCA1Z!DFt4<*$Q831u6%e3H5w?%qc^7!KlDA4+M5S~Cw$ zbc$s^zr+8aV-)jpVY1rhx3lnyi3kEd6t4bStl@z}qs=sz;v+^q$1K(x1#b;3FFJHF zdI!)k*|V_YFl)?lb$gm17rQ*mnpjS$d0VbFyPIw&hh4ek8cgKaV-NS}X!=z-^jh4I zZ}kLQ#OF-@L_!19gOf|`k>C42amYU3VBK5yC|aMM70Dx(sn)T>NlaY#cmJ-_`C|Gy zSs(w-x<_$AylZQ7AxC95^sV`frr}A-t*mRAl0(oq7M}gO$v1|-%saYF2=3ZF=UGhp z&hfH4V5zZ40lvAbtY_`SOdV9sX$j0(3#MEtm6xm&|Clk*;{Qz6btTETxVa!eEoJk5 zvVT0`Y3uIIrDO>S1`w!pJxPf0t;6fRr1Y!3(R4k`JxpM~PaloAG1*s|{)cYop8ejP z+bxQ3ZD%Rh*(S^_dofsFhgWeQHOSJqhZ4?oN?DaCM%xEDJ-OGZ!y`lG()I&S9N z;*bNfirpfwTIo*(Lli`VZCb>xhRNd7)0&8SD646H1N0eaT7XR=KTe@m2~ja;+PvGD z)5}px;Tn)EnGs0B*t=EB^H3K9I#FS&m0SIS7iMEK7AxdG7MIfINFt6>dI}O)PosE2 zKmYbv1?uE_5U+tMgp3H1<}%_v?$oKnu8^5#&<@6v*`cY&%3+fXD4~=GHBR+iaMURw zBit@aP@Vo)+_nwxHT!rYULr@cyY{y!>`O_x!;f{S*xLrFdbVT~9R9?_yGDPbrOOC= zQH;_*(Jx9?mhi?hFv9Gm8q#NzJ-Ov#IBwtUL#g^Z?XLfSq;#G{s37@vDmoQA( zB)O{v#8B9U2%&VZj?zjhVbP~>NAI{90&O)r>eBq+ytT&=CV-PvrbnwVJmM0g-8p>M z_v`rMQ!hej8XkW7{`C3dKBNm)eoQVB53AIIHm2&W1!rcJJxo>Cojfkn?xQp?zCo<2 zZaRZt?+I8?gl^oHJV)5 z@cSONP1vMr@FxeMlprP-VJn31TKoEV7^NUy%B;~53yTF%;3z@hjA~_~WcSgkvir9` zm1fCLji3JhNgvj{xHV1aaFUF0nK5=Avl*3?q)(agYP4ALM87_UeF-Nl<{{Pa{_+Kv zSqdo@q^<^O z25v(zHu&Un;6-IhhEk0qw1VVqs7qBUp0i&dFNy*?gR5u5mWdse26x;ysZ_mlVbYC~ zpw#_)MQ^`KV1}FjetTR2vdlSv>sTy0 z(bm5sN(BKUtsk8t6?5WAKk7@Eqzb=3?yB3O3~ewU0_sG`+@d5IUGT%}JY$ z?dSaLY*N!?ri>VgpedYz4#MiexXuVVScZFmr9(ZXQYlP)Ark^}QfMZ67uTi*USjSF zCV$r4dQaxeBEwsavL<^&uSE{zK4M$aR&`Zc`rSIuk{&>oO8D(QAtBUAJk`w6v3)|p zNvwfP}o&vbxLmRN60|YMBPVeBYdp84XWf-qig0 zvu&6I3eQrvc%QoCMH6(*AFxcbyY>0)BMrVh&kX)dj!TR2j>cijXQWlWB9(Vr%b$$> zS@fN6cMY1T2)DH?C|$2rdpp8<@*uNYyTADp>%{$YFP}aAxx4<`_n_No+jDR8T}O^y zYTOUsJ!UC-)R!MQz0em@9wk>{t!4Uldy(JIUr!7VSan~{49=Rv3eS(lvj~hp&6wme{1E6epq2ND;FWN617~woJ9unwP{k| z3UcpztPx)862z-M`ijO#4?k+4SyWY1OsGn zmWOxu`OOib%8B0O&!TUu$SJu4)eWN-$yL|!QmpTr*oIqu$)!}M=^*;PbPZ{02sz-< zSz1JWyf48GgEkWRE;eRM$E7{h{C*+#E{7Q7<|E*y^F$$sc=5wd#w;?*2tHAte;B^) z?>?nw_2nrnF=>cnK;4jp^=m0j!ce6wCfk@3?T)8^dd9>J!t4~AJ@Fai$~BwITgT{3 z&WGPnVgNbC7^Q%W*yTu2o+KH^%kPv-&nC)+kUN6YVLSv%{eZ-oL`)sYF_DXH5~Tkb zp-}4oJA^b9aUj%g6l6*W>&RiAr;^VaYi!_J$Ashgq8Z^r{+#jL zNvBFPnglgPD4dm)GrN-}6!SRl&uwIc_F@ZX4=T*@=C{{zdbjrG)3r9PEauq$K`tjQ zbZCn>L|tuJsc>~uU#psq?9#l{Ch~Kg>6-#0pX17{OQMyu`u-m)C%$rMsj3(L`T51Q zD`ScAdgfJmEjpD|WNSLMvb;z2oIKb}lh)eBm1mZy&Q-=N+2+naICE2Gqa!oeZZl?G zzfa@682`0e@mM|}O7%J#`0-d2+1+zepNWp;9CZ;oG9;=z9^Dxiu_?${EeK-5MS*sl zj<7k3em*b(L4gQdjJ=GZ=1d>uyI6_=O4}KAx4%2QEmq=`%Lk2(tqLjMO_2q3-vL)( z=4GpL0T3h#0vct!20AL@Ur|D|vM!Ni>JpgBPcZvchvnWM%Y3k3F3Rk!bL-dGkxTS; z$w9kbIpvBpfKm*KVaO2f?BeM#W5Lgh^~rcgt7M@WMLiOrmUC%t2yX_2z!Fzr6eB;Y zIRj{%j=12DmG%u3p9YH=T7~F{a}RdWT3k5NoMMFXLG6sVGOF3EC^|*}(*Ipo-rF0S zb*~kv)32SO(HRJgw)hUeO{Q*DV^KzpqaB#}jF$cc86cb@w^SbEQdM!*e$zyI+=JI^+_rZ^M@EKXNPLFKtJ1@TQu2ws zm9BR(K}#Ns{a`g{*I3L-6%SlE)krmmwXbEaOxV2JniiIy7uS*dJ8kt;sH*^+5Y79s zS|2xxW8`w{E#?bgu9z}-mD;kP@pO*FZsH$P-C``&0b#bP7-!V)Pf#{^(0?oLP5r0E z7eXceWXDC4ZEM9*F3P;IXVw0TfFG9>OU*|mSxy}pmhw{j$hM~UyVYbCiB+*RzOVjp z+qPGiRpl0gX?d>>?Jy#`+KM=gh>o-9H{OZYBZB_tX5IAE!MeSQnny6SOtUMeCy2bxR2^Hu9* z8gDeqpLzn#e$t(&bY!HV@nk(y!>1Gs#3pEe}aCaU;+pK z8Vi4u&%4jxr|$@8y<>Uqccos;v^(uSC(Dzq?%Fmwi@g28n(ado-Gs?#+it&xQYEyj zBI-|u8UL-2rI$x4d`%!wk52`YBEX4KDKGwo+D?-Ck4QzDhFs%j9`LrxLFwer5>NR+ zNO2J9KeOdWNR5&8(SmgT$;o(*te_ZPpC7IxQGa$02XR`znXnLOZw#GM+ixZ+*?3eu z{nhN4i67j;4bJMg$0(xU+JdPdYx)PwTxV`&OjcJ@Hpmh@C~&39c-s}6k;0AEyN7&^ z`!TLFR7_!U_yfIeL z@vrgA)cOWZ+I#F6!f+lN$J-d;GHk?~P{@q%z%2P{PmFUxn|T$u(_{eKV5qso$3o_D z>1+@3ag9^9uhdRnc1;_!HO8oA!R0hCQ1d;_cygC3# z`>6S3oe_e|baq4ou4b;+HJ`2F-@ztUjf1N;wljB&S^Cr&pfAiCaD$k-jQaUA9RnpC zZ>Py=tfPI9(v`CFjY2{ZF3CqtY;T%qVnn!yCVH4SL7)eX3%ajTE6YttMwwBuWB9sQ z;~25`v5Fe0ly{~Ys})@)>N6DwKthXE?6;BnSw;{@`kPa1u)2q2FhW|KzGcvkf3*!a zTH4wk(OjU?6V!$_yAU52KpCSSMm{?MpZg?!MB|J8c_g-8mz1hBVRcoFeC_xpZKRy4SLi_x#yi z)lDV}&*%}H83PLgjwN)DonKiDsFl242_d~W;-}gnIkcEzmc=O2frdx`AAG3YLOf2p z7jP71AZr8S%QM>S2Oz8Vawc)^KR}IfZc~=Xo`Kwp0q$zHP8ipwD84jCFWzH5QxQaD z0vqT%LC7TY^im!!MSZMEQsXXZ-y)ar&342@B<2+k7Z zbrWEh)fX_Sg*K0}iq-U!1lUPM1RuJRQ(Z6T1&gA3pUxv0*b3eTC7rm@GG?J9ir|eH z*ab9}TT`@y&7ItDaC1|%!MPV@BFc0%K2BBz&FDkoe{lNhqD<->2tI0|Z-mvx`GvX3 zpZdXxALtUwqBB5Eh}=dIn~wRLP#7J5>QOC4>7K#ez>{4t6#~Qc)5(&oWu5V+)GfI| zgS#ic-NO}kxq0AA@@FNzOktCEh-ldYr1KGtQo>t$*B#7p)n%L(7g&$q|$f=|G={emX?V1uT$M(CTaIRe=B4be|9eOw&1ku!UYD+H!G)u*(iLy2y|}N5|g(B=CVhX#EbnTeaLy&2z2DQ@itN~m;2#k0ux@af> zVGfE7-{M~}(_jx=9F-8N)EkY1sA)5M{>fi(AhpV?W+CNNC6LLC%t0{<7jL&`T=r${>3U98)YZa?)#*Mu0#FUFX~NEW$#wAIz!mMad*8t6N3DC!L3W`i~QG|XDs z>*h+BVi)HU1XHBuZxFq)&UU_rRj!m{6b7h->8CIl zV8aM&hEi{ixvkzQK3V7;(9seAmP-RWP&h;Vqgdgp#o<-lYq*1EGv1o1^A|WR8n;ax zOjM)ws3mAu>8h%F2bt=6HYUUa3;Owi2M^Zu2&u#cYRE8-p3;2f?^8PjPG>RT4kIu?vH6fS-HgE+1B4pjbb~;*_sFvp zU)8#m8=(ChsTBLujZI4Dt_i1TZTmEm1v8k*rNpANgEvPC1p|a1&6*lhMxaIXB~z&@ zKNm#%PpV`0=%5xCP60B8Fb1pq~-3qOaxE2w5*=6BJP;a-f zP!g|hOoi6{pW-@ewSw@=h-i>#%#7m~FYdR0ekCHQRxBsf4sY^^OA}1^;Tdpzp!k$& ze6pFK5UzYdcWMZ7sPN4~NsBw1-5ZQ^*;F+lI&!w~Pt-)4%v3%L2u#T3y zf~-J^eO*~kDjI)&wm+a$MhC1B0)xt9I^q`Jv|Z`jbRLLfI`fPmwe3VZzbkO7*dPaM zTByOv5@dPTm{(3;%-2Epi|5Ni!UYWbq^mAf)TGkuU4Tn(nn*NNjb|mO`joAe2{klZ z5+Qflf3DD#cNGeJR3{5k9i~Y4%T7e`(R4|hzMQImmgEVS4}-3L#6a8p;E(4WY%sx|CTCs4-nRJkkGGcEy*N5| z`_&VUjgIir$)+H5BV7_Mm(QcOvi8kLTGNZ+;Nr7)PES^emw!Gv?k3o^(Jz>xrhOmx zDdF3i`Ne)8M#T7R7%QiQ%1Y0W@~(XDhpW&_yaOzLjUmX0SK4mFQ0maDw{~CwCMau3 zz6Ua1%e*dD?JceWFm9<`PUMZocJhVA7GNd`vt43zma<7Q6YiSKvDDtOzf8hr(KwSZ zqCDPIn`m3sfMn@Oy|4=sGVL$kg!*e8V}xq`_D2t~WZ}r{3NG^8 zY3a%d3dW)uqvJNXQl<&yEh=ryKr&Q9y7Fd!BR!!w*k&R_W1llD5vAkiz$Bux>2!m; zz_2tUh|Rs!oAczJf7Km}M)5+D$>c#aBWq&>*J{Oew&2x!d9Y|!xK5P7dgW4wYtm4#{VuR5gG##eDV}fc&ixE^sP~0|M3f%6ZJibii_$WBJlp|>)43R$Iz`Vgt9n+^P$4GHp??t zxt<4Q@@jDNni6)?@FP^{+zZ6BFL5?s2e?ionO)sdb>4F2|Cw~+8L-)y8IdVXLypRX zMVOB64gM;onru?_gsF>mX1rG1c}qqLYTjnNfCFG4Z1I!a^onxc8>zM#7dTGvIMUs8 zWiXCI;kEP61q=+4`#WcN!}owo8EZcJpefY=M(liE!va%2xh^6c2Msj3u$YuL?ngoEoeZu%`*eaoEEcVrlNnr@B^3#RUfEUJ!uodkR;4vh2y;D%d zN$&)qphP<+D-P9Cit~#b5M{2vVM@`3y!!!l_$4LsQ-39WKS)2oL_F?^sD}{jUe;%2 zQKrsKiEOz7EJ1x+FlaKK3A<;=Rp0&sy8~juf!G(%PZXK1F~yw_;J|;SCrED63xGK& z657pwWtEcmDmnGypl!Y%CQA_-J`g}H5J7wA$b;sQ$*POksg?%$RiRWFF!+C-b3Py> z@RB~bL%`9Egh2mG?^V?|7J951Xsn&bg*KPL-a zKkrHTlv^gS>$m&T_|1#WO6T4BUW?e1w7(_!q{-BQ2bk3Zz^pg)ONI)9^neA3gJYaO zou0sNX6wDN%$*F?%~T(1=J$}u$2o^3Jc^tr#W39|q#h00VI3JrmMmtyma#H2OZ0G1 zr8`MHbUB|{jjj-SRk9aiBv8&Aff zmfa*wqafN~15H8AU>fl)@F#wecF4LAEiWT`?f9RG;5i&&9CGe(s3~P$H$}QCv_z(q z2{pu#Ps#3+#9?;Cv0lOB1DR$SeC8s8Eu03Kn!Kg%oT1!e6p>VeQn<6Zd!75?;9AZD zo?CFz;-VN6{BRAO=i@Mb(?uxBz(d=^??-Im1>UjcB6IIK7S=FakY{Ep<#9i)WYOHH zVwD@nP-KVC#aGaDt1{H#r53}8Cud%}0*|ciAxJ9vpck$@QxTMD3aKmqr1GKmjr7Cy zTJNMl6IQLQhN3%x9j=#ZDH3-q}z! zxKKS0a~bD#q=b-avw?tx0x)sjIwsZHvK(JxgEsDM*SWnILK+M_^-kln#74~52mcWj z-&Q_0jUCrkZn)8)m9;6fJ*52*SOsPCXqw?<#fKaEjjlj)%|LPU2sL(8W4&u*+E@y4 zW2l{Z)M0u@5p?F-Q*H_I!7d{-6_(qLQY<-GZ&qSx|9oi%e0wZLjW#!LHTUF)C4TPQ zX~su%-dDMCyr4qyNn$DSN%04hQzye_*)uHz29{?jaA;V>@k@*4T@MN=`9n_5YY?ay zC7gC;CzxJ`5E_HqZ*IV?N(i2BKRq|~J4|qxo8u& zMMoyd(6g98?X*C+SRbsh{YkS}96uHb#*6Z4y8%|^1fYU3Xin*MQAJ~Nzk;$;?Ki7M z@2RLhGYTkbg2>FlYGn%BGWLmLJ-W@NK-bPd z!R6RhHb=PGI|Y(=B)EiTv|r?bGn}eTvHiGHI2gY7Mq0E~$OY}|LgI?Mc5Wv$_TrFQ z*$EoU-O6u8IiTQgLh;h(&ZdtrP0s&A;HJZ;)>^ zAAh_Xrj|u#!mlv!)R5vu7C|g({hbmD;#xUblGjxY;CSh8Od{wU;7~d>p0J%SOPllX z+8&Bu)HC*QWDU6cYYCs@r5hHyTof~~Y*>iV^t0(M;@dY;y-`p3jCK)LfB5oL`2QQ#pL!Q8pmR>q{#`2N(+4GiMbd#ToZe7?#R`e%r z_DU(ND!4Q$e}3N-ag6gd+E!7A<-&C4U2|LSD3j^}vjQoCMXx?b+%dFTIV4Luet`R| zlr&zSN{@y880|ccqURd8Y^eSO7nwZn4}RTm!9v zJW@t9YK>E)f7|Ck_iL9DUOiDk-g1j+4DnA|G=1Ohq`-45M$yux5?CtTJ~*E7`mvTi zb#3dy#`vDHh>$V|c6Xvs$!+iG^cuC?az7b|Zqaa-wUh*5-^Wr`8&Gg~pKoW}KkD^eP?W zRi*~WNPlyf@(VF;qr$VHS<5%Q00l`YDyHg*7*yq(sIS*o>wBeZZ`w+v(#^O`6y%sHQmblCbj8<(&p?w-lf74?*N-q@Sc5duDN9LVlT9W()kws{0C|fI<({d z`Sdz}Pg=>8*ijb&xmpSPX1g)LaX6u+$-n}9bgSEa`|OLHr+iIfx3zwPnP0+wTkCF~ zLg>Xop)E((5qNy?a&0>@NGMANI~s#~4otlP_H}GRK3>W4Abh*PYcu*k+eeU zs-XaLvt+Nq7>&Ok5%`z@Ty$B;p5v=0yro-k_3AD7Sn2#V|1=Ry3itwRH~ucE)6+Xs zQy}WMDLZ4>cje>XHYu+O=X{Mp=P$f#(u!L@*hJ4VW{rX0dCEKpd5_*&{NlRY!kv=* z>ebEQH;E5#;?;1A4E77jv1F3SGR}2$rQ(JkHS+rX2eqq6XNB0a8;U0%YZZU*+5fyb zlX!UzAK?2~%fzjY17BBCF$B$WS{Zq7_yryX$lfen{SN=rpT9B|;x}%pR&q=6hALV-azFG`v+b#}_MD2MDDMKW! z^u(WrY2~tZ#(92bA;qAc2f(uiW*EoDeSd0%01=jV)&a&{q7DTFGLrQd^^RhYqb>7C z?@=&WsvWZ|EL?#rEK-i*`T~DUMtNtkSPoZe5&=`kAVOf3>8I)AEbOTAv&56jUki3f zTu`pYD0MpqJsgz*PVLTQ8YmakcC6D$?#|%%*5+v5Va+7YMzLMAET4V@L5cY$F#6#z zrq!H~-Xy)u3qxQ5BMkq*=seV=6f98&LAg1_u;Es;g$J7o7uNA!{_%r@b2vEm=A7eX)3JA)fkSo@lI%@J0};pGj**?cldOuSV;&=$R4U^{ zNwO-H@_Y6E-1qN(f4_fx|GNKkU9OAkb;ff%)-&>@HCWP#csfB|dcAfMG)^2 zp43h$(_0Jk=i0p;~L6(GtrTno|!84;QM#K#xfFnT`W0p{8a^;7XF+xQjba z(;e+8AmdGMhTz60GjspZcv1A|e!>7$mLi`kyf#{LFID=Ebq~^`RhQI-c(cYly(A?f zoDdbK`4yw=VS_)-#>mB;Avy|hE_NEZ!U_?40VU=-TnY;c>IQx~XQwwN4dYljf=ouC z8soO5D#o7O9OC_nYmMK6N=w;v?Q}wHz}d>4y*2#D?Y18MQr991Rj#pLI6ClKrx~sy z5?+4Wey4x()nv?cHfLjt;m5x`N>*=P$9eFRAPPxGYV;X+F%rADlUcCWd${`BsE z9$9{dx9cxW^fY79g#pUC7AFAt>{j`fvhMrQkCxKoH-7DnS$qZcjw-y1nrgdB=Wl+$ zIW&WNNc+opsAVe|Bh~gN|8>)}67ossj9wk*V5pTpr(yGoW3<|&^a!_qRNYTJYx}aS z{I^9}gm4CHMWr5@`}$9n&(nVzj~|DDOsq-v1Y6CJ!nX`EH9oEUziPsyf#sM>D?dhP z92)2B_@S2v|0?J$oKq|v*^qP!4Ql29|oOMoi~cdz`I7BKcS2WrniGYT_RU9KO` z3Larf4^kA+wPvgj<7$<-+_Pm>ng-_(Aymj~Qmw>zLBht`fP9u3)@@+W^ss+*C|%C# zTN>GHN>xfTr%WfY;N%Va(YXkLotGRuFqnlsTi>R4PM{Bo!HM{K`bNAGNqQCRSadNR z!X=h%;Pyy@D|4qpw?`E+nqI4a*m%~}4VB!b zJZB1cpT^ELAXG)fbL0mtC%7!VS48trU0B>ReRUHBoqQ8~Pfl+%Oib6R8vX zaabDrFH-jsNCcSO{(n9K!tu5jI7i6`kh)cJILhUZHa=}t1|BrHVpj^e?4MyC*iy^XbVW@(9PAkS-r&-Jr(;t_!i= zw*szB+rjv+w_JHu{jN8r^Y%I|fe+Bu8}`L}=kTAT#PeT2_xyPhUl*V}y^Lu=+ z{#aPQ{LO>Q=@x{l6PI4AhwaTZxhJy;Kl*j>`Q4=#6H8JrMh|Bv^(M}$WL%({^-spJ zJ`KwjmfDG3Qs1UZOzC~sELzcF&r+GtV_i6tow)7;XgR)ipULE-z|_pGFSw>4^}im3 z;zYN5q_PwBMn0@1DW`SU=xpm!D3W??@ktUYu0Rwz1R2)PJpu%g>I*BOvr}^*G)|&B zm`U~VKpGppu{Pt=UADs_|=%@~i?tb_6I%Sn8+={_)f^IMtGc2z% zE@-H||D3f-1gb*YVdYR(+-piIqqzrv+l(_k}8y#Gdr`7t!mtqHe1cp2iS_JzON<=|GlarLPbB) z-88+)s0wp~Gb=GO2lv(##`cR-!^M?B@K{#m1QtC5b_P)hC4OoZYzeVlOxjZkPXU2E z(oPHUG)URL=RoeI%yZx9$f=q#!PuEF)EvK3X)}*SvtG2vLpgJkAr6u<`rKye!75Cn zBuh!NKCq;SD|2Fi1eb^pMY)og?}GS@(u<`?$!lN~AcI0DaY-5puAE*F5`#>D9U=$; z_9A3jkpQz~Tn^=K6k33CI3VQ{CQ(b_kQav3Bs5jtSXm zFltkf%i<8+FS)cV1pFs9M}Qt6Os5!Vx-uyQDe3lNPsTWyy0XubmuqJw)v>JiqEQ@{%OvfH9kAk7hG5qs zO_THEO_$ivqH{vI9R)Y#YHmn2#6c-6r_^TJkep4p<2iO?Dkl%Wui<4=na0wC;;F&` zLP5I-{@+YU5J(*0K{Wougg{09Gw3nm;%u|u8N@>+{u#B@JOrA)x^cHl;5^BPU-v&u zpRG(6P?9ZOr`{YZY{fBtI^^OivAE^(v`lc?6JaQGMi$T1lrmjoRM!}`k%f3HPmDe^ zh_p3*bPcki^>?u9sKPeLR6AbrolUmN{!?VSAZyof{?QG#G_$ShN8!8mx36(j$vt*{ zOcQOfxPY^5d`xX(Qg5ujOrC8oEn`3b@Om_7$5`Rr#p5pxNjn2tG zd(rkKQ@BuTrQ5Z5llW4BF}u{Y>(j2e^y88O7rqA9vUORu^^Kx;Pi`x~og3vKdLEK?_BSnI~+*C6CjQ2WmFN% zS(_Cgf#XtT_e9ZfNelqpsDOfMs&v*dC$hEH54jQiB4r?`$sbkzjMH#QaE_X7StVnP zc%(E3PUL6Uf_f03c~>}5_qWs?qAK($w#0N@p*ZQuJPoda{4BAOeG^s@X?z~Jhv%iW zG;o{IHO87Np-lfP4|>HwqXe+XM11&ANPhT%-po>?tEwu=QnEHh&ZoJUtZSwR)MKdH zhUu+qsDyDg%UFSGH&?ws`zAZ7k?)u% z2{pKwIwgSKdBB$TN1B_c^BH2wr6RS{_Q{~z11OFIMG<)P`p)oewJRVwd6UxMDc>tJ zL_d>oBAk}cB(S$>sq53o9Z-*kPSdBvE!3E$6U!^jy4oXcEgycmziAB+*r5^x--Uui zI3j56u5q9(cZ&||HCjTuwxStOqoW$zdj5dpMv3sS3@Ecf(h*s|YJ3Npv@Spc&iVG& zn5mr};P<^+ZgUneGfDRHm3Q`$?+s|-bs~u#2 zeONXR4YCbX9`@}>uLj!P#@&gjZNK|Hm#){2!@3SOq@fkIq8e$}jgM8oO9}t#FVm+k z-?_G+cq$>utE%wfhZ}A5w;q4LR zihU>V%nvU)tWx?3uUd!okD+K*&Be20noN4Xek-05Gl_!-**;bcxO<%*nson22^)5H z=b9uD&u97q$bVf;45H!$elp`oL^@Yzm8viDU=U}wVi>3JIrMRP8{EvLkMn9{l|G0s zwRiWM;x4nSBN#;funa=gIa6;cF-W|H8|^X8FtJCi`rXBI>E;6TOOfRjz3@J% zbpfENx^rQEn6ieuuzP->Wh+4qqi14MeL)D&vVD%lGBy!SLvnN-5=6g9YmHbV*?BQBg^no^ld3bUV?DTdBe)~CwY62c^XtvJyrat0?C z$1JHUqn)?Z%jVci$6tUvo#C>YUFBjVSpkQ7N^_Ah&_vWS9sBo$K3)nWNlpWsPLxkj z?A)S$)tsUShrq-HOlm&ng5p(z@M4JeB&Dg;8A^+4qM*)lFZYrOS9tI?lGIJf2GQ6f zfHTAkR_sPjN=!dg-{aBRSkVsaDUyBvDV5#pqB5dP}?eR z8!V7b@;@|J4_Z-#9{Rm!;S#cKlusL{wz(k;xuy6VMmkb>u52%HCKT4yafmB8nEHNi zMd(Udq%B+5%X?1TJn25sGP1`!QKNHx0xX;FGiqqx>1KZJi!;T=;Ys+ksogE@c*GNG zlig*vhYv2jq&(TUhtRqDgKs3KH0#ZSOHt2-XxA>~6#Dhs$T~$`TIznt^zp55lJT2I z*D}TTub;bHPMMhR0(W`^mJmjAPvRA&BSd7!iq6h{KI-{|QI+C#wef(9Jx*eL#GatR z(Txq290qUxe%)>GihhIlTF?|PGFPQkSjtPWo8(su9uMfu)M6ZU z&jM&}z@lVHHozf%ojvEA(0Vk?s3JaElP?_f7+{~p*#|O&S&UE|!PJat(@XSqA$UHF zU$?oLaODkQUgCE{$4AY^2!03ZFIj4(S z<9d^Ap1Y|cR><~*f{1R_$8gshKWjn)ZI3&%^|!4|t;GHct&g3gaf9@f^+T`E_g^? zUw++sPdNmOOM1alONC;T-5aAPZ5}*_(oi~6cgugWVgl>58w@*{e!0V7IRR^~9p~N5 ztT4iaRm*!nzaiwQpwo@J2HP3GLc(l4s$2(7Xl#|iap3E%`YHzWsBzF(K$$SwM*yvb z{!r31Ifr~RC3&QrHzcq?-Wi!N6+U+lJ6Z;S%np^LjJa~AGS#~vVU>QV9INS}7ZnN7 zYS`ssr*AKPA8=lIZCoJ_uhvL8Hot(GSupR)j(BaHr3Ombk?O4)$5g-iLtbW@kKFeL z_VgkvWwaiZ+l0^e5WhcmiQ}>+sL?;@BA=~hv7x{pgT6~EtGBWu*Y8zWF_VgbvQzi) z=aah64{ks8l$MIZjY)Aq=lZ#ehcXU7H_APIFYx?EpRRJNJOh+DN4hhWNXrv9l`edz z*bAaNSmyZt{`@RHe)M#{Azv%zLlXS^_bCmIk~1J8F} zWT=>mBkp{O5qK=lI~1D%0yBsJNT+V&RJ>T%c#M0nAqPEB=m_u|tu90C;c#Fqt%=ek zh%5Kj(1Q#JoEs7s$;aDuPx9d&+#*zlUh1xt8JU3_R0#@p3SrP7;I7-5@L-qPNR~&p zkvj-xEu(l?I1I2y+KXVxRXScfAbg##81_CA%(W=v3yBgpwyq-t-X~5QS=`tigOOO4 z(S|rMj|9{keRd?RkY~y9;yYWx;n$KlI#_MLwe_j?_Wciov z=oCMb*Bb3wohj_UmA?7D z>~x;*xbk{eLe@4Nb=vtLQ~X&xMbdvEBUAKje6~iq#kntHUL($rQ?gnt-t-MS(`?DZ7%mCj{^N_F*8>O{H_3Bsf37ZSj1&E;JagHlClr5%9q91Z}LFBU;K-jn6W zSv`zLML2^k1+;#eY~HnG3he{L0Da@?dx!XR;io^=fQ}MXJy;insgY20$ndMHF>r2@ zMGpr_v}$e3vm&})cS~gJjt67up^oZ+8|szz$enZGdm#)Q<3;$^ha*4TErfyo5OD4j z*FXjw=qVPxZx-Z2@*S4oY0p+eZ?<)Ug<%3%p|Lz>K-Z$u@T6|@Wh1u=Vf9NtTzJmN zc0$PHbp7ih*oT1($KpZQ@AVcHJgL$xM+bZLN+%qDyKHLK%2?s+ob9;__w;`nbv$o( zI(x@V?fjtoJ>AiOnXpWX1fU9XQGZ6mEnylHH1E<=tu<{x z`Aw_+)hwZc?yibxx*aCQxgyZeqC*Jma=A0Iod1VhL+xEw?s^(_8^)ZY$=bXt_26xP zyBdSA*XONzC8n?0-vFofs#lC=9u9(>lya!0yD`9jYniF*8S?}hEmg#A^6SeN-4D>x z%8T3E_flVco{a9m7iYC#<18U<5ivGHFdt5+Y-LqU{S2qF&|$BN=Z^7MkqhI=B7@Dq z9qzqFqu&BJ_T(j2K~ybH7(>-g!N3z`X$y~o+HgtW8s?nkzQg0yo06Zg2_one0?YMq zc&I%C2xT>zO{Rk)Lg~1R3@j&k!Z{=7CPcMrxpm6~fEKiDyl_b5@YzlEl*c2^XUt^J zY}^gh;{2xhcs2Y+;dvG z?gUGCFP2A|Xy)Nk?NdOG*FBRbq}5Gfdk#yK7Qr4*Mz+?g+(8q4RL{Up1|v8M+6GX> zeq@}7kxJuK9bn9f6~oY!1_gjLMJ%i1+z{{g55^%+cQZTy|CJoY8bLp886&x-^$I(I zEtoBPvs3N*+sZhoF^pVNzb44Z77LNxa{sqLL< zRf}Z3c!=Yp$DWj9bZ3Yl8*ho_j$AqZFAG;~H(=rF^&C$j7Sj zmMI9q`Y?tthOaeBoXc2w=FqE{HxAhMr29?wQ7P7}^mtitpwCZ3M9dXJ7A*_eYvH(v ztx^3eezRoUy#tT)%9SSv_vx`PMlgy<#^{k49PGmzPn9w7kpnlnt{VK7$w*+u~^Zmd5^Ae z%hBGdP`+_i{lp$hsSR*)L0DbsoCn~#$St9RI`$^s_$`N;WxbpIyK41#y*aY@JpZFO zYP{=n7eMGiX>J6iTGGOU{P&D^!I>U$=xg^3ort|OpWzIJkqz3D6! zjV7YXHowyS|Gctquzu_x6ZK+hxF)aqg`uJZdx>{0y;?Q1?)hKspI`cS?j~NO|Lk}E z*Q-V6(YyDGJ_i{qApS=T=L155G(q@(mZbO|A!6HduNCD;#;K8vK&#%;78EHWuk?{f zJjmD7-K;zm0-pw!F}$U74YuOR`LTbPnEUyyL&644Ld-yu(d0O2o%&o7RyXVKdcY%i4n)J2s+gpt8=G!ONMIu60+fr}) zLcN~9>dIu1yKQ&JA#`zc&C2ublJ;ls^i+kKkw!>f^@!^Vg(2(FTinV(e{R2~q}AS9 zf5opypE-Iir?*1RMg{%WzWfDtq3$^3$`hUN{kMa8g;`>nA3x65#D8yi`uK>JMistv z^2w=RzXnoKEc%}ZG%9ewdXpCYf(_8ODen$n5P0_c+AU>L*O|(M$(~%s_!)=Uq$DOW z2!|Fm_RvYMsH=05Btu6ON!|g+n;?8Vkcm7b7p{zqB|0sK>pKa#gHWm9DSCF@b&Y1X^t!BFfPbnX_6(n|gP&>WbI zhka{VWnz(m17wL?m|)|X@dCMjTYrvAnC8(@68~B1ap;41?zwG0g%Dg<DGia55KX_($LeU(Fk z5)B&E#r9zFt-~<`^tO{r+=#~l=5|~AZ)b0FL@8kxs?eCI#s;mRb}D4-_f)4S>k;;# z7WBMmtJWOCDhsYxkjGS-<&MSF)y19}iH&l(PM9TXg+eE0nd>prEBnNjO|~o4 z8*spz*M%?hUW%~F6j!R!ko~yhtG)v9Wfzh8lrbl#PBPv7_kz2Cqs=NL`^j=PGLB>_ zdUaX93|+5OI*of;L=j|CMk}O87^^iS^`U9iVr!KXxQsKI&RYhoIJr{gyvE;nW&L#j(2IaX!89(AEF7gA~%*{A4r7knW9 zz-_j}-g;AUl~?OWH^^(`>kqS@kddYye=)Y;7dT)U#6)MfIA}6_%H>XF1AWpy-^HX$ zrsLoQxdeku`=JHMdT)yWm_zIv53z6HPrM4+b(~a*FSASuyQxU1P`?1t$ zMOiPjXt_R=OoGbx#+r{Wo_KcnjhG`@YuzWvr?Q7gkd`rl`0(O*h`n}VG&(a@Q9gUm z$f4*GKkK{nXB$vC$x^ylh~re8@XlqKXwoLy{(Oi+f{3&E z3lEFFh6&jK`|Yx`GM^$Vo;aYpWcG*Mkfw7#Di?77Rt5S1fSC^968Ue)Ecl<$2$n9p zh6_3|%)F+mM0P=8^+ZXoj7Do0UDZvo&@Fq6o1SMg5l0|)h!Jz-&k7OjC*SGal+d}~ z^lj>bS+N|6WxPoV-W>fWibeG2e&yd2TSlTj3La0wZ90JyTUXG!p|kli_{2b`$Hf;q zSC6#FPL1jofKJe$Fo;zlhYCGmn(Nc1gmKyO$&<=mAx7-!`eNOT3S5+bTon<_7)=#* zIu)oQb+_{|Y5l&^*lZj_mSu&Ssa+=gZIQ6;sl>OBUzP=4yZ<0Ck6)WBi`#Lrz(eaf zuSE>?>NvIYb(zk!TfC>f^IvdAb19(A2@plA0(oWpo8#a<`L4jpCRPieH;Ac-h^U(r z)V<6~RAupWFFr>FQYQZ)i%o9k4+(geQJ76ymnswWADX`?3@+EBkd&dwJVPOb_-FGVsOXkmX^XYW;M;X>u{A&v{d<0eOunM#-OXL zH&IL0i!_9=vLH;rNJagy93z(R6|v*B-K%-lraV;Wd!;iT1?U`nobWVbe|0(+WtO3| z`c_Y20W^d{r;^jX`i9>>q1Z|q<1-{&-qJLXk?RDehe{S{x+M#8d14cTk`;M*Otv*C z3@T&t@i67LkTq8msi~%>`f>0~pH!Fhv5FstYQ~iz*S#9~30jI+HOxUCXPn z{r5yXPAfdPiaWRim}9W5Js7JcT1c0 zCOV;wKb?pKE97(mm&m^_^|Sx=^Jew0X$~l>f2=K=8kKvnq&)Vnt&Dvbx*L|=hs$sZ zeUvtLhC|nQL{6@C-lBpMRUxWO4*)KD~41tQa(RaPBM^raEFoI z>h$l41+IJ1mTF%J*2((e4~|8zVol#YiRX(5rfWidm7#5_%ZR-!(ANXGlsPN2{TbnQ znA1oSWpTyf3M;e}$A*mJ1z%6h4vonmtX(Z zXz9TQiwOQ^au~{Kt8EF{TR#@W04Z4Y3O*5o0p?Qp#a~?Xsd^_)MAv+(fY1Y^ z@lsYOl*D|q**lA&#|NHmol>$m0E4`p_-i6&p=(n=mtm^uF3Yh-R!PDxy28Iw5ZJvo z(ta^nWCy|oKUSgx(f9TX8F;WN2N_8H{=py;Es!AelpFGKtCG>n2C8+y1)I|YfzlS= z8eLrmnDa8I>S;8ku^ng35WODaP`1N>sOenFxlu|;W}zKCRsySarTF_yjJ@f zhbjXm_$6<_b*GP7=Xc%Lhbjvjy5mLG4+*{)Yn@hdzYsORTsm?p9#}Vsck)LK9_J-u z3Tlbt@G0Lt!^cIoOq%C9%ry*sM*SrRwV%DDlN<`sGegsjL`NuXGZsnkMWqUP5#H6FvB>d|fi6h3>xv2hb8~RM5%Hn7uuW60pmf7HMJGu`Pl28)_8!x#>Jnz ztGhIn>o+CdT}>R3*`%v6<|9U+3`g;WtQ=fYkj1uJz1^P!OB1Rw*+0YP-IGvW+xG)ij7z%s9M=$+_L+-WlSNBCP-D49vX2drl#@_qZgC@o?z-^BG-Wo2THH8!*+{I_-u<~M72&=Nb3%IwI{5qq zAomJPjaiW^Q`lNr6{54iDU@+`sVwT-$Fy=5Afk@hsT(S#d`;7OpgMik8VtxQ+8TKf zw~Vd%GQ667|1maOSj_$75DfLHF5&B>6VoJ)`qp7wj~{-lzjcEpkK1CIf<1dzFxMm@PxujzJ~a>Opn^xsIzt7lC}jAE z)D;g-BOGE}+Ty>*4%y(K zgYe;4o@)?Sh6qh=tCBsbmEGV>$#IjJE*~tZ@&ok!k9Vhwa_MAAF>IJ3EW#iR>A>zmVBtiI%noyV z1y?aTbH)j)x8HS1h>Mfb#Imw5R(#s=G2yCOR-U8^?E*R}C(Rj{7digw{X`ZwK{oQ^ zmt9J=Cokb2dt&Vl@B&`>o7@5t=HZdt;$__^w(kQ{A`Dd{$vZ=4b9DnQUb=Qx%>>Sug~3G8%^_dx{+Yg zZmw3Meu;%paN_ytn|d+zJhD?~r)wnsDU~v38jP-?674Vfoh_HRRZaS-%j1^RLqnRXqlZm$UU!GRE2y5h^(f8ZGV8lH+oD9N z`@7O-)E|8de)Bk*A%^|Q;Vx~=y9Rf!@zM7$Z)fL^b<8k}*G@Y3UO(5MxN`MmXtNSW zV3Fvf%ZK}G&$2IuVt)ap>bb^uC=qdE>%_pMUV6Cob4=$vFi-N>`K;)zW#FB0N!E)i zae{C0RS8fNdq68{A7AW6FSx#%p7dmYB8ic5Se>DmYfnx9s2!t#G)<Oq_I%nG z(C2Wn3MQNv9Ark$VQJws4}v{sFzi{F`5m4L2Lh_)41sC!ixPha$bn99=SQyE(wafS#K z5J;Vi?50;GJq%>PQHtX6A2WDUgMm^y2 z5!HUb*)Z+$Pv5%_av9i_nVUed)}wFztUcWYN*1uoNph{-r8zYzYDJ(OgF9>Yb%I)n zra$jII&fsjk}ILO`$->sOSf)F!!+IL_@hclWSNC9azA)$ciG=?Zow4eQ8n+MDO?h~z;bpe@b=TVt`2-Uc5dr=X!E$k z={q6+^JmL(vtsUexnS>TE-1Mhsjc1^ z1{hq7skJ#*g{oA+IWtB~Ttl7ieVk#m=5jYKn2na)P!W(i$Mvsoq#lR^{C~N+msU~-xuQvDm3l%T;UBB`5jj7jqsM_YRVRfA?+Tn^N;FjxQ zx|-#*I{z%N0$r$TRb)_NX2PT{Y47>UCXrc6P`(vqNapdZK9hRRu3$PIjm!ujLyhMOa^XArm)pC8P(H zE^nFU;G7?1LnxCYWpEM^MUB;?7qsr5Md!EFahlmgmT(Cd#iD+CRki}RsZT!aUD!Au zEV()3)1#~X&Npq-{$sV$z521xyq{mC8Vj}WzxKHG^G8#Is>AX8XmcIzYx8Zh#44gv zP3VGb-95!2d%icEcr(?q8%-_wV+FRn!T~&}KqO`FifqfX`I?M!f4lRg52l;WX6plc zDu@2EBj5JjdQ;oU?yXlPikIbje7-z6T)+M0>CcJl3az_tuUqQI`J&|7=+0lqP(tW+ zcS;>(-+$@bdrRZcyy8_A!pD70QX1;yO%J1x(zETQ@H+&%`AsXI@KBN+j44`TUCD_Oq!h3Q#JGyd_FuID0^XV zfj3#z8{rVuJ~Z$AwB_C+`z6Bq6+qOHBI*^Tmy#EheaxmYZCn4KJmoE<`_bB|qhfES(2mWg+3iZ7Cjmf@1O^efBXCkZBRTT`~L?wIGRQObOyzmgE0(R0yFGs}M%#D3oU zY_vlaf7Q|UY%p^mny0k1_#pY_8{aP>3lKvg0iP$ThbLuq7)FoB?L^pTIQ*aCm|nH! zGMYKVot~S&*RsW)h|Wu45u_Syn2l8oj+pxIhCj2hOR@Ois^~#Q5{uT7j^l+h?n&W_ga$PJPC{i~{(10*i%*%^ng=dI> z=yu{5@m|l7TESGlW)OMk;(XeIv}{Kbw3i_bFM(oZ;_#797ZXP98ygO)b_FYO&5gAq zjn0{S-bX*Mk~HauoJT2k7lpVFayXcln>mEiYU7l?&u^Xl7p<2B(gwPjIPe(vfyemU z%M8MIV6y0qlc}f}DDO}AMTS_p6fQv^2p+*>6wjk|^@xb6C1!Inm*|>=RzFKaX+_Ha zP+kYoF~m(TNuvNfiCeJwfEmwd4RSx~!6nI~w8AOnZuADA@_P*yf=v5|px*v`_=pl? z2~%uJ!Jig~cptW<_*zcRK8a-BmyV4Kv`s@|&?`F+du9~Rk?o30Dwsp!6ojvRbue30 zqd=f|&!fMP&2H$ieXtkl- z#z!7(EjxH`J)G4-gW;b;rS2(-J^d$#NU2O zbjbVqF;rMQiTv`6$^(3wnfx8&S}WfjixHmwldb+YbXnJqR77>9-!VXp?D!5ncsKbh z=I1j?H+a&ss*FcW=+BA9SApGIG@55zAnhOpDw(zDnv|l-=UbjCn*-z=pECak)&}|) z-zXI#;ZG%sNyK~jiWT?=_mtlh>@%uDDeC>8h2}+7y}KbZRoTbLqiWsH$l8d3h#NU! zD2!@FE5kxEhYolaBM~<-7S+n-2KFdeS}iF}vkAB{`p|@S14*dF^U6ra_VJktR1P3G zr`nP@nFJ^h7EbA7>qKUz50yRn}Z(fJ{>i#*2^Uz96wAElwM=aTk#YZT*gpTkRu?=NWoTom`Sc z=Y|YY*jeS5N$giuLrA7A^JARQgK+kCzDc(Ma!e8nsd;8k>=`Z#fENqtD zZG&FPwmp_Nn*0FNe>LR?1{p8nMgYgxy6h5LD~pws(-31J*TuqN9Ub$IArzq`0XKTa zc-l*5i54MOD&l<6X$pVx4kr#`{RNhrYvw3%SFUV+>>{(+JCR)k|A31zguAh_Z!F1d zi^PnyIu2$)DhX6F-D9y3!*31ggM&c=zAmB3923pZ?fr z7#>DOlV6{bm`*;vMRG8(`pqQpEZf#6_NyEMS02U$mfVR+D1KfqqA9_s7!3PTgGiMx zg0FO$u_0hp5MyE=SIJI-Gz`gnSDDSVdnXZ-;VSRO*)Q}eI7xkcg*Bub*HmP3g3aMH z{CHlO0LdCiL10&n^Hbn#zcGrIXfvK{%|SFfP1=6En(qO1P|>eF*~P;xpt*Y>F+~aK z5#S~?+|4I-KO{3CRf6{&5$l{zA;wKlcis%^509Rk6DKoZ)#GZlvrMG3KRxroiR0p^y{O)Wv=uU=Ade zc0$PSIXNF zGnW}^;@IrCe{CwPdR3&IB_Et@B8{jH;IOJBVW|KU~)+t;)8~a2Nj>Y$(0{D$Rap zwe`igzcx0~BHyS6&O4PL|NNz(p~sBS^5KfrY^|iHSwCmOG9!Ux4GTIxa1%d*C(GJm zVLnuPzNd$h&U*e9(zeUp7h;(-i0yG}>{Wunnyn1g!EB-Q?u^MMu+Q4u5JLhxcNGLI zsay~9FhJe!JS(U6bDF~&aB6mt4TJART4;(O*y|y2AOdR~uP%z6U4R}_iFtEIvjzy~ zy5Pa3@;1X1PESBr9qzA9JRpsEFPmL72l=1t+_exzBxaIeD?Od@ChUBknXT zp`e)hKB14Li^A?_i+~hsf=~m$ocL-OZJ4_%U5D5)PpZBP((fj$Y_H!yWD5+ZgrNo5fO)<$2i^X6%Gg{P*zVOJIU zQF&W>u08u&OSQlc;#aw=?JHqdYlL5Y7*ejGumx%!33kwiw7PP# zH*a$IHO7tqn3FS9NL%?~#31@%CrLTHDLp8>UwEZaRx3R9AV^O17GHFe+Qr?pGiCJs z3m%Me7VX)QU8q;G$J+Y+S=0Wxd;HG~bTF8s^!sVmbBu+*w8FHK>{5Q05TC7MTYPti#p1c~p%RKq>XobZWp)_urJKlaGo}k^~v5;@wnpBu@*KfFi;42%NiN)<}LfT-x%Vj|wCGmm9 znG1xG>?>X8D3~(dn?_^9k4gzv|9A zsOffH*C`MJ1W4$;gETSpj-mJ71vK=kbOZ?!YUmv)0#c+3ND%}<4NU@qgbp@3qM{;A z1d$WJZ>_!7o-?!0nK@_n%>ElD{DznE=DP0ZxktI1-Vo8Kf5q)m0G9x?q&*`5=zYLy z&R-832U_KFf=-q^7fk&n>b~7d0kESNRVM@RTIrn$@tr^Y<_N8Po|CP)@T;+u0&s7qBvHtR&LY$yj5uFS*A5 z`z`$c`x~wQ;spVu(|iL6`0Exu0{!#m1uyFZ6O%{|tn5$lY5GZ8WB+GU>BWKSqPF$n z3~nvc4H0PpLay*ApGtCT&C5dMHAuN*P|VEllM^ZR9x)dFzI|QGyXaK;E(!s z#+6q>3v_ZGJg_IFP)g+NMeGOzsa!@~O-rqX(~ZtAyPDrT4qU$qlCAq_S!4SBeP^0= zz_L_oQuAw_?$*_TC(sM(#*6ta?nGX#yT(23n=kKd`o14uYA&j_JgZ`K&V8gm!)N#8 zid<3KXQypu8Tro74$`AVA)k}xdmaaL2lyI9vzL7HUa!eX`!k&7f+kiQ6`I19D5)Usle6ukgELUAv+~V70Oa^xB-zoOt3jWh zj+DoJrBS#i`9zy#LWxnAfh&>mwUwD-(?+}VO;D1^)mTB_a9->0F8OPEu##DR-AY-C z)x@7G@mXp_9k=z&A8$k6;rJm+=oIZIhe;PHg;Q2?&3iQUk;B^1cS;T>-9X;u{->{Aq?&iWijZ$m##I)$=<`PQJCuQfytx zGgE1Vg=AeGtLOKO!RPyUM%+(lWucb7->}3JP(P>7uS4EpR~DQrW{GqotNPOil1s@| zuzL1ZNCXJ$26GzS-%w)+lHsx|jm}ya! z-CyYsLUGoT@nMChu&o0EL5fH+q`4woNB9*+K?jUA*1$9RcmVY;3gOMig!IONV?rq2 zz?*9`UH~nT&Yg=9AP3bL1F(|uIrB@&%PsmM0@pWwpYD!aI{$UB5O99BcgjGu<)Oi? zf2lP)Q>}|J%?U>1i=xl^yhu#<8j5A{55$W<3d(qURz**%I4pPjI~P>&E4}o}8;b0k zDfHb~hC+R9T5Vz*#oG*EioOvf7(1})41&P`30DlwtoTIWE#z}G+uZQ?3Rav=6X`SRriZ7TqkDtcg^BELSFf|Xf`ISUbm2n-982F# zg$0HmenYvCzeZEYiNO<$IRS_@xE_lvG2Qx>CdcS8EpUwD975)fg7BkI%&5vP`tg1c z9k!oB0)-VQ%E+S6?mwtC$9_{okxA?0DO|g|uV}YlSs1$`(4b_gdt2OTeeA7lOJd43 z0>9pIH-%h%KV2o_uj^B^y`2OFmKQ1>i0A5)N&xq*kW<(X$VsnasX{gU-}9{sO>f0z z)sVyW2>sR`D{%t#NXifZNUs?1*d4T*L_^S|wO^StDXT=J;)fbj=_h;*U)_(mNZDx3 zv|s~#!PFihirk5%yWOP8sqU&1wpeL(y8FmpqqQH-t&!m(8&9JF1%b;}Ve+t8TJww~ z$07rMiH*Qp_cM(6x8TVVM40K!xcH{KIKAq*vy4qFUc1PXF#~g8$oOe-M8(!39YZ)^ z>eXL~crrzEJ#<}uY9&d&_B4GoaGmqDw?|}o4fD+2l!8>FpZ|!83mfU^M<45v}?vo=h)}11tPDToGv+1jc@1Z7PCD105Q;!^KIduePjg-#cMLf z9<`O}q(qxOG;Wu z3hS`QiwLX+>;^_zHGt6Ba&zMI)zgy&Jna!ryGE9bz)!*p`7Z6;isY079U*_T7GJFi zX@;N|9tR)ltTUlB)L(nELi@OT#N>v-?AqIH)5lLHX{gMbPTzgHq0qN%;=e|`)9+53 zp?wR0Es=r>9(w1CnX@dC`pbM`6Nmd*p6!ar{8}Z?YPa6D_>^MDDYmcQ)sR-R}%s0rg$1)E^Psc<$ zf`;R4TQ5sbHT%^;t@zWA<_b@yYjVG@T1S7#dGRDQ{|nu13bhSo+}Lc5ba>kcIK|tn z_w~Z7BYy7T&)0rXD7`lQ@#M|f*N91#*UQ`o-ILDWu(*5kX7BbfEalrLUvDi_@M3{j z;mM>pX_G=4l^RU#uTFp08Hr8>fm#dBo@zqA5T92{bTa{_v!g> zEd&;%h}E=U%qqO?_&7+9)DN~wN2+?{M}~5>(QmRaFh|6)7R9o&`aaiS*;csq=_X4B z%LSqr4IMISuMqAP2*D8JB#+~etnt!{@v`>u7YVV?zq3(!2k;hQnGw-UQZ$ub2tqcw zPivffQG#K2f^>5HGYi*7mc-+vNFoll7RPCw1G%UOby`fkdYp)6O>$LCawjC3dh-rQ zMQ}d8RiuCjdCaNXo#>&63$@3gBa+I1iB=+!{sdfBKDjp<7pIu)k&G*?#5Fv|-M^41 z21!mhKTgi}PD=9T*RM(rkjE?Do9`>mw3 z*e4gdA}e2rmV?CHc*WAaBh*B0)#ZvkXlF=4Q^ey_9@?ini?B?@#ZO6J-9R8iAW#ksx!nq3RszapgLDI-SCc`qSP*Xm7_&m*9-`otC+~MYBOkz^ z5F$en=9U-Pp%A^V5G$jY7@`<=z?=@&b_FV>!kO3il6pqcAStO_Km+&HY`AES2P+6s z0k+8oQDH%j6=aYKvUoU9wiTj_2I%6UB5R`&Eai_8_;Xe&+6G?<`Uq^Dc20h= z1Daw$AIFysNoD**9mD251BKtrgIxzXn2wyh8L3T^)NDv4uU&`^{LJolYAc2qk*Z$r zYGhqr=TPeGb1xcu58+!X#d|kCJBJz$wrK^}5=l8a0JSg0$L)#)Bov1xLm;6K8I3xu zjUMM4bq^Um(>9-vCm)#A8*(=$#~P2h7yHH*n;Xa&0BKd%sZR6YZWS?`EL2(%nCz}> z&q}Bp@?PjRP&M`5iBG8lnw*971Ly-{JV!z~mnhPScXR=jj`&0_6!2Og6axqGA)%*a zRA&cfKsj^rFmuXIbDBeQAA|*ijYVvz1zV>Dam~WS=i)2E#d$ca$ei(Kp%_JdoxNU$ zs1WnQUY)XG5*NPO`lOl#4i1Nd7%D(w`1?u7O70aP9}1{|f=ZzQzR0`OD4^I1kjk7q zoY%(M!zLuh#&OQ(8k4P?jcq`nEjruQZ_YOOJ4L{pT^^&Pa4n{{BxXVbfltLo9i|0` z!LiCXT_Rb114KYHUDuo(-Uy!l)LQRDE>!{ct7uLl77ps)RjU9=1y+P3uLM=r2CO>Q z&#^UaUa^x(khy}GhYM_<`y@~#W3AcfyXLNd+gd}nGaLy}EfUQLd~~V=Wd~bM7LkFZy2FU?RXMo%?EeD ztM_&%#ndOtICLj*Bw+%f(WugqF{nFrPnuPVvLj++rK2FKr%;5+8j)P~rl*#gv;OlF zCIl7c%@Z4kYEwjShf!~*pWv;d$Ba@vHdDPSj`TVakbb}JK|jGP&%Tk~-a8M{YP{(w zHPRW-o(q@|&wo;658F28@RrQ7b z2A$?kx_jvhKfR}iZ}gqs_^a;>VZ}#u{D;l_hi;B1Z?yJq-5m*69f@%ou{R#HQ6&WV zBYvnrOwRcTNvZ{zeb2FP33g5dCYOE#|G}T0iErwn-(0ABzA#!o{-O#Kv0_=b@RCqh zNiR_u%U;CL@@q^^YHa<@5G&u11Kgw@hO-5Z^?qPN2QlM0$6fedMsSYirxSWV2Rw3O z$MUhZ^s}b%ao|>2vtv1WgC_Dd#zXtYSrNlwp8{0s*_*zPrvo{r{aGKmaLlJqEHq8T zj!z(6pVr=t61U}9XXKg-;wsTKeay`LRbuq^Tf&deSAM3A&OdTL!b}o#curk(Qa!!S zG2;-msqd=rwZjoQexY}GVbwbDyGaux5cqI)tR{tP5hE-(5bP6A{*9ZWMB%0+zA-RbnS_|`=E zCH|)QWY~vX$+T}o}9IhURT2q4MUxon(gZPnh?LusXgm+n$lU*nUh z5an6CuXLF~R?A9OFHhEJMz*Z>_11x$ZM@tSE1=W5oHc{|8si)Li&KxH-)+EQqN#75 zp5KCFnCztX^3?{F2F{c!Wt3I7sOvi>)wh&e1ymlnsgRa2_N}Yn_qlxgmR*_ws%MdUUCIy}gWO11ag!CRQWLEPU1A zB+!7p%z$pfpq$LGO53nD!LU&q-O9Cey>vZHjp;d;w$3x+y91Ne*(sIH`49P~pJz?K zF`DgLn>8kwDc;z$H(k11w%M3wS@zV@jc6HHBOt9~C7)=O(YZK$DYx_3WOfs%L<)-;HckMk8{m^7{=EsJbsy5IALylkMbS19CDn&)>xx>4+%jktN91W)e{)9;sU_YS!CZ<@RK z=wIZ5z4H0`WnLib`!~$@?leyaqVLeIZ=1Ydr|`GsXA|lH--@8ur@OA3-@9(Ue0|gQ z(DwW~a`J$gComHfL|zvN{cuQglgf26(1Q8j(tarbNm9(?4>mprjQ&MpNwI>QjG!7L zmR1~|(60rds~nEo9S{-Qf>+7K7-&0jeiSpRxi(bQ@T-U%m6Zcs&Cz<1WKwxI(H;Z< zfNM1(m_CbI)fnDs%CZjMJ1~IPKfm?+>$~+4x`yc*^)hzS5X7gsLS!em_SU73HUMEP z2{PWfxOB%b%H2TI*7Z#oB<=c}UBM5DEQA1`1geqyZ0b%D zpPv(4M~r2*#@W<~Ka&$FXdRG#&uK_Q5HBJ_Ki15h&`A_SPAc}}Au>Yx?^RE*|Dx3u zqB5?lvBkr=Ptyvv@AYu%6B36(I!rvUs&_@94L? zkC2|}iD6!i$IE|vss94^{&#-p?^7p5YoWD()qeyu{&nhP{G}1_tE@W1oSOOiZPl0U z9K_D}nVSXmvi^D4g^8wBvLbEeq#<_q(OjiGv`Ng5uiw{4IxbA!I!t|KE!NubXTZ_0 z%#g`Es(r@<6U;5xrj#rp74^nzL z;x|93=*)s&HJM4qw6DHxT`QeiJQyaL%h@gkV`>y+t*&#llK<7IN9W5HS9N`1qjPU4CAYCXa%CX?X7Q~8*cCT#Qs-^CadG>bmqjuUE@~Y# zT+O|wm2{-|WZl57(H3}hqNKCMbvv~4>UG0~4)-ka&+lm*I=(IYFVEf@o0Yjr9MQS( zV_Pgk9FMOux~E>o`pmBhV?m)yW&h~g%3#_#j~Tb>?*4Wn!=>h&r&F6;@9g}4V-^k^ z@Vizc0cOh&zr2mKdyge6!M%?yY4Cb`YqxV@C2DBuLQ&{cshf-Jw1ao0+F`<=q2z3s z-AeK%pZA&vmAnv-j`)p(3jIr#f|Zg}6TznH$184Tp;H^%>uNvHELB)R=AZ9U^jk;{ z0VdzGl4tVAAu#CP%v3)Cd>j{1*qfot{yn?Ckga>Cc%#_kV+9yM#Z(D}8rN#6D|NYg!%>hHXqfl#LgQJn> z`p)0xAdp|r_iC`R&Ogk-Rf`#Fj&kohXPJ{B=~wC}-Smbn%|io552w!zxwF;~Y$kk6 z-XMnvX+g{XqdCwwZewL}B%&bb?wS#y8P4kVF@n7eQ`f>3YN3!+>}i4YjG`v=5--RK zrtoxUFiWwqUK1wUj$ca)fA)!kn~_QT9l7k=>~_srEm^SMEUk8sNd%33st(Y8oNo}| zk(?6?b#USbfjtU|cpXZj>U%A~~Y z^2Jk)!FQwysP>S_R?8W)%dZ%pK9XpcIIE8clyHJ~e(pyI0ZIItD~tg}$32<8^G z5(E$!$&4**nX$8$AXtr4@DOK(6Nn5H%JCipK_*zfWF)5LpqNLmO`~Wh9)<;vrHj*# z>9N#2{C%?X_}gFlAm;RUf@P$A1_db+K2t-oNe>9*ABw`C?B36$nTXKkjOd3@^l5S| z6urA7wu-fKT4!150g=IOQnGD)0T1+QQ##>Oj0E{9_z8>P?H7PpXMV7n$SUI&1OfpJ zNC=4l*UY=RAIg5k>{D04&vE)g$8+CqZ<6#uM1B?~LI4XU%YHhfl>k;mlLvLbpr@^f zt+&Ag3o6jUB~!3n_$u{~_)DHzd>l{^$jBe4u`iJwEBZ?kDABh{-G4LX{aHr5$4_kg zqVzNFTQxx~*rjpJPW=q?SHUhhqrasSYFwHILII71GXsOEihU?R=yn{KJ2u&J`k(p$ zCL{cB`T&&~)HKXN1xUJbEBgiw(G7B}IQX0io7JnTc|2>W-`Xvalf_{pP`!j0TN=mZ zI`Jb<1~cs=+cE z)=hC5llC*K4veVPOTljM#xV`G>Pg!34V&o3)6)Vp;I=DRl?XRd$fKW{wl%@eeE`U` zJ+Odh7%(cDAmnXa>Zvn_yq*tjuM8R-LU=qZgk3s?}Ui*p&Egc%N zs<7NJ_#tMJTlA@=D4Ft_lg5+nMKBM5Q7^kao-TYhRAqyVPACo?&!-w!JAu}M$6--k zit#jB6@%7U`FA`@pw#mI+_@y(-yd)5z^ujrH%m46Z%Bu zqwkqEi~>9|(TUL|@)b8iwK(44O=P<(Gd`(N820a`Z~6wKGW#<^tNB(wvx~Dgkau=-FzfM31jIl=z6_ zKe|#Dq&q^0625+Kaz7~&z^+^9nUlAQAjL%nCuR%|9$@;tEwq%U^VG9QCLzms(tzlf zY0i8_Cq0n4etuX)x4ECe6b+B($Pu38NHW=RL?+uE!H!G@+XCcdz7yMEFvI@1Fjp4@RBS!h28O?cTU4YKZuDYcT5lQOo0wdoZ`W zBb5p2#*s`LjVEy&eJicJ&I5EUfk`UAR^f%iR@+k~eb6Vmzxe2d^V9bhbAvpnv4;db z^}T3l#)jbedFdD_dy*IBvx&@#e3{66-FU%ihSTiZ3l`C}^3%5;z9)}N;!F=J7|yoU z6i%$RfY;L%&X(h!{d`dOCEz|;nWA0H*jyUnuqEp3Gn%eNMGq-(quY&IBc1L^e^xfYFB)a_yOn3aegTS)_H)QrSJAM7;_g^8L zn`TiJ$G?Bz&r{5mJARIbT>Z|jWMF7I`D^Qf;2b{oPPQM7z%>;EI z0sGx}^N6_X7NQ>tMek0F-UG!&)Foo<#OkV`imT9YG=zyY(Nhsikk)UQmzbotoUMv+ zgyNj*5pUNn&I1hI^TIG_h}3aHIv(n`n*bz0lh!kjlK#Py zJp-%qX{)}7R~;KveFavt=T*C8t+tS-Mq>8M9ap@jjM3+@lpQ#F)ls-qEM?_N>R!E% z-_3Bn1B+|1lx}E>aBS=!Xpj2O&>ofkFKACprw#;16Z3(KKru8loJb*u1bSl0I6@V< zXG!@aW&vx>3;CLqt(x_lnn*z{S$8eP`&tP@S}v7&eUI}*5Ks*t8eWThkDK|fmHEaW z^W_5X7zP48(G+%U;Dib=bt{0Iq#lSQicelXO2JqG_7cUipzp;01?)RgbZlV z${l7_Q1ST^#oeXicp^{=T@at0_8;gT*bQj$p-5%D`yc3Dbl0#=)~GYor~;|^ghcO$ za{C2Khh!D7@y6s8#yA7@YZj@*A^sJB`ej#zSd*$dZsy$Lu?dyW}`L1s@!w@WiZwXm-v>5-;Wy1?JRq z=5#gygq%gRyT$8x3&{!#&Kg1PF#YMhq(xVnez^UtQdukt!*7^npoj>ox4bHK(TK8~ zY7w|TdapwX`Y(i!x|d{r_dg;0^T2|?89p_M;jaK~Y#`SPYy$diu9Dlj>ezZF+7?#W z`fu6>3fbNGP7&&17gu06OGf80mf#Rr+e(huDfEds%<~+Bi~7crNWO6cWN0y;MDo=d z!C0y$>u&Qnj@GRJBsKyt%1Nz}V71rLKzmQxJKS9LWwa^}IzBZD zHl_-Gk5fI@&$u0!1g(eYR**Fs)Tx)$k+CL>Pq)ussGE&c*Y(E8zl^?}-J$G<{R8s@ z@1R~n+3vP$b8sqjw;(iV>F$yn-c5bu;DTiB^zQ9+ii$V{ceuT&4YBX=O6yX7&}F#i zHT;3@l73k#s%yuP{_9OhC`V`b1LlG|@wYyAF}{fji;ebB?6$D)_HOfKdHjgA?Xd%I zM}2oUB8Xm-y61AYf0fFU`w#plj{K835S4)~432H3)QA<{d%notM~=N+QN7)Fd!O|7 zKDrxlloasHF#yle_oXY~v9e4%J5=D)E^{dCDM zXhvQd;1=q#v%oZVd>$&@fT+)#b{Lf8J+r>IkkVAKM#oL=iCWXJnpx? z@E}?o|I4UD$anyX8Hjl>(2|w_FYOT-uN74tly({ndO7g@E}pCHiAW{mv0p+u%g`N_ zXXb@{;>Lq*Nw8|Y$Z9(#**k(pA0jvkn^?PIInsWO=1dV3-;PMBBBCUQ_l$Um zIR>*XT#KvTSzA{9!ejavyuAMyk^|Zt^>Tle^MRAV&o-`Kt2}B<{u>%E`<%wQnnu2* zj*Y4!WHkBM`AzW^e0y_zJXKyVIB5u&!Ooo#b{Daprj|(w#8fGdLauo0o^f=;qr&q2t_W@?|sA;?iLf0cSCheU1***jDCUn&^jAg+d-Ao4=9$}t6dP3 z+;Ek|V+f;dv>A6n^F8vOkrB)j*3T>Uuo9HOMp%pdHC^T$p82 ztd>z_;Q9}|Un~65TtLp+P0qerj>PttfiQ96{Ka1rM$Stlw$Dj5XMWZKlX~%rZJ`Lx zTvV+TLjx<@SE>wgeTGuTnQ%9BC^zpbx5=n(vHbEK6f+bIrM& zC&=|1j0zF~oA#da>KMb=hlTwc=VGda+H zN%L|-joT(v=MUi@l5^PrkO=?&I5BUBc&Zk$i=thgW|EpJ1GSL0W7FLkdaHHDRd!%d zDQj4h0Ijn&qr9{1-@L4?+Hvdpd&mv@6QarT2K7^UQ*{s1?fa%X8>YL0W(W5Te=u_A zmks_ZTMaF;yt&9EQ2>!7x7ty)k_)p^>a-eGe_C<~R6}j`Yj`Mt?^Xp+}{d1bl)Z44Ni=1B**&E@1@cxgm;L~Bcv(A+% zI2v@sMSgS%nzZegIdb-8%kur!dnc1uK`v|%A_?#F&W8wky6OnO%wgW7q4`i>K26-= zl&d9;>ba?OQK=odX&m9Tby0O)UfoweHT>S`yYcF}JeN(lM@W~4^O1*({4|x?r_Awf z|KD4+w_i;Gy@Kau!os~G?-2Li?#TZBqHp1illOho=ABXdas1i#>g`W!v|p@W@s}O> zyy<^yWaZo1x8t_;mFnK}6eGVbUEc0mKe}bV=ks6QtFKejeT{UvzQ}xo`}{ihazCay@Aeus1y2pW%iI?|bH}~%U{+hcV#D@zO`4FsnDOmbLu$01q zm_o=e^#1PceN|kj!G%!cYoVs+2bb$YPg)La?i~o`gqc(x8nuPJIXgu29JyXTa=&)u zggao3IrMq>-TwKJ$hV!T_uqmhzlGl0`+a*U`21VYvmd>Z^uJeHgI#}wTspqB{3GJp zkGn^4|L-AcHPVtp9}lMd!61&tnY=xz$yLW=h7++?KwVsg>UT={ zvw>+ePsFOxX_Vfh6_qM+#X1Q#{UWodefu9glN5(G%ohGUTKjiVRkpBS5+V-1F{_D4M^{C4$mG|%6?ls-&# zJ%;fg3!9|+=OH&#im!W&#_&Kms_SKHUzIwK<|eXqEYDzNsF~TkjVn+^)+8g6*6usg zVDZ?QbGpFa>VyC1E{sY*P`8g$+t{YjLet6+Zk_pSc-sS~@n~S0?$fA)w{v+j=MY`) zq8}fZC$%1_KkGZe6=oRWlKqVz9BnMCq|==r-#(}5XCO8DY+IH`eiTZ6ALrMTt0UA0 zXP8JhA&2s6{W=?A`O|Nv%g4)X$(O_;nO~V$M;hJtZ^cVjr3i_I@~4QNgiNaa+TE{8 zmlBp0NS~gwn#@!`N+xEhzWR`L)mZ-HMxME@=w`mPtmq%c=LbqtfH7x z&}GloytQ2t4x+SYcl*<%Q@SVRbGJbFBeS%_{Ysy@^7)1f6%W4Jiq*vrohuXS0+Pf$ z!yfMOeQs*ZH4v{RS4w`-()Rnwc3S|Yk;KEulh>b1uaoC}ZmA*nZE7aZ8NBjmSW?1$ zLi2Q|tR!fQ?XNk>miR@R=I+&8l@^V7rf_?mzvlegYCX1|11L((o%G4$#|5tij@bmb z%I)k93g?6s^h3peG1`!p^0k@UIw$@aVO*UxbIbSN8x<++`##2gGktc1FJ#kDq=@)y zN$=q%%*spkCUdHIKN9wxC}H5wJRW}hQb38ZCphGl+BvKa?@5m-;OBj_+7h(OyB_N2 zVBT-VOAtwIUW!9_vbr)Y707kZ$9-g6bla9CUd7LkHEQ1d4ox5R5~@Q4|Jy*t|7cV3 JzdyXn{{lcal#u`c literal 0 HcmV?d00001 diff --git a/packages/node_modules/@node-red/editor-client/src/tours/2.2/images/subflow-labels.png b/packages/node_modules/@node-red/editor-client/src/tours/2.2/images/subflow-labels.png new file mode 100644 index 0000000000000000000000000000000000000000..80023be78c9ac9377961782cd2c168e5d9e754cf GIT binary patch literal 30101 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 Cd{%}4 literal 0 HcmV?d00001 diff --git a/packages/node_modules/@node-red/editor-client/src/tours/2.2/welcome.js b/packages/node_modules/@node-red/editor-client/src/tours/2.2/welcome.js new file mode 100644 index 000000000..f46ef3fbb --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/tours/2.2/welcome.js @@ -0,0 +1,156 @@ +export default { + version: "2.2.0", + steps: [ + { + titleIcon: "fa fa-map-o", + title: { + "en-US": "Welcome to Node-RED 2.2!", + "ja": "Node-RED 2.2へようこそ!" + }, + description: { + "en-US": "Let's take a moment to discover the new features in this release.", + "ja": "本リリースの新機能を見つけてみましょう。" + } + }, + { + title: { + "en-US": "Search history", + "ja": "検索履歴" + }, + description: { + "en-US": "

    The Search dialog now keeps a history of your searches, making it easier to go back to a previous search.

    ", + "ja": "

    検索ダイアログが検索履歴を保持するようになりました。これによって、過去の検索に戻りやすくなりました。

    " + }, + element: "#red-ui-search .red-ui-searchBox-form", + prepare(done) { + RED.search.show(); + setTimeout(done,400); + }, + complete() { + RED.search.hide(); + }, + }, + { + title: { + "en-US": "Remembering Zoom & Position", + "ja": "拡大/縮小のレベルや位置を記憶" + }, + description: { + "en-US": "

    The editor has new options to restore the zoom level and scroll position when reloading the editor.

    ", + "ja": "

    エディタを再読み込みした時に、拡大/縮小のレベルやスクロール位置を復元するための新しいオプションを利用できます。

    " + }, + element: function() { return $("#user-settings-view-store-position").parent()}, + prepare(done) { + RED.actions.invoke("core:show-user-settings") + setTimeout(done,400); + }, + complete(done) { + $("#node-dialog-ok").trigger("click"); + setTimeout(done,400); + }, + }, + { + title: { + "en-US": "New wiring actions", + "ja": "新しいワイヤー操作" + }, + // image: "images/", + description: { + "en-US": `

    A pair of new actions have been added to help with wiring nodes together:

    +
      +
    • Wire Series Of Nodes - adds a wire (if necessary) between each pair of nodes in the order they were selected.
    • +
    • Wire Node To Multiple - wires the first node selected to all of the other selected nodes.
    • +
    +

    Actions can be accessed from the Action List in the main menu.

    `, + "ja": `

    ノード接続を支援する2つの新しい操作が追加されました:

    +
      +
    • Wire Series Of Nodes - ノードを選択した順序で、各ノードのペアの間にワイヤーを(必要に応じて)追加します。
    • +
    • Wire Node To Multiple - 最初に選択したノードから、他の選択した全てのノードに対して、ワイヤーを追加します。
    • +
    +

    メインメニュー内の動作一覧から、これらの操作を利用できます。

    ` + }, + }, + { + title: { + "en-US": "Deleting nodes and reconnecting wires", + "ja": "ノードの削除とワイヤーの再接続" + }, + image: "2.2/images/delete-repair.gif", + description: { + "en-US": `

    It is now possible to delete a selection of nodes and automatically repair the wiring behind them.

    +

    This is really useful if you want to remove a node from the middle of the flow.

    +

    Hold the Ctrl (or Cmd) key when you press Delete and the nodes will be gone and the wires repaired.

    + `, + "ja": `

    選択したノードを削除した後、その背後にあるワイヤーを自動的に修復できるようになりました。

    +

    これは、フローの中からノードを削除する時に、とても便利に使えます。

    +

    Ctrl (またはCmd)キーを押しながらDeleteキーを押すと、ノードがなくなり、ワイヤーが修復されます。

    + ` + } + }, + { + title: { + "en-US": "Detaching nodes from a flow", + "ja": "フローからノードの切り離し" + }, + image: "2.2/images/detach-repair.gif", + description: { + "en-US": `

    If you want to remove a node from a flow without deleting it, + you can use the Detach Selected Nodes action.

    +

    The nodes will be removed from their flow, the wiring repaired behind them, and then attached to the mouse + so you can drop them wherever you want in the workspace.

    +

    There isn't a default keyboard shortcut assigned for this new action, but + you can add your own via the Keyboard pane of the main Settings dialog.

    `, + "ja": `

    ノードを削除することなく、フローからノードを除きたい場合は、Detach Selected Nodes操作を利用できます。

    +

    フローからノードが除かれた後、背後のワイヤーが修復され、ノードはマウスポインタにつながります。そのため、ワークスペースの好きな所にノードを配置できます。

    +

    この新しい操作に対して、デフォルトのキーボードショートカットは登録されていませんが、メイン設定ダイアログのキーボード設定から追加できます。

    ` + } + }, + { + title: { + "en-US": "More wiring tricks", + "ja": "その他のワイヤー操作" + }, + image: "2.2/images/slice.gif", + description: { + "en-US": `

    A couple more wiring tricks to share.

    +

    You can now select multiple wires by holding the Ctrl (or Cmd) key + when clicking on a wire. This makes it easier to delete multiple wires in one go.

    +

    If you hold the Ctrl (or Cmd) key, then click and drag with the right-hand mouse button, + you can slice through wires to remove them.

    `, + "ja": `

    その他のいくつかのワイヤー操作

    +

    Ctrl (またはCmd)キーを押しながらワイヤーをクリックすることで、複数のワイヤーを選択できるようになりました。これによって、複数のワイヤーを一度に削除することが簡単になりました。

    +

    Ctrl (またはCmd)キーを押しながら、マウスの右ボタンを用いてドラッグすると、ワイヤーを切って削除できます。

    ` + } + }, + { + title: { + "en-US": "Subflow Output Labels", + "ja": "サブフローの出力ラベル" + }, + image: "2.2/images/subflow-labels.png", + description: { + "en-US": "

    If a subflow has labels set for its outputs, they now get shown on the ports within the subflow template view.

    ", + "ja": "

    サブフローの出力にラベルが設定されている場合、サブフローテンプレート画面内のポートにラベルが表示されるようになりました。

    " + }, + }, + { + title: { + "en-US": "Node Updates", + "ja": "ノードの更新" + }, + // image: "images/", + description: { + "en-US": `
      +
    • The JSON node will now handle parsing Buffer payloads
    • +
    • The TCP Client nodes support TLS connections
    • +
    • The WebSocket node allows you to specify a sub-protocol when connecting
    • +
    `, + "ja": `
      +
    • JSONノードが、バッファ形式のペイロードを解析できるようになりました。
    • +
    • TCPクライアントノードが、TLS接続をサポートしました。
    • +
    • WebSocketノードで、接続時にサブプロトコルを指定できるようになりました。
    • +
    ` + } + } + ] +} From 42ecf54df6c839e0513422a7f5f01dffc7002e9c Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Tue, 21 Jun 2022 22:39:22 +0900 Subject: [PATCH 026/237] add i18n support adn English and Japanese message --- .../@node-red/editor-client/locales/en-US/editor.json | 3 ++- .../@node-red/editor-client/locales/ja/editor.json | 3 ++- .../node_modules/@node-red/editor-client/src/js/ui/tab-help.js | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index 4f3a3b104..e6fe4a4af 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -1158,7 +1158,8 @@ "tourGuide": { "takeATour": "Take a tour", "start": "Start", - "next": "Next" + "next": "Next", + "tours": "Tours" }, "diagnostics": { "title": "System Info" diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index d4a638137..791b553e8 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -1157,7 +1157,8 @@ "tourGuide": { "takeATour": "ツアーを開始", "start": "開始", - "next": "次へ" + "next": "次へ", + "tours": "ツアー" }, "languages": { "de": "ドイツ語", diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js index c6b33dbc7..09a67e338 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js @@ -218,7 +218,7 @@ RED.sidebar.help = (function() { nodeHelp, { id: "tours", - label: "Tours", + label: RED._("tourGuide.tours"), children: tours }, ]; From d66def5530346b5ed38e57bf69cece29e00a6a6e Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Tue, 21 Jun 2022 10:56:18 -0400 Subject: [PATCH 027/237] Add fix from flowforge-nr-theme https://github.com/flowforge/flowforge-nr-theme/blob/main/common/forge-common.css#L92-L98 --- .../@node-red/editor-client/src/sass/jquery.scss | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/jquery.scss b/packages/node_modules/@node-red/editor-client/src/sass/jquery.scss index 27661c459..e25617637 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/jquery.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/jquery.scss @@ -31,6 +31,8 @@ } .ui-widget-content { border: 1px solid var(--red-ui-secondary-border-color); + background: var(--red-ui-secondary-background); + color: var(--red-ui-primary-text-color); } .ui-widget-header { @@ -173,6 +175,16 @@ html .ui-button.ui-state-disabled:active { background: var(--red-ui-form-button-background); } +.ui-state-default a, +.ui-state-default a:link, +.ui-state-default a:visited, +a.ui-button, +a:link.ui-button, +a:visited.ui-button, +.ui-button{ + color: var(--red-ui-primary-text-color); +} + .ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, From 8aa922c55f0f102fc42e840d16c12d3531545370 Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Tue, 21 Jun 2022 11:02:56 -0400 Subject: [PATCH 028/237] Fix indentation --- .../@node-red/editor-client/src/sass/jquery.scss | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/jquery.scss b/packages/node_modules/@node-red/editor-client/src/sass/jquery.scss index e25617637..ec76049f9 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/jquery.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/jquery.scss @@ -27,12 +27,12 @@ } .ui-widget.ui-widget-content { - border: 1px solid var(--red-ui-tertiary-border-color); + border: 1px solid var(--red-ui-tertiary-border-color); } .ui-widget-content { - border: 1px solid var(--red-ui-secondary-border-color); - background: var(--red-ui-secondary-background); - color: var(--red-ui-primary-text-color); + border: 1px solid var(--red-ui-secondary-border-color); + background: var(--red-ui-secondary-background); + color: var(--red-ui-primary-text-color); } .ui-widget-header { @@ -182,7 +182,7 @@ a.ui-button, a:link.ui-button, a:visited.ui-button, .ui-button{ - color: var(--red-ui-primary-text-color); + color: var(--red-ui-primary-text-color); } .ui-state-hover, From 2ea10206faa4892426b8be3b0284ab1e96ad2cd7 Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Tue, 21 Jun 2022 11:43:02 -0400 Subject: [PATCH 029/237] Fix select box alignment --- .../node_modules/@node-red/nodes/core/common/20-inject.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/nodes/core/common/20-inject.html b/packages/node_modules/@node-red/nodes/core/common/20-inject.html index de3990a93..0fafa9df0 100644 --- a/packages/node_modules/@node-red/nodes/core/common/20-inject.html +++ b/packages/node_modules/@node-red/nodes/core/common/20-inject.html @@ -118,7 +118,7 @@ .inject-time-row { padding-left: 110px; } - .inject-time-row select { + .inject-time-row:not(#inject-time-row-interval) select { margin: 3px 0; } .inject-time-days label { From 6939546f22c9cedf0cb16b762ff1639dd4d6af4c Mon Sep 17 00:00:00 2001 From: ralphwetzel Date: Wed, 22 Jun 2022 19:58:02 +0200 Subject: [PATCH 030/237] Disable keyboard shortcut mapping when showing Edit[..]Dialog --- .../editor-client/src/js/ui/editor.js | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js index fb4c200f5..0ea00621e 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js @@ -1033,6 +1033,8 @@ RED.editor = (function() { }) }, open: function(tray, done) { + RED.keyboard.disable(); + if (editing_node.hasOwnProperty('outputs')) { editing_node.__outputs = editing_node.outputs; } @@ -1075,6 +1077,8 @@ RED.editor = (function() { }); }, close: function() { + RED.keyboard.enable(); + if (RED.view.state() != RED.state.IMPORT_DRAGGING) { RED.view.state(RED.state.DEFAULT); } @@ -1178,6 +1182,8 @@ RED.editor = (function() { }) }, open: function(tray, done) { + RED.keyboard.disable(); + var trayHeader = tray.find(".red-ui-tray-header"); var trayBody = tray.find('.red-ui-tray-body'); var trayFooter = tray.find(".red-ui-tray-footer"); @@ -1258,6 +1264,8 @@ RED.editor = (function() { }); }, close: function() { + RED.keyboard.enable(); + RED.workspaces.refresh(); activeEditPanes.forEach(function(pane) { @@ -1635,6 +1643,8 @@ RED.editor = (function() { }) }, open: function(tray, done) { + RED.keyboard.disable(); + var trayFooter = tray.find(".red-ui-tray-footer"); var trayFooterLeft = $("
    ", { class: "red-ui-tray-footer-left" @@ -1666,6 +1676,8 @@ RED.editor = (function() { }); }, close: function() { + RED.keyboard.enable(); + if (RED.view.state() != RED.state.IMPORT_DRAGGING) { RED.view.state(RED.state.DEFAULT); } @@ -1755,6 +1767,8 @@ RED.editor = (function() { }) }, open: function(tray, done) { + RED.keyboard.disable(); + var trayFooter = tray.find(".red-ui-tray-footer"); var trayFooterLeft = $("
    ", { class: "red-ui-tray-footer-left" @@ -1776,6 +1790,8 @@ RED.editor = (function() { }, close: function() { + RED.keyboard.enable(); + if (RED.view.state() != RED.state.IMPORT_DRAGGING) { RED.view.state(RED.state.DEFAULT); } @@ -1888,6 +1904,8 @@ RED.editor = (function() { }) }, open: function(tray, done) { + RED.keyboard.disable(); + var trayFooter = tray.find(".red-ui-tray-footer"); var trayBody = tray.find('.red-ui-tray-body'); trayBody.parent().css('overflow','hidden'); @@ -1915,6 +1933,8 @@ RED.editor = (function() { }); }, close: function() { + RED.keyboard.enable(); + if (RED.view.state() != RED.state.IMPORT_DRAGGING) { RED.view.state(RED.state.DEFAULT); } From d2e84925f7738dcceda6465627e4945d20b23ffc Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 22 Jun 2022 21:43:25 +0100 Subject: [PATCH 031/237] Set default editor to monaco in absence of user preference --- .../src/js/ui/editors/code-editor.js | 16 ++++++++-------- .../@node-red/runtime/lib/api/settings.js | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editor.js index 13ed25611..7cee2026b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editor.js @@ -21,7 +21,7 @@ const MONACO = "monaco"; const ACE = "ace"; - const defaultEditor = ACE; + const defaultEditor = MONACO; const DEFAULT_SETTINGS = { lib: defaultEditor, options: {} }; var selectedCodeEditor = null; var initialised = false; @@ -48,12 +48,12 @@ } function create(options) { - //TODO: (quandry - for consideration) + //TODO: (quandry - for consideration) // Below, I had to create a hidden element if options.id || options.element is not in the DOM - // I have seen 1 node calling `this.editor = RED.editor.createEditor()` with an + // I have seen 1 node calling `this.editor = RED.editor.createEditor()` with an // invalid (non existing html element selector) (e.g. node-red-contrib-components does this) - // This causes monaco to throw an error when attempting to hook up its events to the dom & the rest of the 'oneditperapre' - // code is thus skipped. + // This causes monaco to throw an error when attempting to hook up its events to the dom & the rest of the 'oneditperapre' + // code is thus skipped. // In ACE mode, creating an ACE editor (with an invalid ID) allows the editor to be created (but obviously there is no UI) // Because one (or more) contrib nodes have left this bad code in place, how would we handle this? // For compatibility, I have decided to create a hidden element so that at least an editor is created & errors do not occur. @@ -79,7 +79,7 @@ return this.editor.create(options);//fallback to ACE } } - + return { init: init, /** @@ -91,7 +91,7 @@ }, /** * Get user selected code editor - * @return {string} Returns + * @return {string} Returns * @memberof RED.editor.codeEditor */ get editor() { @@ -104,4 +104,4 @@ */ create: create } -})(); \ No newline at end of file +})(); diff --git a/packages/node_modules/@node-red/runtime/lib/api/settings.js b/packages/node_modules/@node-red/runtime/lib/api/settings.js index f56b8ab61..9bf640227 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/settings.js +++ b/packages/node_modules/@node-red/runtime/lib/api/settings.js @@ -91,7 +91,7 @@ var api = module.exports = { safeSettings.context = runtime.nodes.listContextStores(); if (runtime.settings.editorTheme && runtime.settings.editorTheme.codeEditor) { safeSettings.codeEditor = runtime.settings.editorTheme.codeEditor || {}; - safeSettings.codeEditor.lib = safeSettings.codeEditor.lib || "ace"; + safeSettings.codeEditor.lib = safeSettings.codeEditor.lib || "monaco"; safeSettings.codeEditor.options = safeSettings.codeEditor.options || {}; } safeSettings.libraries = runtime.library.getLibraries(); From 4bd71da056a7fbd3acbc233f0375ffe6a38643be Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Thu, 23 Jun 2022 16:52:43 +0900 Subject: [PATCH 032/237] fix credential type input item of subflow template --- .../@node-red/editor-client/src/js/ui/editors/envVarList.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js index c1e1a0217..41a528e21 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js @@ -94,6 +94,11 @@ RED.editor.envVarList = (function() { } opt.ui.label = opt.ui.label || {}; opt.ui.type = opt.ui.type || "input"; + if ((opt.ui.type === "cred") && + opt.ui.opts && + opt.ui.opts.types) { + opt.ui.type = "input"; + } var uiRow = $('
    ').appendTo(container).hide(); // save current info for reverting on cancel From d1efd2137ad6bd89588db4e99e5a14d9742ee27a Mon Sep 17 00:00:00 2001 From: Dennis Neufeld Date: Sat, 25 Jun 2022 16:24:28 +0200 Subject: [PATCH 033/237] Add german contextMenu translation --- .../@node-red/editor-client/locales/de/editor.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/locales/de/editor.json b/packages/node_modules/@node-red/editor-client/locales/de/editor.json index ba3007f51..f47ebf5cf 100755 --- a/packages/node_modules/@node-red/editor-client/locales/de/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/de/editor.json @@ -1185,5 +1185,11 @@ "missing-config": "__prop__: Konfigurations-Node fehlt", "validation-error": "__prop__: Validierungsfehler: __node__, __id__: __error__" } + }, + "contextMenu": { + "insert": "Einfügen", + "node": "Node", + "junction": "Kreuzung", + "linkNodes": "Verknüpfe Nodes" } } From 5393fa81b906f1ba2c943e913a5d3c19e6707bff Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Sat, 25 Jun 2022 20:27:32 -0400 Subject: [PATCH 034/237] Leave Monaco theme commented out by default --- packages/node_modules/node-red/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/node-red/settings.js b/packages/node_modules/node-red/settings.js index d8f879851..99fa6437f 100644 --- a/packages/node_modules/node-red/settings.js +++ b/packages/node_modules/node-red/settings.js @@ -401,7 +401,7 @@ module.exports = { * packages/node_modules/@node-red/editor-client/src/vendor/monaco/dist/theme * e.g. "tomorrow-night", "upstream-sunburst", "github", "my-theme" */ - theme: "vs", + // theme: "vs", /** other overrides can be set e.g. fontSize, fontFamily, fontLigatures etc. * for the full list, see https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html */ From cd71b3daf3cccffa88d7542dacac1a125233617e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=84=E5=BB=BA=E5=9B=BD?= <50353452+hotlong@users.noreply.github.com> Date: Sun, 26 Jun 2022 13:02:16 +0800 Subject: [PATCH 035/237] =?UTF-8?q?fix=20chinese=20translate:=20=E7=99=BB?= =?UTF-8?q?=E9=99=86=20->=20=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../editor-client/locales/zh-CN/editor.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/zh-CN/editor.json b/packages/node_modules/@node-red/editor-client/locales/zh-CN/editor.json index af3e833da..eca5878ae 100644 --- a/packages/node_modules/@node-red/editor-client/locales/zh-CN/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/zh-CN/editor.json @@ -97,7 +97,7 @@ "rename": "重命名", "delete": "删除", "keyboardShortcuts": "键盘快捷方式", - "login": "登陆", + "login": "登录", "logout": "退出", "editPalette": "节点管理", "other": "其他", @@ -122,16 +122,16 @@ "zoom-in": "放大" }, "user": { - "loggedInAs": "作为 __name__ 登陆", + "loggedInAs": "作为 __name__ 登录", "username": "账号", "password": "密码", - "login": "登陆", - "loginFailed": "登陆失败", + "login": "登录", + "loginFailed": "登录失败", "notAuthorized": "未授权", "errors": { - "settings": "设置信息需要登陆后才能访问", - "deploy": "改动需要登陆后才能部署", - "notAuthorized": "此操作需要登陆后才能执行" + "settings": "设置信息需要登录后才能访问", + "deploy": "改动需要登录后才能部署", + "notAuthorized": "此操作需要登录后才能执行" } }, "notification": { From 78327716a4ed027582723fdc8094645513eed0d1 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Sun, 26 Jun 2022 09:08:26 +0100 Subject: [PATCH 036/237] ensure workspace clean after undoing dropped node --- .../@node-red/editor-client/src/js/ui/view-tools.js | 5 +++-- .../node_modules/@node-red/editor-client/src/js/ui/view.js | 3 ++- .../node_modules/@node-red/nodes/core/common/21-debug.html | 2 +- .../node_modules/@node-red/nodes/core/common/60-link.html | 2 +- .../@node-red/nodes/core/function/10-function.html | 2 +- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js index 888fb4d7f..a781c44fc 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js @@ -979,13 +979,14 @@ RED.view.tools = (function() { * - it uses ` ` - where N is the next available integer that * doesn't clash with any existing nodes of that type * @param {Object} node The node to set the name of - if not provided, uses current selection + * @param {{ renameBlank: boolean, renameClash: boolean, generateHistory: boolean }} options Possible options are `renameBlank`, `renameClash` and `generateHistory` */ function generateNodeNames(node, options) { - options = options || { + options = Object.assign({ renameBlank: true, renameClash: true, generateHistory: true - } + }, options) let nodes = node; if (node) { if (!Array.isArray(node)) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index b55244d0f..0ddc06a8c 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -5872,6 +5872,7 @@ RED.view = (function() { * @private */ function createNode(type, x, y, z) { + const wasDirty = RED.nodes.dirty() var m = /^subflow:(.+)$/.exec(type); var activeSubflow = z ? RED.nodes.subflow(z) : null; if (activeSubflow && m) { @@ -5930,7 +5931,7 @@ RED.view = (function() { var historyEvent = { t: "add", nodes: [nn.id], - dirty: RED.nodes.dirty() + dirty: wasDirty } if (activeSubflow) { var subflowRefresh = RED.subflow.refresh(true); diff --git a/packages/node_modules/@node-red/nodes/core/common/21-debug.html b/packages/node_modules/@node-red/nodes/core/common/21-debug.html index 62da1b259..f861f518b 100644 --- a/packages/node_modules/@node-red/nodes/core/common/21-debug.html +++ b/packages/node_modules/@node-red/nodes/core/common/21-debug.html @@ -558,7 +558,7 @@ onadd: function() { if (this.name === '_DEFAULT_') { this.name = '' - RED.actions.invoke("core:generate-node-names", this) + RED.actions.invoke("core:generate-node-names", this, {generateHistory: false}) } } }); diff --git a/packages/node_modules/@node-red/nodes/core/common/60-link.html b/packages/node_modules/@node-red/nodes/core/common/60-link.html index f592bcd3d..4b8c9a3d6 100644 --- a/packages/node_modules/@node-red/nodes/core/common/60-link.html +++ b/packages/node_modules/@node-red/nodes/core/common/60-link.html @@ -221,7 +221,7 @@ function onAdd() { if (this.name === '_DEFAULT_') { this.name = '' - RED.actions.invoke("core:generate-node-names", this) + RED.actions.invoke("core:generate-node-names", this, {generateHistory: false}) } for (var i=0;i Date: Sun, 26 Jun 2022 10:17:16 +0100 Subject: [PATCH 037/237] Fix delay rate limit last timing when empty --- .../@node-red/nodes/core/function/89-delay.js | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/function/89-delay.js b/packages/node_modules/@node-red/nodes/core/function/89-delay.js index 5205a5b18..6524aa040 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-delay.js +++ b/packages/node_modules/@node-red/nodes/core/function/89-delay.js @@ -275,18 +275,22 @@ module.exports = function(RED) { if (msg.hasOwnProperty("flush")) { var len = node.buffer.length; if (typeof(msg.flush) == 'number') { len = Math.min(Math.floor(msg.flush),len); } - while (len > 0) { - const msgInfo = node.buffer.shift(); - if (Object.keys(msgInfo.msg).length > 1) { - node.send(msgInfo.msg); - msgInfo.done(); - } - len = len - 1; - } - if (node.buffer.length === 0) { + if (len === 0) { clearInterval(node.intervalID); node.intervalID = -1; } + else { + while (len > 0) { + const msgInfo = node.buffer.shift(); + if (Object.keys(msgInfo.msg).length > 1) { + node.send(msgInfo.msg); + msgInfo.done(); + } + len = len - 1; + } + clearInterval(node.intervalID); + node.intervalID = setInterval(sendMsgFromBuffer, node.rate); + } node.status({fill:"blue",shape:"dot",text:node.buffer.length}); done(); } From f8ad75329d648799d9a47f130b97b00210e8c622 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Sun, 26 Jun 2022 11:52:55 +0100 Subject: [PATCH 038/237] use $node-port-background as config node icon background Makse sense since it contrasts the $node-port-label-color used for icon text Also, only show pointer if node icon has count --- .../@node-red/editor-client/src/sass/tab-config.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss index 5c8d0ba94..f88fce07a 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss @@ -37,7 +37,7 @@ ul.red-ui-sidebar-node-config-list { } .red-ui-palette-node { overflow: hidden; - + cursor: default; &.selected { border-color: transparent; box-shadow: 0 0 0 2px $node-selected-color; @@ -58,7 +58,7 @@ ul.red-ui-sidebar-node-config-list { .red-ui-palette-icon-container { font-size: 12px; line-height: 30px; - background-color: $node-icon-background-color; + background-color: $node-port-background; border-top-right-radius: 4px; border-bottom-right-radius: 4px; a { @@ -68,6 +68,7 @@ ul.red-ui-sidebar-node-config-list { left: 0; right: 0; color: $node-port-label-color; + cursor: pointer; &:hover { text-decoration: none; background: $node-port-background-hover; From 125b37e98f6d33894593a907b3d25339791709f2 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Sun, 26 Jun 2022 12:16:39 +0100 Subject: [PATCH 039/237] increase quick-add height to reveal 2 most recent fixes #3568 --- .../node_modules/@node-red/editor-client/src/sass/search.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/search.scss b/packages/node_modules/@node-red/editor-client/src/sass/search.scss index cce1e69e4..168bc7a94 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/search.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/search.scss @@ -54,7 +54,7 @@ } .red-ui-search-results-container { display: none; - height: 150px; + height: 195px; .red-ui-editableList-container { border: 1px dashed $primary-border-color; border-top: 1px solid $secondary-border-color; From 495a81768d5b3da535fd6341c7fffe8cbaa5904d Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Sun, 26 Jun 2022 16:27:11 +0100 Subject: [PATCH 040/237] Delay node - add example for simple queue with release --- .../delay/06 - Simple Queue with release | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 packages/node_modules/@node-red/nodes/examples/function/delay/06 - Simple Queue with release diff --git a/packages/node_modules/@node-red/nodes/examples/function/delay/06 - Simple Queue with release b/packages/node_modules/@node-red/nodes/examples/function/delay/06 - Simple Queue with release new file mode 100644 index 000000000..51d7595f2 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/function/delay/06 - Simple Queue with release @@ -0,0 +1,149 @@ +[ + { + "id": "48d660b3a4109400", + "type": "inject", + "z": "9e5f48c16729e4f0", + "name": "inject", + "props": [ + { + "p": "payload" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 185, + "y": 795, + "wires": [ + [ + "e0f9e206681f3504" + ] + ] + }, + { + "id": "e0f9e206681f3504", + "type": "delay", + "z": "9e5f48c16729e4f0", + "name": "", + "pauseType": "rate", + "timeout": "5", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "30", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "allowrate": false, + "outputs": 1, + "x": 430, + "y": 795, + "wires": [ + [ + "e470f1d794e1bef9", + "af7cea1dfb797a75" + ] + ] + }, + { + "id": "943543cf7a1958e4", + "type": "change", + "z": "9e5f48c16729e4f0", + "name": "set flush to 1", + "rules": [ + { + "t": "set", + "p": "flush", + "pt": "msg", + "to": "1", + "tot": "num" + }, + { + "t": "delete", + "p": "payload", + "pt": "msg" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 510, + "y": 915, + "wires": [ + [ + "e0f9e206681f3504" + ] + ] + }, + { + "id": "e470f1d794e1bef9", + "type": "function", + "z": "9e5f48c16729e4f0", + "name": "Do something that takes a few seconds", + "func": "\n//send on the message between 3 and 6 seconds later\nsetTimeout(\n function() { \n node.send(msg) \n }, \n Math.random() * 3000 + 3000\n);\nreturn null;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 760, + "y": 795, + "wires": [ + [ + "943543cf7a1958e4", + "859258551b8389b7" + ] + ] + }, + { + "id": "af7cea1dfb797a75", + "type": "debug", + "z": "9e5f48c16729e4f0", + "name": "IN", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "statusVal": "", + "statusType": "auto", + "x": 710, + "y": 735, + "wires": [] + }, + { + "id": "859258551b8389b7", + "type": "debug", + "z": "9e5f48c16729e4f0", + "name": "OUT", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "statusVal": "", + "statusType": "auto", + "x": 895, + "y": 735, + "wires": [] + }, + { + "id": "ecaaf26326da10ee", + "type": "comment", + "z": "9e5f48c16729e4f0", + "name": "Simple Queue with release", + "info": "This example shows how to use a delay node set to rate limit mode as a simple queue to feed a\nprocess that may take some time to complete. Once that process completes the feedback is then\nset to flush out the next message - thus running the \"loop\" as fast as possible with no overlaps.\n\n**Note**: only the `msg.flush` property msut be set - otherwise the other properties that are fed \nback will be added as another new message to the queue.", + "x": 235, + "y": 915, + "wires": [] + } +] \ No newline at end of file From ab1e38dde81bb1325d02d574a990e9b8772cafa0 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Mon, 27 Jun 2022 23:56:24 +0900 Subject: [PATCH 041/237] Fix use default button for node icon --- .../editor-client/src/js/ui/editors/panes/appearance.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js index f4534965d..912fa3528 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js @@ -37,8 +37,7 @@ if (!node._def.defaults || !node._def.defaults.hasOwnProperty("icon")) { var icon = $("#red-ui-editor-node-icon").val()||""; if (!this.isDefaultIcon) { - if ((icon !== node.icon) && - (icon !== "")) { + if ((node.icon && icon !== node.icon) || (!node.icon && icon !== "")) { editState.changes.icon = node.icon; node.icon = icon; editState.changed = true; From 2f1f587c50a24355e3329bc15030d5eebfdb7a06 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Mon, 27 Jun 2022 18:03:14 +0100 Subject: [PATCH 042/237] Use HTTP body instead of header for setting flows run state --- .../node_modules/@node-red/editor-api/lib/admin/flows.js | 2 +- .../node_modules/@node-red/editor-client/src/js/ui/deploy.js | 5 +---- test/unit/@node-red/editor-api/lib/admin/flows_spec.js | 4 ++-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/flows.js b/packages/node_modules/@node-red/editor-api/lib/admin/flows.js index 611d9c2ca..2ad233f8f 100644 --- a/packages/node_modules/@node-red/editor-api/lib/admin/flows.js +++ b/packages/node_modules/@node-red/editor-api/lib/admin/flows.js @@ -83,7 +83,7 @@ module.exports = { postState: function(req,res) { const opts = { user: req.user, - requestedState: req.get("Node-RED-Flow-Run-State-Change")||"", + requestedState: req.body.state||"", req: apiUtils.getRequestLogObject(req) } runtimeAPI.flows.setState(opts).then(function(result) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js index 69460f8cf..ab36df9ff 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js @@ -306,10 +306,7 @@ RED.deploy = (function() { $.ajax({ url:"flows/state", type: "POST", - data: {state: state}, - headers: { - "Node-RED-Flow-Run-State-Change": state - } + data: {state: state} }).done(function(data,textStatus,xhr) { if (deployWasEnabled) { $("#red-ui-header-button-deploy").removeClass("disabled"); diff --git a/test/unit/@node-red/editor-api/lib/admin/flows_spec.js b/test/unit/@node-red/editor-api/lib/admin/flows_spec.js index ba09c9fa1..9ec6a3bc9 100644 --- a/test/unit/@node-red/editor-api/lib/admin/flows_spec.js +++ b/test/unit/@node-red/editor-api/lib/admin/flows_spec.js @@ -254,7 +254,7 @@ describe("api/admin/flows", function() { request(app) .post('/flows/state') .set('Accept', 'application/json') - .set('Node-RED-Flow-Run-State-Change', 'stop') + .send({state:'stop'}) .expect(200) .end(function (err, res) { if (err) { @@ -295,7 +295,7 @@ describe("api/admin/flows", function() { request(app) .post('/flows/state') .set('Accept', 'application/json') - .set('Node-RED-Flow-Run-State-Change', 'bad-state') + .send({state:'bad-state'}) .expect(400) .end(function(err,res) { if (err) { From 51baed493220261d33d84a31f464c22b1f19676a Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Mon, 27 Jun 2022 18:06:53 +0100 Subject: [PATCH 043/237] default stop/start feature to `enabled:false` --- .../@node-red/editor-api/lib/admin/index.js | 2 +- .../@node-red/editor-client/src/js/runtime.js | 6 +++--- .../@node-red/editor-client/src/js/ui/deploy.js | 2 +- .../node_modules/@node-red/runtime/lib/api/flows.js | 2 +- .../@node-red/runtime/lib/api/settings.js | 8 ++++---- packages/node_modules/node-red/settings.js | 12 ++++++------ 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/index.js b/packages/node_modules/@node-red/editor-api/lib/admin/index.js index 078779f5a..8406fa8e9 100644 --- a/packages/node_modules/@node-red/editor-api/lib/admin/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/admin/index.js @@ -56,7 +56,7 @@ module.exports = { // Flows/state adminApp.get("/flows/state", needsPermission("flows.read"), flows.getState, apiUtil.errorHandler); - if (!settings.runtimeState || settings.runtimeState.enabled !== false) { + if (settings.runtimeState && settings.runtimeState.enabled === true) { adminApp.post("/flows/state", needsPermission("flows.write"), flows.postState, apiUtil.errorHandler); } diff --git a/packages/node_modules/@node-red/editor-client/src/js/runtime.js b/packages/node_modules/@node-red/editor-client/src/js/runtime.js index 939517877..49960e382 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/runtime.js +++ b/packages/node_modules/@node-red/editor-client/src/js/runtime.js @@ -1,12 +1,12 @@ RED.runtime = (function() { let state = "" - let settings = {ui: true, enabled: true}; + let settings = {ui: false, enabled: false}; const STOPPED = "stopped" const STARTED = "started" return { init: function() { // refresh the current runtime status from server - settings = RED.settings.runtimeState; + settings = Object.assign({}, settings, RED.settings.runtimeState); RED.runtime.requestState() // {id:"flows-run-state", started: false, state: "stopped", retain:true} @@ -29,7 +29,7 @@ RED.runtime = (function() { // disable pointer events on node buttons (e.g. inject/debug nodes) $(".red-ui-flow-node-button").toggleClass("red-ui-flow-node-button-stopped", state === STOPPED) // show/hide Start/Stop based on current state - if(!RED.settings.runtimeState || RED.settings.runtimeState.ui !== false) { + if(settings.enabled === true && settings.ui === true) { RED.menu.setVisible("deploymenu-item-runtime-stop", state === STARTED) RED.menu.setVisible("deploymenu-item-runtime-start", state === STOPPED) } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js index ab36df9ff..3766763c7 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js @@ -69,7 +69,7 @@ RED.deploy = (function() { {id:"deploymenu-item-node",toggle:"deploy-type",icon:"red/images/deploy-nodes.svg",label:RED._("deploy.modifiedNodes"),sublabel:RED._("deploy.modifiedNodesDesc"),onselect:function(s) { if(s){changeDeploymentType("nodes")}}}, null ] - if(!RED.settings.runtimeState || RED.settings.runtimeState.ui !== false) { + if(RED.settings.runtimeState && RED.settings.runtimeState.ui === true) { mainMenuItems.push({id:"deploymenu-item-runtime-start", icon:"red/images/start.svg",label:"Start"/*RED._("deploy.startFlows")*/,sublabel:"Start Flows" /*RED._("deploy.startFlowsDesc")*/,onselect:"core:start-flows", visible:false}) mainMenuItems.push({id:"deploymenu-item-runtime-stop", icon:"red/images/stop.svg",label:"Stop"/*RED._("deploy.startFlows")*/,sublabel:"Stop Flows" /*RED._("deploy.startFlowsDesc")*/,onselect:"core:stop-flows", visible:false}) } diff --git a/packages/node_modules/@node-red/runtime/lib/api/flows.js b/packages/node_modules/@node-red/runtime/lib/api/flows.js index 83ef68021..b3c471a5a 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/flows.js +++ b/packages/node_modules/@node-red/runtime/lib/api/flows.js @@ -310,7 +310,7 @@ var api = module.exports = { } } - if(runtime.settings.runtimeState ? runtime.settings.runtimeState.enabled === false : false) { + if(!runtime.settings.runtimeState || runtime.settings.runtimeState.enabled !== true) { throw (makeError("Method Not Allowed", "not_allowed", 405)) } switch (opts.requestedState) { diff --git a/packages/node_modules/@node-red/runtime/lib/api/settings.js b/packages/node_modules/@node-red/runtime/lib/api/settings.js index 7e17f57b5..6c13596ce 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/settings.js +++ b/packages/node_modules/@node-red/runtime/lib/api/settings.js @@ -153,11 +153,11 @@ var api = module.exports = { } safeSettings.runtimeState = { - //unless runtimeState.ui and runtimeState.enabled are explicitly false, they will default to true. - enabled: (runtime.settings.runtimeState && runtime.settings.runtimeState.enabled === false) ? false : true, - ui: (runtime.settings.runtimeState && runtime.settings.runtimeState.ui === false) ? false : true + //unless runtimeState.ui and runtimeState.enabled are explicitly true, they will default to false. + enabled: !!runtime.settings.runtimeState && runtime.settings.runtimeState.enabled === true, + ui: !!runtime.settings.runtimeState && runtime.settings.runtimeState.ui === true } - if(safeSettings.runtimeState.enabled === false) { + if(safeSettings.runtimeState.enabled !== true) { safeSettings.runtimeState.ui = false; // cannot have UI without endpoint } diff --git a/packages/node_modules/node-red/settings.js b/packages/node_modules/node-red/settings.js index 26e5ca8de..fae6c8079 100644 --- a/packages/node_modules/node-red/settings.js +++ b/packages/node_modules/node-red/settings.js @@ -269,17 +269,17 @@ module.exports = { ui: true, }, /** Configure runtimeState options - * - enabled: When `enabled` is `true` (or unset), runtime Start/Stop will - * be available at http://localhost:1880/flows/state - * - ui: When `ui` is `true` (or unset), the action `core:start-flows` and - * `core:stop-flows` be available to logged in users of node-red editor + * - enabled: When `enabled` is `true` flows runtime can be Started/Stoped + * by POSTing to available at http://localhost:1880/flows/state + * - ui: When `ui` is `true`, the action `core:start-flows` and + * `core:stop-flows` will be available to logged in users of node-red editor * Also, the deploy menu (when set to default) will show a stop or start button */ runtimeState: { /** enable or disable flows/state endpoint. Must be set to `false` to disable */ - enabled: true, + enabled: false, /** show or hide runtime stop/start options in the node-red editor. Must be set to `false` to hide */ - ui: true, + ui: false, }, /** Configure the logging output */ logging: { From 1b8a4577d5107e5704f51ad611729b1e9ee5bcc9 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Mon, 27 Jun 2022 18:07:22 +0100 Subject: [PATCH 044/237] improve UI, i18n and layout of stop/start feature --- .../editor-client/locales/en-US/editor.json | 11 + .../editor-client/src/js/ui/deploy.js | 46 +- .../editor-client/src/js/ui/view_copy.js | 6287 +++++++++++++++++ .../editor-client/src/sass/header.scss | 2 +- .../@node-red/runtime/lib/api/flows.js | 2 +- 5 files changed, 6328 insertions(+), 20 deletions(-) create mode 100644 packages/node_modules/@node-red/editor-client/src/js/ui/view_copy.js diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index e3028272b..b25885e21 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -288,6 +288,17 @@ "copyMessageValue": "Value copied", "copyMessageValue_truncated": "Truncated value copied" }, + "stopstart":{ + "status": { + "state_changed": "Flows runtime has been changed to '__state__' state" + }, + "errors": { + "notAllowed": "Method not allowed", + "notAuthorized": "Not authorized", + "notFound": "Not found", + "noResponse": "No response from server" + } + }, "deploy": { "deploy": "Deploy", "full": "Full", diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js index 3766763c7..46408cb6c 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js @@ -297,41 +297,51 @@ RED.deploy = (function() { $(".red-ui-deploy-button-spinner").hide(); } function stopStartFlows(state) { - const startTime = Date.now(); - const deployWasEnabled = !$("#red-ui-header-button-deploy").hasClass("disabled"); - deployInflight = true; - deployButtonSetBusy(); - shadeShow(); - RED.runtime.updateState(state); + const startTime = Date.now() + const deployWasEnabled = !$("#red-ui-header-button-deploy").hasClass("disabled") + deployInflight = true + deployButtonSetBusy() + shadeShow() + RED.runtime.updateState(state) $.ajax({ url:"flows/state", type: "POST", data: {state: state} }).done(function(data,textStatus,xhr) { if (deployWasEnabled) { - $("#red-ui-header-button-deploy").removeClass("disabled"); + $("#red-ui-header-button-deploy").removeClass("disabled") } - RED.runtime.updateState((data && data.state) || "unknown" ) - RED.notify('

    Done

    ',"success"); + RED.runtime.updateState((data && data.state) || "unknown") + RED.notify(RED._("stopstart.status.state_changed", data), "success") }).fail(function(xhr,textStatus,err) { if (deployWasEnabled) { - $("#red-ui-header-button-deploy").removeClass("disabled"); + $("#red-ui-header-button-deploy").removeClass("disabled") } if (xhr.status === 401) { - RED.notify("Not authorized" ,"error"); + RED.notify(RED._("notification.error", { message: RED._("stopstart.errors.notAuthorized") }), "error") + } else if (xhr.status === 404) { + RED.notify(RED._("notification.error", { message: RED._("stopstart.errors.notFound") }), "error") + } else if (xhr.status === 405) { + RED.notify(RED._("notification.error", { message: RED._("stopstart.errors.notAllowed") }), "error") } else if (xhr.responseText) { - RED.notify("Operation failed: " + xhr.responseText,"error"); + const errorDetail = { message: err ? (err + "") : "" } + try { + errorDetail.message = JSON.parse(xhr.responseText).message + } finally { + errorDetail.message = errorDetail.message || xhr.responseText + } + RED.notify(RED._("notification.error", errorDetail), "error") } else { - RED.notify("Operation failed: no response","error"); + RED.notify(RED._("notification.error", { message: RED._("stopstart.errors.noResponse") }), "error") } RED.runtime.requestState() }).always(function() { - const delta = Math.max(0,300-(Date.now()-startTime)); - setTimeout(function() { - deployButtonClearBusy(); + const delta = Math.max(0, 300 - (Date.now() - startTime)) + setTimeout(function () { + deployButtonClearBusy() shadeHide() - deployInflight = false; - },delta); + deployInflight = false + }, delta); }); } function restart() { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view_copy.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view_copy.js new file mode 100644 index 000000000..569fee1d5 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view_copy.js @@ -0,0 +1,6287 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + + + /*
    #red-ui-workspace-chart + * \- "outer" + * \- + * \- .red-ui-workspace-chart-event-layer "eventLayer" + * |- .red-ui-workspace-chart-background + * |- .red-ui-workspace-chart-grid "gridLayer" + * |- "groupLayer" + * |- "groupSelectLayer" + * |- "linkLayer" + * |- "junctionLayer" + * |- "dragGroupLayer" + * |- "nodeLayer" + */ + +RED.view = (function() { + var space_width = 5000, + space_height = 5000, + lineCurveScale = 0.75, + scaleFactor = 1, + node_width = 100, + node_height = 30, + dblClickInterval = 650; + + var touchLongPressTimeout = 1000, + startTouchDistance = 0, + startTouchCenter = [], + moveTouchCenter = [], + touchStartTime = 0; + + var workspaceScrollPositions = {}; + var entryCoordinates = {x:-1, y:-1}; + var gridSize = 20; + var snapGrid = false; + + var activeSpliceLink; + var spliceActive = false; + var spliceTimer; + var groupHoverTimer; + + var activeSubflow = null; + var activeNodes = []; + var activeLinks = []; + var activeJunctions = []; + var activeFlowLinks = []; + var activeLinkNodes = {}; + var activeGroup = null; + var activeHoverGroup = null; + var activeGroups = []; + var dirtyGroups = {}; + + var mousedown_link = null; + var mousedown_node = null; + var mousedown_group = null; + var mousedown_port_type = null; + var mousedown_port_index = 0; + var mouseup_node = null; + var mouse_offset = [0,0]; + var mouse_position = null; + var mouse_mode = 0; + var mousedown_group_handle = null; + var lasso = null; + var slicePath = null; + var slicePathLast = null; + var ghostNode = null; + var showStatus = false; + var lastClickNode = null; + var dblClickPrimed = null; + var clickTime = 0; + var clickElapsed = 0; + var scroll_position = []; + var quickAddActive = false; + var quickAddLink = null; + var showAllLinkPorts = -1; + var groupNodeSelectPrimed = false; + var lastClickPosition = []; + var selectNodesOptions; + + let flashingNodeId; + + var clipboard = ""; + + // Note: these are the permitted status colour aliases. The actual RGB values + // are set in the CSS - flow.scss/colors.scss + var status_colours = { + "red": "#c00", + "green": "#5a8", + "yellow": "#F9DF31", + "blue": "#53A3F3", + "grey": "#d3d3d3", + "gray": "#d3d3d3" + } + + var PORT_TYPE_INPUT = 1; + var PORT_TYPE_OUTPUT = 0; + + var chart; + var outer; + var eventLayer; + var gridLayer; + var linkLayer; + var junctionLayer; + var dragGroupLayer; + var groupSelectLayer; + var nodeLayer; + var groupLayer; + var drag_lines; + + var movingSet = (function() { + var setIds = new Set(); + var set = []; + var api = { + add: function(node) { + if (Array.isArray(node)) { + for (var i=0;i1) { + clearTimeout(touchStartTime); + touchStartTime = null; + d3.event.preventDefault(); + touch0 = d3.event.touches.item(0); + var touch1 = d3.event.touches.item(1); + var a = touch0["pageY"]-touch1["pageY"]; + var b = touch0["pageX"]-touch1["pageX"]; + + var offset = chart.offset(); + var scrollPos = [chart.scrollLeft(),chart.scrollTop()]; + startTouchCenter = [ + (touch1["pageX"]+(b/2)-offset.left+scrollPos[0])/scaleFactor, + (touch1["pageY"]+(a/2)-offset.top+scrollPos[1])/scaleFactor + ]; + moveTouchCenter = [ + touch1["pageX"]+(b/2), + touch1["pageY"]+(a/2) + ] + startTouchDistance = Math.sqrt((a*a)+(b*b)); + } else { + var obj = d3.select(document.body); + touch0 = d3.event.touches.item(0); + var pos = [touch0.pageX,touch0.pageY]; + startTouchCenter = [touch0.pageX,touch0.pageY]; + startTouchDistance = 0; + var point = d3.touches(this)[0]; + touchStartTime = setTimeout(function() { + touchStartTime = null; + showTouchMenu(obj,pos); + //lasso = eventLayer.append("rect") + // .attr("ox",point[0]) + // .attr("oy",point[1]) + // .attr("rx",2) + // .attr("ry",2) + // .attr("x",point[0]) + // .attr("y",point[1]) + // .attr("width",0) + // .attr("height",0) + // .attr("class","nr-ui-view-lasso"); + },touchLongPressTimeout); + } + d3.event.preventDefault(); + }) + .on("touchmove", function(){ + if (RED.touch.radialMenu.active()) { + d3.event.preventDefault(); + return; + } + if (RED.view.DEBUG) { console.warn("eventLayer.touchmove", mouse_mode, mousedown_node); } + var touch0; + if (d3.event.touches.length<2) { + if (touchStartTime) { + touch0 = d3.event.touches.item(0); + var dx = (touch0.pageX-startTouchCenter[0]); + var dy = (touch0.pageY-startTouchCenter[1]); + var d = Math.abs(dx*dx+dy*dy); + if (d > 64) { + clearTimeout(touchStartTime); + touchStartTime = null; + if (!mousedown_node && !mousedown_group) { + mouse_mode = RED.state.PANNING; + mouse_position = [touch0.pageX,touch0.pageY] + scroll_position = [chart.scrollLeft(),chart.scrollTop()]; + } + + } + } else if (lasso) { + d3.event.preventDefault(); + } + canvasMouseMove.call(this); + } else { + touch0 = d3.event.touches.item(0); + var touch1 = d3.event.touches.item(1); + var a = touch0["pageY"]-touch1["pageY"]; + var b = touch0["pageX"]-touch1["pageX"]; + var offset = chart.offset(); + var scrollPos = [chart.scrollLeft(),chart.scrollTop()]; + var moveTouchDistance = Math.sqrt((a*a)+(b*b)); + var touchCenter = [ + touch1["pageX"]+(b/2), + touch1["pageY"]+(a/2) + ]; + + if (!isNaN(moveTouchDistance)) { + oldScaleFactor = scaleFactor; + scaleFactor = Math.min(2,Math.max(0.3, scaleFactor + (Math.floor(((moveTouchDistance*100)-(startTouchDistance*100)))/10000))); + + var deltaTouchCenter = [ // Try to pan whilst zooming - not 100% + startTouchCenter[0]*(scaleFactor-oldScaleFactor),//-(touchCenter[0]-moveTouchCenter[0]), + startTouchCenter[1]*(scaleFactor-oldScaleFactor) //-(touchCenter[1]-moveTouchCenter[1]) + ]; + + startTouchDistance = moveTouchDistance; + moveTouchCenter = touchCenter; + + chart.scrollLeft(scrollPos[0]+deltaTouchCenter[0]); + chart.scrollTop(scrollPos[1]+deltaTouchCenter[1]); + redraw(); + } + } + d3.event.preventDefault(); + }); + + // Workspace Background + eventLayer.append("svg:rect") + .attr("class","red-ui-workspace-chart-background") + .attr("width", space_width) + .attr("height", space_height); + + gridLayer = eventLayer.append("g").attr("class","red-ui-workspace-chart-grid"); + updateGrid(); + + groupLayer = eventLayer.append("g"); + groupSelectLayer = eventLayer.append("g"); + linkLayer = eventLayer.append("g"); + dragGroupLayer = eventLayer.append("g"); + junctionLayer = eventLayer.append("g"); + nodeLayer = eventLayer.append("g"); + + drag_lines = []; + + RED.events.on("workspace:change",function(event) { + if (event.old !== 0) { + workspaceScrollPositions[event.old] = { + left:chart.scrollLeft(), + top:chart.scrollTop() + }; + } + var scrollStartLeft = chart.scrollLeft(); + var scrollStartTop = chart.scrollTop(); + + activeSubflow = RED.nodes.subflow(event.workspace); + + RED.menu.setDisabled("menu-item-workspace-edit", activeSubflow || event.workspace === 0); + RED.menu.setDisabled("menu-item-workspace-delete",event.workspace === 0 || RED.workspaces.count() == 1 || activeSubflow); + + if (workspaceScrollPositions[event.workspace]) { + chart.scrollLeft(workspaceScrollPositions[event.workspace].left); + chart.scrollTop(workspaceScrollPositions[event.workspace].top); + } else { + chart.scrollLeft(0); + chart.scrollTop(0); + } + var scrollDeltaLeft = chart.scrollLeft() - scrollStartLeft; + var scrollDeltaTop = chart.scrollTop() - scrollStartTop; + if (mouse_position != null) { + mouse_position[0] += scrollDeltaLeft; + mouse_position[1] += scrollDeltaTop; + } + if (RED.workspaces.selection().length === 0) { + resetMouseVars(); + clearSelection(); + } + RED.nodes.eachNode(function(n) { + n.dirty = true; + n.dirtyStatus = true; + }); + updateSelection(); + updateActiveNodes(); + redraw(); + }); + + RED.statusBar.add({ + id: "view-zoom-controls", + align: "right", + element: $(''+ + ''+ + ''+ + ''+ + '') + }) + + $("#red-ui-view-zoom-out").on("click", zoomOut); + RED.popover.tooltip($("#red-ui-view-zoom-out"),RED._('actions.zoom-out'),'core:zoom-out'); + $("#red-ui-view-zoom-zero").on("click", zoomZero); + RED.popover.tooltip($("#red-ui-view-zoom-zero"),RED._('actions.zoom-reset'),'core:zoom-reset'); + $("#red-ui-view-zoom-in").on("click", zoomIn); + RED.popover.tooltip($("#red-ui-view-zoom-in"),RED._('actions.zoom-in'),'core:zoom-in'); + chart.on("DOMMouseScroll mousewheel", function (evt) { + if ( evt.altKey ) { + evt.preventDefault(); + evt.stopPropagation(); + var move = -(evt.originalEvent.detail) || evt.originalEvent.wheelDelta; + if (move <= 0) { zoomOut(); } + else { zoomIn(); } + } + }); + + //add search to status-toolbar + RED.statusBar.add({ + id: "view-search-tools", + align: "left", + hidden: false, + element: $(''+ + '' + + '' + + '' + + '? of ?' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '') + }) + $("#red-ui-view-searchtools-search").on("click", searchFlows); + RED.popover.tooltip($("#red-ui-view-searchtools-search"),RED._('actions.search-flows'),'core:search'); + $("#red-ui-view-searchtools-prev").on("click", searchPrev); + RED.popover.tooltip($("#red-ui-view-searchtools-prev"),RED._('actions.search-prev'),'core:search-previous'); + $("#red-ui-view-searchtools-next").on("click", searchNext); + RED.popover.tooltip($("#red-ui-view-searchtools-next"),RED._('actions.search-next'),'core:search-next'); + RED.popover.tooltip($("#red-ui-view-searchtools-close"),RED._('common.label.close')); + + // Handle nodes dragged from the palette + chart.droppable({ + accept:".red-ui-palette-node", + drop: function( event, ui ) { + d3.event = event; + var selected_tool = $(ui.draggable[0]).attr("data-palette-type"); + var result = createNode(selected_tool); + if (!result) { + return; + } + var historyEvent = result.historyEvent; + var nn = result.node; + + RED.nodes.add(nn); + + var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label"); + if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) { + nn.l = showLabel; + } + + var helperOffset = d3.touches(ui.helper.get(0))[0]||d3.mouse(ui.helper.get(0)); + var helperWidth = ui.helper.width(); + var helperHeight = ui.helper.height(); + var mousePos = d3.touches(this)[0]||d3.mouse(this); + + try { + var isLink = (nn.type === "link in" || nn.type === "link out") + var hideLabel = nn.hasOwnProperty('l')?!nn.l : isLink; + + var label = RED.utils.getNodeLabel(nn, nn.type); + var labelParts = getLabelParts(label, "red-ui-flow-node-label"); + if (hideLabel) { + nn.w = node_height; + nn.h = Math.max(node_height,(nn.outputs || 0) * 15); + } else { + nn.w = Math.max(node_width,20*(Math.ceil((labelParts.width+50+(nn._def.inputs>0?7:0))/20)) ); + nn.h = Math.max(6+24*labelParts.lines.length,(nn.outputs || 0) * 15, 30); + } + } catch(err) { + } + + mousePos[1] += this.scrollTop + ((helperHeight/2)-helperOffset[1]); + mousePos[0] += this.scrollLeft + ((helperWidth/2)-helperOffset[0]); + mousePos[1] /= scaleFactor; + mousePos[0] /= scaleFactor; + + nn.x = mousePos[0]; + nn.y = mousePos[1]; + + if (snapGrid) { + var gridOffset = RED.view.tools.calculateGridSnapOffsets(nn); + nn.x -= gridOffset.x; + nn.y -= gridOffset.y; + } + + var spliceLink = $(ui.helper).data("splice"); + if (spliceLink) { + // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp/showQuickAddDialog + RED.nodes.removeLink(spliceLink); + var link1 = { + source:spliceLink.source, + sourcePort:spliceLink.sourcePort, + target: nn + }; + var link2 = { + source:nn, + sourcePort:0, + target: spliceLink.target + }; + RED.nodes.addLink(link1); + RED.nodes.addLink(link2); + historyEvent.links = [link1,link2]; + historyEvent.removedLinks = [spliceLink]; + } + + + var group = $(ui.helper).data("group"); + if (group) { + RED.group.addToGroup(group, nn); + historyEvent = { + t: 'multi', + events: [historyEvent], + + } + historyEvent.events.push({ + t: "addToGroup", + group: group, + nodes: nn + }) + } + + RED.history.push(historyEvent); + RED.editor.validateNode(nn); + RED.nodes.dirty(true); + // auto select dropped node - so info shows (if visible) + exitActiveGroup(); + clearSelection(); + nn.selected = true; + movingSet.add(nn); + if (group) { + selectGroup(group,false); + enterActiveGroup(group); + activeGroup = group; + } + updateActiveNodes(); + updateSelection(); + redraw(); + + if (nn._def.autoedit) { + RED.editor.edit(nn); + } + } + }); + chart.on("focus", function() { + $("#red-ui-workspace-tabs").addClass("red-ui-workspace-focussed"); + }); + chart.on("blur", function() { + $("#red-ui-workspace-tabs").removeClass("red-ui-workspace-focussed"); + }); + + RED.actions.add("core:copy-selection-to-internal-clipboard",copySelection); + RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection();deleteSelection();}); + RED.actions.add("core:paste-from-internal-clipboard",function(){importNodes(clipboard,{generateIds: true, generateDefaultNames: true});}); + + RED.actions.add("core:detach-selected-nodes", function() { detachSelectedNodes() }) + + RED.events.on("view:selection-changed", function(selection) { + var hasSelection = (selection.nodes && selection.nodes.length > 0); + var hasMultipleSelection = hasSelection && selection.nodes.length > 1; + RED.menu.setDisabled("menu-item-edit-cut",!hasSelection); + RED.menu.setDisabled("menu-item-edit-copy",!hasSelection); + RED.menu.setDisabled("menu-item-edit-select-connected",!hasSelection); + RED.menu.setDisabled("menu-item-view-tools-move-to-back",!hasSelection); + RED.menu.setDisabled("menu-item-view-tools-move-to-front",!hasSelection); + RED.menu.setDisabled("menu-item-view-tools-move-backwards",!hasSelection); + RED.menu.setDisabled("menu-item-view-tools-move-forwards",!hasSelection); + + RED.menu.setDisabled("menu-item-view-tools-align-left",!hasMultipleSelection); + RED.menu.setDisabled("menu-item-view-tools-align-center",!hasMultipleSelection); + RED.menu.setDisabled("menu-item-view-tools-align-right",!hasMultipleSelection); + RED.menu.setDisabled("menu-item-view-tools-align-top",!hasMultipleSelection); + RED.menu.setDisabled("menu-item-view-tools-align-middle",!hasMultipleSelection); + RED.menu.setDisabled("menu-item-view-tools-align-bottom",!hasMultipleSelection); + RED.menu.setDisabled("menu-item-view-tools-distribute-horizontally",!hasMultipleSelection); + RED.menu.setDisabled("menu-item-view-tools-distribute-veritcally",!hasMultipleSelection); + }) + + RED.actions.add("core:delete-selection",deleteSelection); + RED.actions.add("core:delete-selection-and-reconnect",function() { deleteSelection(true) }); + RED.actions.add("core:edit-selected-node",editSelection); + RED.actions.add("core:go-to-selection",function() { + if (movingSet.length() > 0) { + var node = movingSet.get(0).n; + if (/^subflow:/.test(node.type)) { + RED.workspaces.show(node.type.substring(8)) + } else if (node.type === 'group') { + enterActiveGroup(node); + redraw(); + } + } + }); + RED.actions.add("core:undo",RED.history.pop); + RED.actions.add("core:redo",RED.history.redo); + RED.actions.add("core:select-all-nodes",selectAll); + RED.actions.add("core:select-none", selectNone); + RED.actions.add("core:zoom-in",zoomIn); + RED.actions.add("core:zoom-out",zoomOut); + RED.actions.add("core:zoom-reset",zoomZero); + RED.actions.add("core:enable-selected-nodes", function() { setSelectedNodeState(false)}); + RED.actions.add("core:disable-selected-nodes", function() { setSelectedNodeState(true)}); + + RED.actions.add("core:toggle-show-grid",function(state) { + if (state === undefined) { + RED.userSettings.toggle("view-show-grid"); + } else { + toggleShowGrid(state); + } + }); + RED.actions.add("core:toggle-snap-grid",function(state) { + if (state === undefined) { + RED.userSettings.toggle("view-snap-grid"); + } else { + toggleSnapGrid(state); + } + }); + RED.actions.add("core:toggle-status",function(state) { + if (state === undefined) { + RED.userSettings.toggle("view-node-status"); + } else { + toggleStatus(state); + } + }); + + RED.view.annotations.init(); + RED.view.navigator.init(); + RED.view.tools.init(); + + + RED.view.annotations.register("red-ui-flow-node-changed",{ + type: "badge", + class: "red-ui-flow-node-changed", + element: function() { + var changeBadge = document.createElementNS("http://www.w3.org/2000/svg","circle"); + changeBadge.setAttribute("cx",5); + changeBadge.setAttribute("cy",5); + changeBadge.setAttribute("r",5); + return changeBadge; + }, + show: function(n) { return n.changed||n.moved } + }) + + RED.view.annotations.register("red-ui-flow-node-error",{ + type: "badge", + class: "red-ui-flow-node-error", + element: function(d) { + var errorBadge = document.createElementNS("http://www.w3.org/2000/svg","path"); + errorBadge.setAttribute("d","M 0,9 l 10,0 -5,-8 z"); + return errorBadge + }, + tooltip: function(d) { + if (d.validationErrors && d.validationErrors.length > 0) { + return RED._("editor.errors.invalidProperties")+"\n - "+d.validationErrors.join("\n - ") + } + }, + show: function(n) { return !n.valid } + }) + + if (RED.settings.get("editor.view.view-store-zoom")) { + var userZoomLevel = parseFloat(RED.settings.getLocal('zoom-level')) + if (!isNaN(userZoomLevel)) { + scaleFactor = userZoomLevel + } + } + + var onScrollTimer = null; + function storeScrollPosition() { + workspaceScrollPositions[RED.workspaces.active()] = { + left:chart.scrollLeft(), + top:chart.scrollTop() + }; + RED.settings.setLocal('scroll-positions', JSON.stringify(workspaceScrollPositions) ) + } + chart.on("scroll", function() { + if (RED.settings.get("editor.view.view-store-position")) { + if (onScrollTimer) { + clearTimeout(onScrollTimer) + } + onScrollTimer = setTimeout(storeScrollPosition, 200); + } + }) + + if (RED.settings.get("editor.view.view-store-position")) { + var scrollPositions = RED.settings.getLocal('scroll-positions') + if (scrollPositions) { + try { + workspaceScrollPositions = JSON.parse(scrollPositions) + } catch(err) { + } + } + } + } + + + + function updateGrid() { + var gridTicks = []; + for (var i=0;i 0) { + if (delta < node_width) { + scale = 0.75-0.75*((node_width-delta)/node_width); + // scale += 2*(Math.min(5*node_width,Math.abs(dx))/(5*node_width)); + // if (Math.abs(dy) < 3*node_height) { + // scaleY = ((dy>0)?0.5:-0.5)*(((3*node_height)-Math.abs(dy))/(3*node_height))*(Math.min(node_width,Math.abs(dx))/(node_width)) ; + // } + } + } else { + scale = 0.4-0.2*(Math.max(0,(node_width-Math.min(Math.abs(dx),Math.abs(dy)))/node_width)); + } + if (dx*sc > 0) { + return "M "+origX+" "+origY+ + " C "+(origX+sc*(node_width*scale))+" "+(origY+scaleY*node_height)+" "+ + (destX-sc*(scale)*node_width)+" "+(destY-scaleY*node_height)+" "+ + destX+" "+destY + } else { + + var midX = Math.floor(destX-dx/2); + var midY = Math.floor(destY-dy/2); + // + if (dy === 0) { + midY = destY + node_height; + } + var cp_height = node_height/2; + var y1 = (destY + midY)/2 + var topX =origX + sc*node_width*scale; + var topY = dy>0?Math.min(y1 - dy/2 , origY+cp_height):Math.max(y1 - dy/2 , origY-cp_height); + var bottomX = destX - sc*node_width*scale; + var bottomY = dy>0?Math.max(y1, destY-cp_height):Math.min(y1, destY+cp_height); + var x1 = (origX+topX)/2; + var scy = dy>0?1:-1; + var cp = [ + // Orig -> Top + [x1,origY], + [topX,dy>0?Math.max(origY, topY-cp_height):Math.min(origY, topY+cp_height)], + // Top -> Mid + // [Mirror previous cp] + [x1,dy>0?Math.min(midY, topY+cp_height):Math.max(midY, topY-cp_height)], + // Mid -> Bottom + // [Mirror previous cp] + [bottomX,dy>0?Math.max(midY, bottomY-cp_height):Math.min(midY, bottomY+cp_height)], + // Bottom -> Dest + // [Mirror previous cp] + [(destX+bottomX)/2,destY] + ]; + if (cp[2][1] === topY+scy*cp_height) { + if (Math.abs(dy) < cp_height*10) { + cp[1][1] = topY-scy*cp_height/2; + cp[3][1] = bottomY-scy*cp_height/2; + } + cp[2][0] = topX; + } + return "M "+origX+" "+origY+ + " C "+ + cp[0][0]+" "+cp[0][1]+" "+ + cp[1][0]+" "+cp[1][1]+" "+ + topX+" "+topY+ + " S "+ + cp[2][0]+" "+cp[2][1]+" "+ + midX+" "+midY+ + " S "+ + cp[3][0]+" "+cp[3][1]+" "+ + bottomX+" "+bottomY+ + " S "+ + cp[4][0]+" "+cp[4][1]+" "+ + destX+" "+destY + } + } + + function canvasMouseDown() { + if (RED.view.DEBUG) { + console.warn("canvasMouseDown", { mouse_mode, point: d3.mouse(this), event: d3.event }); + } + if (mouse_mode === RED.state.SELECTING_NODE) { + d3.event.stopPropagation(); + return; + } + + if (d3.event.button === 1) { + // Middle Click pan + mouse_mode = RED.state.PANNING; + mouse_position = [d3.event.pageX,d3.event.pageY] + scroll_position = [chart.scrollLeft(),chart.scrollTop()]; + return; + } + if (!mousedown_node && !mousedown_link && !mousedown_group) { + selectedLinks.clear(); + updateSelection(); + } + if (mouse_mode === 0 && lasso) { + lasso.remove(); + lasso = null; + } + if (d3.event.touches || d3.event.button === 0) { + if ((mouse_mode === 0 || mouse_mode === RED.state.QUICK_JOINING) && (d3.event.metaKey || d3.event.ctrlKey) && !(d3.event.altKey || d3.event.shiftKey)) { + // Trigger quick add dialog + d3.event.stopPropagation(); + clearSelection(); + const point = d3.mouse(this); + var clickedGroup = getGroupAt(point[0], point[1]); + if (drag_lines.length > 0) { + clickedGroup = clickedGroup || RED.nodes.group(drag_lines[0].node.g) + } + showQuickAddDialog({ position: point, group: clickedGroup }); + } else if (mouse_mode === 0 && !(d3.event.metaKey || d3.event.ctrlKey)) { + // CTRL not being held + if (!d3.event.altKey) { + // ALT not held (shift is allowed) Trigger lasso + if (!touchStartTime) { + const point = d3.mouse(this); + lasso = eventLayer.append("rect") + .attr("ox", point[0]) + .attr("oy", point[1]) + .attr("rx", 1) + .attr("ry", 1) + .attr("x", point[0]) + .attr("y", point[1]) + .attr("width", 0) + .attr("height", 0) + .attr("class", "nr-ui-view-lasso"); + d3.event.preventDefault(); + } + } else if (d3.event.altKey) { + //Alt [+shift] held - Begin slicing + clearSelection(); + mouse_mode = (d3.event.shiftKey) ? RED.state.SLICING_JUNCTION : RED.state.SLICING; + const point = d3.mouse(this); + slicePath = eventLayer.append("path").attr("class", "nr-ui-view-slice").attr("d", `M${point[0]} ${point[1]}`) + slicePathLast = point; + RED.view.redraw(); + } + } + } + } + + function showQuickAddDialog(options) { + options = options || {}; + var point = options.position || lastClickPosition; + var spliceLink = options.splice; + var targetGroup = options.group; + var touchTrigger = options.touchTrigger; + + if (targetGroup && !targetGroup.active) { + selectGroup(targetGroup,false); + enterActiveGroup(targetGroup); + RED.view.redraw(); + } + + var ox = point[0]; + var oy = point[1]; + + if (RED.settings.get("editor").view['view-snap-grid']) { + // eventLayer.append("circle").attr("cx",point[0]).attr("cy",point[1]).attr("r","2").attr('fill','red') + point[0] = Math.round(point[0] / gridSize) * gridSize; + point[1] = Math.round(point[1] / gridSize) * gridSize; + // eventLayer.append("circle").attr("cx",point[0]).attr("cy",point[1]).attr("r","2").attr('fill','blue') + } + + var mainPos = $("#red-ui-main-container").position(); + + if (mouse_mode !== RED.state.QUICK_JOINING) { + mouse_mode = RED.state.QUICK_JOINING; + $(window).on('keyup',disableQuickJoinEventHandler); + } + quickAddActive = true; + + if (ghostNode) { + ghostNode.remove(); + } + ghostNode = eventLayer.append("g").attr('transform','translate('+(point[0] - node_width/2)+','+(point[1] - node_height/2)+')'); + ghostNode.append("rect") + .attr("class","red-ui-flow-node-placeholder") + .attr("rx", 5) + .attr("ry", 5) + .attr("width",node_width) + .attr("height",node_height) + .attr("fill","none") + // var ghostLink = ghostNode.append("svg:path") + // .attr("class","red-ui-flow-link-link") + // .attr("d","M 0 "+(node_height/2)+" H "+(gridSize * -2)) + // .attr("opacity",0); + + var filter; + if (drag_lines.length > 0) { + if (drag_lines[0].virtualLink) { + filter = {type:drag_lines[0].node.type === 'link in'?'link out':'link in'} + } else if (drag_lines[0].portType === PORT_TYPE_OUTPUT) { + filter = {input:true} + } else { + filter = {output:true} + } + + quickAddLink = { + node: drag_lines[0].node, + port: drag_lines[0].port, + portType: drag_lines[0].portType, + } + if (drag_lines[0].virtualLink) { + quickAddLink.virtualLink = true; + } + hideDragLines(); + } + if (spliceLink) { + filter = {input:true, output:true} + } + + var rebuildQuickAddLink = function() { + if (!quickAddLink) { + return; + } + if (!quickAddLink.el) { + quickAddLink.el = dragGroupLayer.append("svg:path").attr("class", "red-ui-flow-drag-line"); + } + var numOutputs = (quickAddLink.portType === PORT_TYPE_OUTPUT)?(quickAddLink.node.outputs || 1):1; + var sourcePort = quickAddLink.port; + var portY = -((numOutputs-1)/2)*13 +13*sourcePort; + var sc = (quickAddLink.portType === PORT_TYPE_OUTPUT)?1:-1; + quickAddLink.el.attr("d",generateLinkPath(quickAddLink.node.x+sc*quickAddLink.node.w/2,quickAddLink.node.y+portY,point[0]-sc*node_width/2,point[1],sc)); + } + if (quickAddLink) { + rebuildQuickAddLink(); + } + + + var lastAddedX; + var lastAddedWidth; + + RED.typeSearch.show({ + x:d3.event.clientX-mainPos.left-node_width/2 - (ox-point[0]), + y:d3.event.clientY-mainPos.top+ node_height/2 + 5 - (oy-point[1]), + disableFocus: touchTrigger, + filter: filter, + move: function(dx,dy) { + if (ghostNode) { + var pos = d3.transform(ghostNode.attr("transform")).translate; + ghostNode.attr("transform","translate("+(pos[0]+dx)+","+(pos[1]+dy)+")") + point[0] += dx; + point[1] += dy; + rebuildQuickAddLink(); + } + }, + cancel: function() { + if (quickAddLink) { + if (quickAddLink.el) { + quickAddLink.el.remove(); + } + quickAddLink = null; + } + quickAddActive = false; + if (ghostNode) { + ghostNode.remove(); + } + resetMouseVars(); + updateSelection(); + hideDragLines(); + redraw(); + }, + add: function(type,keepAdding) { + if (touchTrigger) { + keepAdding = false; + resetMouseVars(); + } + + var nn; + var historyEvent; + if (type === 'junction') { + nn = { + _def: {defaults:{}}, + type: 'junction', + z: RED.workspaces.active(), + id: RED.nodes.id(), + x: 0, + y: 0, + w: 0, h: 0, + outputs: 1, + inputs: 1, + dirty: true + } + historyEvent = { + t:'add', + junctions:[nn] + } + } else { + var result = createNode(type); + if (!result) { + return; + } + nn = result.node; + historyEvent = result.historyEvent; + } + if (keepAdding) { + mouse_mode = RED.state.QUICK_JOINING; + } + + nn.x = point[0]; + nn.y = point[1]; + var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label"); + if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) { + nn.l = showLabel; + } + if (quickAddLink) { + var drag_line = quickAddLink; + var src = null,dst,src_port; + if (drag_line.portType === PORT_TYPE_OUTPUT && (nn.inputs > 0 || drag_line.virtualLink) ) { + src = drag_line.node; + src_port = drag_line.port; + dst = nn; + } else if (drag_line.portType === PORT_TYPE_INPUT && (nn.outputs > 0 || drag_line.virtualLink)) { + src = nn; + dst = drag_line.node; + src_port = 0; + } + + if (src !== null) { + // Joining link nodes via virual wires. Need to update + // the src and dst links property + if (drag_line.virtualLink) { + historyEvent = { + t:'multi', + events: [historyEvent] + } + var oldSrcLinks = $.extend(true,{},{v:src.links}).v + var oldDstLinks = $.extend(true,{},{v:dst.links}).v + src.links.push(dst.id); + dst.links.push(src.id); + src.dirty = true; + dst.dirty = true; + + historyEvent.events.push({ + t:'edit', + node: src, + dirty: RED.nodes.dirty(), + changed: src.changed, + changes: { + links:oldSrcLinks + } + }); + historyEvent.events.push({ + t:'edit', + node: dst, + dirty: RED.nodes.dirty(), + changed: dst.changed, + changes: { + links:oldDstLinks + } + }); + src.changed = true; + dst.changed = true; + } else { + var link = {source: src, sourcePort:src_port, target: dst}; + RED.nodes.addLink(link); + historyEvent.links = [link]; + } + if (!keepAdding) { + quickAddLink.el.remove(); + quickAddLink = null; + if (mouse_mode === RED.state.QUICK_JOINING) { + if (drag_line.portType === PORT_TYPE_OUTPUT && nn.outputs > 0) { + showDragLines([{node:nn,port:0,portType:PORT_TYPE_OUTPUT}]); + } else if (!quickAddLink && drag_line.portType === PORT_TYPE_INPUT && nn.inputs > 0) { + showDragLines([{node:nn,port:0,portType:PORT_TYPE_INPUT}]); + } else { + resetMouseVars(); + } + } + } else { + quickAddLink.node = nn; + quickAddLink.port = 0; + } + } else { + hideDragLines(); + resetMouseVars(); + } + } else { + if (!keepAdding) { + if (mouse_mode === RED.state.QUICK_JOINING) { + if (nn.outputs > 0) { + showDragLines([{node:nn,port:0,portType:PORT_TYPE_OUTPUT}]); + } else if (nn.inputs > 0) { + showDragLines([{node:nn,port:0,portType:PORT_TYPE_INPUT}]); + } else { + resetMouseVars(); + } + } + } else { + if (nn.outputs > 0) { + quickAddLink = { + node: nn, + port: 0, + portType: PORT_TYPE_OUTPUT + } + } else if (nn.inputs > 0) { + quickAddLink = { + node: nn, + port: 0, + portType: PORT_TYPE_INPUT + } + } else { + resetMouseVars(); + } + } + } + if (nn.type === 'junction') { + RED.nodes.addJunction(nn); + } else { + RED.nodes.add(nn); + } + RED.editor.validateNode(nn); + + if (targetGroup) { + RED.group.addToGroup(targetGroup, nn); + if (historyEvent.t !== "multi") { + historyEvent = { + t:'multi', + events: [historyEvent] + } + } + historyEvent.events.push({ + t: "addToGroup", + group: targetGroup, + nodes: nn + }) + + } + + if (spliceLink) { + resetMouseVars(); + // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp/showQuickAddDialog + RED.nodes.removeLink(spliceLink); + var link1 = { + source:spliceLink.source, + sourcePort:spliceLink.sourcePort, + target: nn + }; + var link2 = { + source:nn, + sourcePort:0, + target: spliceLink.target + }; + RED.nodes.addLink(link1); + RED.nodes.addLink(link2); + historyEvent.links = (historyEvent.links || []).concat([link1,link2]); + historyEvent.removedLinks = [spliceLink]; + } + RED.history.push(historyEvent); + RED.nodes.dirty(true); + // auto select dropped node - so info shows (if visible) + clearSelection(); + nn.selected = true; + if (targetGroup) { + selectGroup(targetGroup,false); + enterActiveGroup(targetGroup); + } + movingSet.add(nn); + updateActiveNodes(); + updateSelection(); + redraw(); + // At this point the newly added node will have a real width, + // so check if the position needs nudging + if (lastAddedX !== undefined) { + var lastNodeRHEdge = lastAddedX + lastAddedWidth/2; + var thisNodeLHEdge = nn.x - nn.w/2; + var gap = thisNodeLHEdge - lastNodeRHEdge; + if (gap != gridSize *2) { + nn.x = nn.x + gridSize * 2 - gap; + nn.dirty = true; + nn.x = Math.ceil(nn.x / gridSize) * gridSize; + redraw(); + } + } + if (keepAdding) { + if (lastAddedX === undefined) { + // ghostLink.attr("opacity",1); + setTimeout(function() { + RED.typeSearch.refresh({filter:{input:true}}); + },100); + } + + lastAddedX = nn.x; + lastAddedWidth = nn.w; + + point[0] = nn.x + nn.w/2 + node_width/2 + gridSize * 2; + ghostNode.attr('transform','translate('+(point[0] - node_width/2)+','+(point[1] - node_height/2)+')'); + rebuildQuickAddLink(); + } else { + quickAddActive = false; + ghostNode.remove(); + } + } + }); + + updateActiveNodes(); + updateSelection(); + redraw(); + } + + function canvasMouseMove() { + var i; + var node; + // Prevent touch scrolling... + //if (d3.touches(this)[0]) { + // d3.event.preventDefault(); + //} + + // TODO: auto scroll the container + //var point = d3.mouse(this); + //if (point[0]-container.scrollLeft < 30 && container.scrollLeft > 0) { container.scrollLeft -= 15; } + //console.log(d3.mouse(this),container.offsetWidth,container.offsetHeight,container.scrollLeft,container.scrollTop); + + if (mouse_mode === RED.state.PANNING) { + var pos = [d3.event.pageX,d3.event.pageY]; + if (d3.event.touches) { + var touch0 = d3.event.touches.item(0); + pos = [touch0.pageX, touch0.pageY]; + } + var deltaPos = [ + mouse_position[0]-pos[0], + mouse_position[1]-pos[1] + ]; + + chart.scrollLeft(scroll_position[0]+deltaPos[0]) + chart.scrollTop(scroll_position[1]+deltaPos[1]) + return + } + if (entryCoordinates.x != -1) { + mouse_position = [entryCoordinates.x, entryCoordinates.y] + } else { + mouse_position = d3.touches(this)[0]||d3.mouse(this); + } +if(RED.view.DEBUG) { console.log(`mousemove ${JSON.stringify(mouse_position)}`)} + if (lasso) { + var ox = parseInt(lasso.attr("ox")); + var oy = parseInt(lasso.attr("oy")); + var x = parseInt(lasso.attr("x")); + var y = parseInt(lasso.attr("y")); + var w; + var h; + if (mouse_position[0] < ox) { + x = mouse_position[0]; + w = ox-x; + } else { + w = mouse_position[0]-x; + } + if (mouse_position[1] < oy) { + y = mouse_position[1]; + h = oy-y; + } else { + h = mouse_position[1]-y; + } + lasso + .attr("x",x) + .attr("y",y) + .attr("width",w) + .attr("height",h) + ; + return; + } else if (mouse_mode === RED.state.SLICING || mouse_mode === RED.state.SLICING_JUNCTION) { + if (slicePath) { + var delta = Math.max(1,Math.abs(slicePathLast[0]-mouse_position[0]))*Math.max(1,Math.abs(slicePathLast[1]-mouse_position[1])) + if (delta > 20) { + var currentPath = slicePath.attr("d") + currentPath += " L"+mouse_position[0]+" "+mouse_position[1] + slicePath.attr("d",currentPath); + slicePathLast = mouse_position + } + } + return + } + + if (mouse_mode === RED.state.SELECTING_NODE) { + d3.event.stopPropagation(); + return; + } + + if (mouse_mode != RED.state.QUICK_JOINING && mouse_mode != RED.state.IMPORT_DRAGGING && mouse_mode != RED.state.DETACHED_DRAGGING && !mousedown_node && !mousedown_group && selectedLinks.length() === 0) { + return; + } + + var mousePos; + // if (mouse_mode === RED.state.GROUP_RESIZE) { + // mousePos = mouse_position; + // var nx = mousePos[0] + mousedown_group.dx; + // var ny = mousePos[1] + mousedown_group.dy; + // switch(mousedown_group.activeHandle) { + // case 0: mousedown_group.pos.x0 = nx; mousedown_group.pos.y0 = ny; break; + // case 1: mousedown_group.pos.x1 = nx; mousedown_group.pos.y0 = ny; break; + // case 2: mousedown_group.pos.x1 = nx; mousedown_group.pos.y1 = ny; break; + // case 3: mousedown_group.pos.x0 = nx; mousedown_group.pos.y1 = ny; break; + // } + // mousedown_group.dirty = true; + // } + if (mouse_mode == RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { + // update drag line + if (drag_lines.length === 0 && mousedown_port_type !== null) { + if (d3.event.shiftKey) { + // Get all the wires we need to detach. + var links = []; + var existingLinks = []; + if (selectedLinks.length() > 0) { + selectedLinks.forEach(function(link) { + if (((mousedown_port_type === PORT_TYPE_OUTPUT && + link.source === mousedown_node && + link.sourcePort === mousedown_port_index + ) || + (mousedown_port_type === PORT_TYPE_INPUT && + link.target === mousedown_node + ))) { + existingLinks.push(link); + } + }) + } else { + var filter; + if (mousedown_port_type === PORT_TYPE_OUTPUT) { + filter = { + source:mousedown_node, + sourcePort: mousedown_port_index + } + } else { + filter = { + target: mousedown_node + } + } + existingLinks = RED.nodes.filterLinks(filter); + } + for (i=0;i 3 && !dblClickPrimed) || (dblClickPrimed && d > 10)) { + mouse_mode = RED.state.MOVING_ACTIVE; + clickElapsed = 0; + spliceActive = false; + if (movingSet.length() === 1) { + node = movingSet.get(0); + spliceActive = node.n.hasOwnProperty("_def") && + ((node.n.hasOwnProperty("inputs") && node.n.inputs > 0) || (!node.n.hasOwnProperty("inputs") && node.n._def.inputs > 0)) && + ((node.n.hasOwnProperty("outputs") && node.n.outputs > 0) || (!node.n.hasOwnProperty("outputs") && node.n._def.outputs > 0)) && + RED.nodes.filterLinks({ source: node.n }).length === 0 && + RED.nodes.filterLinks({ target: node.n }).length === 0; + } + } + } else if (mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.IMPORT_DRAGGING || mouse_mode == RED.state.DETACHED_DRAGGING) { + mousePos = mouse_position; + var minX = 0; + var minY = 0; + var maxX = space_width; + var maxY = space_height; + if(RED.view.DEBUG) { console.log(`mousemove - MOVING_ACTIVE ${JSON.stringify(mousePos)}`)} + for (var n = 0; n 0) { + var i = 0; + + // Prefer to snap nodes to the grid if there is one in the selection + do { + node = movingSet.get(i++); + } while(i 0) { + historyEvent = { + t:"delete", + links: removedLinks, + dirty:RED.nodes.dirty() + }; + RED.history.push(historyEvent); + RED.nodes.dirty(true); + } + hideDragLines(); + } + if (lasso) { + var x = parseInt(lasso.attr("x")); + var y = parseInt(lasso.attr("y")); + var x2 = x+parseInt(lasso.attr("width")); + var y2 = y+parseInt(lasso.attr("height")); + var ag = activeGroup; + if (!d3.event.shiftKey) { + clearSelection(); + if (ag) { + if (x < ag.x+ag.w && x2 > ag.x && y < ag.y+ag.h && y2 > ag.y) { + // There was an active group and the lasso intersects with it, + // so reenter the group + enterActiveGroup(ag); + activeGroup.selected = true; + } + } + } + activeGroups.forEach(function(g) { + if (!g.selected) { + if (g.x > x && g.x+g.w < x2 && g.y > y && g.y+g.h < y2) { + if (!activeGroup || RED.group.contains(activeGroup,g)) { + while (g.g && (!activeGroup || g.g !== activeGroup.id)) { + g = RED.nodes.group(g.g); + } + if (!g.selected) { + selectGroup(g,true); + } + } + } + } + }) + + activeNodes.forEach(function(n) { + if (!n.selected) { + if (n.x > x && n.x < x2 && n.y > y && n.y < y2) { + if (!activeGroup || RED.group.contains(activeGroup,n)) { + if (n.g && (!activeGroup || n.g !== activeGroup.id)) { + var group = RED.nodes.group(n.g); + while (group.g && (!activeGroup || group.g !== activeGroup.id)) { + group = RED.nodes.group(group.g); + } + if (!group.selected) { + selectGroup(group,true); + } + } else { + n.selected = true; + n.dirty = true; + movingSet.add(n); + } + } + } + } + }); + activeJunctions.forEach(function(n) { + if (!n.selected) { + if (n.x > x && n.x < x2 && n.y > y && n.y < y2) { + n.selected = true; + n.dirty = true; + movingSet.add(n); + } + } + }) + + + + // var selectionChanged = false; + // do { + // selectionChanged = false; + // selectedGroups.forEach(function(g) { + // if (g.g && g.selected && RED.nodes.group(g.g).selected) { + // g.selected = false; + // selectionChanged = true; + // } + // }) + // } while(selectionChanged); + + if (activeSubflow) { + activeSubflow.in.forEach(function(n) { + n.selected = (n.x > x && n.x < x2 && n.y > y && n.y < y2); + if (n.selected) { + n.dirty = true; + movingSet.add(n); + } + }); + activeSubflow.out.forEach(function(n) { + n.selected = (n.x > x && n.x < x2 && n.y > y && n.y < y2); + if (n.selected) { + n.dirty = true; + movingSet.add(n); + } + }); + if (activeSubflow.status) { + activeSubflow.status.selected = (activeSubflow.status.x > x && activeSubflow.status.x < x2 && activeSubflow.status.y > y && activeSubflow.status.y < y2); + if (activeSubflow.status.selected) { + activeSubflow.status.dirty = true; + movingSet.add(activeSubflow.status); + } + } + } + updateSelection(); + lasso.remove(); + lasso = null; + } else if (mouse_mode == RED.state.DEFAULT && mousedown_link == null && !d3.event.ctrlKey && !d3.event.metaKey ) { + clearSelection(); + updateSelection(); + } else if (mouse_mode == RED.state.SLICING) { + deleteSelection(); + slicePath.remove(); + slicePath = null; + RED.view.redraw(true); + } else if (mouse_mode == RED.state.SLICING_JUNCTION) { + var removedLinks = new Set() + var addedLinks = [] + var addedJunctions = [] + + var groupedLinks = {} + selectedLinks.forEach(function(l) { + var sourceId = l.source.id+":"+l.sourcePort + groupedLinks[sourceId] = groupedLinks[sourceId] || [] + groupedLinks[sourceId].push(l) + + groupedLinks[l.target.id] = groupedLinks[l.target.id] || [] + groupedLinks[l.target.id].push(l) + }); + var linkGroups = Object.keys(groupedLinks) + linkGroups.sort(function(A,B) { + return groupedLinks[B].length - groupedLinks[A].length + }) + linkGroups.forEach(function(gid) { + var links = groupedLinks[gid] + var junction = { + _def: {defaults:{}}, + type: 'junction', + z: RED.workspaces.active(), + id: RED.nodes.id(), + x: 0, + y: 0, + w: 0, h: 0, + outputs: 1, + inputs: 1, + dirty: true + } + links = links.filter(function(l) { return !removedLinks.has(l) }) + if (links.length === 0) { + return + } + links.forEach(function(l) { + junction.x += l._sliceLocation.x + junction.y += l._sliceLocation.y + }) + junction.x = Math.round(junction.x/links.length) + junction.y = Math.round(junction.y/links.length) + if (snapGrid) { + junction.x = (gridSize*Math.round(junction.x/gridSize)); + junction.y = (gridSize*Math.round(junction.y/gridSize)); + } + + var nodeGroups = new Set() + + RED.nodes.addJunction(junction) + addedJunctions.push(junction) + let newLink + if (gid === links[0].source.id+":"+links[0].sourcePort) { + newLink = { + source: links[0].source, + sourcePort: links[0].sourcePort, + target: junction + } + } else { + newLink = { + source: junction, + sourcePort: 0, + target: links[0].target + } + } + addedLinks.push(newLink) + RED.nodes.addLink(newLink) + links.forEach(function(l) { + removedLinks.add(l) + RED.nodes.removeLink(l) + let newLink + if (gid === l.target.id) { + newLink = { + source: l.source, + sourcePort: l.sourcePort, + target: junction + } + } else { + newLink = { + source: junction, + sourcePort: 0, + target: l.target + } + } + addedLinks.push(newLink) + RED.nodes.addLink(newLink) + nodeGroups.add(l.source.g || "__NONE__") + nodeGroups.add(l.target.g || "__NONE__") + }) + if (nodeGroups.size === 1) { + var group = nodeGroups.values().next().value + if (group !== "__NONE__") { + RED.group.addToGroup(RED.nodes.group(group), junction) + } + } + }) + slicePath.remove(); + slicePath = null; + + if (addedJunctions.length > 0) { + RED.history.push({ + t: 'add', + links: addedLinks, + junctions: addedJunctions, + removedLinks: Array.from(removedLinks) + }) + RED.nodes.dirty(true) + } + RED.view.redraw(true); + } + if (mouse_mode == RED.state.MOVING_ACTIVE) { + if (movingSet.length() > 0) { + var addedToGroup = null; + if (activeHoverGroup) { + for (var j=0;j 0 && mouse_mode == RED.state.MOVING_ACTIVE) { + historyEvent = {t:"move",nodes:ns,dirty:RED.nodes.dirty()}; + if (activeSpliceLink) { + // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp + var spliceLink = d3.select(activeSpliceLink).data()[0]; + RED.nodes.removeLink(spliceLink); + var link1 = { + source:spliceLink.source, + sourcePort:spliceLink.sourcePort, + target: movingSet.get(0).n + }; + var link2 = { + source:movingSet.get(0).n, + sourcePort:0, + target: spliceLink.target + }; + RED.nodes.addLink(link1); + RED.nodes.addLink(link2); + historyEvent.links = [link1,link2]; + historyEvent.removedLinks = [spliceLink]; + updateActiveNodes(); + } + if (addedToGroup) { + historyEvent.addToGroup = addedToGroup; + } + RED.nodes.dirty(true); + RED.history.push(historyEvent); + } + } + } + // if (mouse_mode === RED.state.MOVING && mousedown_node && mousedown_node.g) { + // if (mousedown_node.gSelected) { + // delete mousedown_node.gSelected + // } else { + // if (!d3.event.ctrlKey && !d3.event.metaKey) { + // clearSelection(); + // } + // RED.nodes.group(mousedown_node.g).selected = true; + // mousedown_node.selected = true; + // mousedown_node.dirty = true; + // movingSet.add(mousedown_node); + // } + // } + if (mouse_mode == RED.state.MOVING || mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.DETACHED_DRAGGING) { + // if (mousedown_node) { + // delete mousedown_node.gSelected; + // } + if (mouse_mode === RED.state.DETACHED_DRAGGING) { + var ns = []; + for (var j=0;j 0.3) { + zoomView(scaleFactor-0.1); + } + } + function zoomZero() { zoomView(1); } + function searchFlows() { RED.actions.invoke("core:search", $(this).data("term")); } + function searchPrev() { RED.actions.invoke("core:search-previous"); } + function searchNext() { RED.actions.invoke("core:search-next"); } + + + function zoomView(factor) { + var screenSize = [chart.width(),chart.height()]; + var scrollPos = [chart.scrollLeft(),chart.scrollTop()]; + var center = [(scrollPos[0] + screenSize[0]/2)/scaleFactor,(scrollPos[1] + screenSize[1]/2)/scaleFactor]; + scaleFactor = factor; + var newCenter = [(scrollPos[0] + screenSize[0]/2)/scaleFactor,(scrollPos[1] + screenSize[1]/2)/scaleFactor]; + var delta = [(newCenter[0]-center[0])*scaleFactor,(newCenter[1]-center[1])*scaleFactor] + chart.scrollLeft(scrollPos[0]-delta[0]); + chart.scrollTop(scrollPos[1]-delta[1]); + + RED.view.navigator.resize(); + redraw(); + if (RED.settings.get("editor.view.view-store-zoom")) { + RED.settings.setLocal('zoom-level', factor.toFixed(1)) + } + } + + function selectNone() { + if (mouse_mode === RED.state.MOVING || mouse_mode === RED.state.MOVING_ACTIVE) { + return; + } + if (mouse_mode === RED.state.DETACHED_DRAGGING) { + for (var j=0;j 0) { + activeFlowLinks.push({ + refresh: Math.floor(Math.random()*10000), + node: linkNode, + links: offFlowLinks//offFlows.map(function(i) { return {id:i,links:offFlowLinks[i]};}) + }); + } + } + } + if (activeFlowLinks.length === 0 && selectedLinks.length() > 0) { + selectedLinks.forEach(function(link) { + if (link.link) { + activeLinks.push(link); + activeLinkNodes[link.source.id] = link.source; + link.source.dirty = true; + activeLinkNodes[link.target.id] = link.target; + link.target.dirty = true; + } + }) + } + } else { + selection.flows = workspaceSelection; + } + } + var selectionJSON = activeWorkspace+":"+JSON.stringify(selection,function(key,value) { + if (key === 'nodes' || key === 'flows') { + return value.map(function(n) { return n.id }) + } else if (key === 'link') { + return value.source.id+":"+value.sourcePort+":"+value.target.id; + } else if (key === 'links') { + return value.map(function(link) { + return link.source.id+":"+link.sourcePort+":"+link.target.id; + }); + } + return value; + }); + if (selectionJSON !== lastSelection) { + lastSelection = selectionJSON; + RED.events.emit("view:selection-changed",selection); + } + } + + function editSelection() { + if (movingSet.length() > 0) { + var node = movingSet.get(0).n; + if (node.type === "subflow") { + RED.editor.editSubflow(activeSubflow); + } else if (node.type === "group") { + RED.editor.editGroup(node); + } else { + RED.editor.edit(node); + } + } + } + function deleteSelection(reconnectWires) { + if (mouse_mode === RED.state.SELECTING_NODE) { + return; + } + if (portLabelHover) { + portLabelHover.remove(); + portLabelHover = null; + } + var workspaceSelection = RED.workspaces.selection(); + if (workspaceSelection.length > 0) { + var workspaceCount = 0; + workspaceSelection.forEach(function(ws) { if (ws.type === 'tab') { workspaceCount++ } }); + if (workspaceCount === RED.workspaces.count()) { + // Cannot delete all workspaces + return; + } + var historyEvent = { + t: 'delete', + dirty: RED.nodes.dirty(), + nodes: [], + links: [], + groups: [], + junctions: [], + workspaces: [], + subflows: [] + } + var workspaceOrder = RED.nodes.getWorkspaceOrder().slice(0); + + for (var i=0;i 0 || selectedLinks.length() > 0) { + var result; + var node; + var removedNodes = []; + var removedLinks = []; + var removedGroups = []; + var removedJunctions = []; + var removedSubflowOutputs = []; + var removedSubflowInputs = []; + var removedSubflowStatus; + var subflowInstances = []; + var historyEvents = []; + var addToRemovedLinks = function(links) { + if(!links) { return; } + var _links = Array.isArray(links) ? links : [links]; + _links.forEach(function(l) { + removedLinks.push(l); + selectedLinks.remove(l); + }) + } + if (reconnectWires) { + var reconnectResult = RED.nodes.detachNodes(movingSet.nodes()) + var addedLinks = reconnectResult.newLinks; + if (addedLinks.length > 0) { + historyEvents.push({ t:'add', links: addedLinks }) + } + addToRemovedLinks(reconnectResult.removedLinks) + } + + var startDirty = RED.nodes.dirty(); + var startChanged = false; + var selectedGroups = []; + if (movingSet.length() > 0) { + + for (var i=0;i=0; i--) { + var g = selectedGroups[i]; + removedGroups.push(g); + RED.nodes.removeGroup(g); + } + if (removedSubflowOutputs.length > 0) { + result = RED.subflow.removeOutput(removedSubflowOutputs); + if (result) { + addToRemovedLinks(result.links); + } + } + // Assume 0/1 inputs + if (removedSubflowInputs.length == 1) { + result = RED.subflow.removeInput(); + if (result) { + addToRemovedLinks(result.links); + } + } + if (removedSubflowStatus) { + result = RED.subflow.removeStatus(); + if (result) { + addToRemovedLinks(result.links); + } + } + + var instances = RED.subflow.refresh(true); + if (instances) { + subflowInstances = instances.instances; + } + movingSet.clear(); + if (removedNodes.length > 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0 || removedSubflowStatus || removedGroups.length > 0 || removedJunctions.length > 0) { + RED.nodes.dirty(true); + } + } + + if (selectedLinks.length() > 0) { + selectedLinks.forEach(function(link) { + if (link.link) { + var sourceId = link.source.id; + var targetId = link.target.id; + var sourceIdIndex = link.target.links.indexOf(sourceId); + var targetIdIndex = link.source.links.indexOf(targetId); + historyEvents.push({ + t: "edit", + node: link.source, + changed: link.source.changed, + changes: { + links: $.extend(true,{},{v:link.source.links}).v + } + }) + historyEvents.push({ + t: "edit", + node: link.target, + changed: link.target.changed, + changes: { + links: $.extend(true,{},{v:link.target.links}).v + } + }) + link.source.changed = true; + link.target.changed = true; + link.target.links.splice(sourceIdIndex,1); + link.source.links.splice(targetIdIndex,1); + link.source.dirty = true; + link.target.dirty = true; + + } else { + RED.nodes.removeLink(link); + removedLinks.push(link); + } + }) + } + RED.nodes.dirty(true); + var historyEvent = { + t:"delete", + nodes:removedNodes, + links:removedLinks, + groups: removedGroups, + junctions: removedJunctions, + subflowOutputs:removedSubflowOutputs, + subflowInputs:removedSubflowInputs, + subflow: { + id: activeSubflow?activeSubflow.id:undefined, + instances: subflowInstances + }, + dirty:startDirty + }; + if (removedSubflowStatus) { + historyEvent.subflow.status = removedSubflowStatus; + } + if (historyEvents.length > 0) { + historyEvents.unshift(historyEvent); + RED.history.push({ + t:"multi", + events: historyEvents + }) + } else { + RED.history.push(historyEvent); + } + + selectedLinks.clear(); + updateActiveNodes(); + updateSelection(); + redraw(); + } + } + + function copySelection() { + if (mouse_mode === RED.state.SELECTING_NODE) { + return; + } + var nodes = []; + var selection = RED.workspaces.selection(); + if (selection.length > 0) { + nodes = []; + selection.forEach(function(n) { + if (n.type === 'tab') { + nodes.push(n); + nodes = nodes.concat(RED.nodes.groups(n.id)); + nodes = nodes.concat(RED.nodes.filterNodes({z:n.id})); + } + }); + } else { + selection = RED.view.selection(); + if (selection.nodes) { + selection.nodes.forEach(function(n) { + nodes.push(n); + if (n.type === 'group') { + nodes = nodes.concat(RED.group.getNodes(n,true)); + } + }) + } + } + + if (nodes.length > 0) { + var nns = []; + var nodeCount = 0; + var groupCount = 0; + var junctionCount = 0; + var handled = {}; + for (var n=0;n 0) { + RED.notify(RED._("clipboard.nodeCopied",{count:nodeCount}),{id:"clipboard"}); + } else if (groupCount > 0) { + RED.notify(RED._("clipboard.groupCopied",{count:groupCount}),{id:"clipboard"}); + } + } + } + + + function detachSelectedNodes() { + var selection = RED.view.selection(); + if (selection.nodes) { + const {newLinks, removedLinks} = RED.nodes.detachNodes(selection.nodes); + if (removedLinks.length || newLinks.length) { + RED.history.push({ + t: "multi", + events: [ + { t:'delete', links: removedLinks }, + { t:'add', links: newLinks } + ], + dirty: RED.nodes.dirty() + }) + RED.nodes.dirty(true) + } + prepareDrag([selection.nodes[0].x,selection.nodes[0].y]); + mouse_mode = RED.state.DETACHED_DRAGGING; + RED.view.redraw(true); + } + } + + function calculateTextWidth(str, className) { + var result = convertLineBreakCharacter(str); + var width = 0; + for (var i=0;i 1) { + var i=0; + for (i=0;i 0) { + if (drag_lines[0].node === d) { + // Cannot quick-join to self + return + } + if (drag_lines[0].virtualLink && + ( + (drag_lines[0].node.type === 'link in' && d.type !== 'link out') || + (drag_lines[0].node.type === 'link out' && d.type !== 'link in') + ) + ) { + return + } + } + document.body.style.cursor = ""; + if (mouse_mode == RED.state.JOINING || mouse_mode == RED.state.QUICK_JOINING) { + if (typeof TouchEvent != "undefined" && evt instanceof TouchEvent) { + var found = false; + RED.nodes.eachNode(function(n) { + if (n.z == RED.workspaces.active()) { + var hw = n.w/2; + var hh = n.h/2; + if (n.x-hw mouse_position[0] && + n.y-hhmouse_position[1]) { + found = true; + mouseup_node = n; + portType = mouseup_node.inputs>0?PORT_TYPE_INPUT:PORT_TYPE_OUTPUT; + portIndex = 0; + } + } + }); + if (!found && activeSubflow) { + var subflowPorts = []; + if (activeSubflow.status) { + subflowPorts.push(activeSubflow.status) + } + if (activeSubflow.in) { + subflowPorts = subflowPorts.concat(activeSubflow.in) + } + if (activeSubflow.out) { + subflowPorts = subflowPorts.concat(activeSubflow.out) + } + for (var i=0;i mouse_position[0] && + n.y-hhmouse_position[1]) { + found = true; + mouseup_node = n; + portType = mouseup_node.direction === "in"?PORT_TYPE_OUTPUT:PORT_TYPE_INPUT; + portIndex = 0; + break; + } + } + } + } else { + mouseup_node = d; + } + var addedLinks = []; + var removedLinks = []; + var modifiedNodes = []; // joining link nodes + + var select_link = null; + + for (i=0;i 0 || removedLinks.length > 0 || modifiedNodes.length > 0) { + // console.log(addedLinks); + // console.log(removedLinks); + // console.log(modifiedNodes); + var historyEvent; + if (modifiedNodes.length > 0) { + historyEvent = { + t:"multi", + events: linkEditEvents, + dirty:RED.nodes.dirty() + }; + } else { + historyEvent = { + t:"add", + links:addedLinks, + removedLinks: removedLinks, + dirty:RED.nodes.dirty() + }; + } + if (activeSubflow) { + var subflowRefresh = RED.subflow.refresh(true); + if (subflowRefresh) { + historyEvent.subflow = { + id:activeSubflow.id, + changed: activeSubflow.changed, + instances: subflowRefresh.instances + } + } + } + RED.history.push(historyEvent); + updateActiveNodes(); + RED.nodes.dirty(true); + } + if (mouse_mode === RED.state.QUICK_JOINING) { + if (addedLinks.length > 0 || modifiedNodes.length > 0) { + hideDragLines(); + if (portType === PORT_TYPE_INPUT && d.outputs > 0) { + showDragLines([{node:d,port:0,portType:PORT_TYPE_OUTPUT}]); + } else if (portType === PORT_TYPE_OUTPUT && d.inputs > 0) { + showDragLines([{node:d,port:0,portType:PORT_TYPE_INPUT}]); + } else { + resetMouseVars(); + } + mousedown_link = select_link; + if (select_link) { + selectedLinks.clear(); + selectedLinks.add(select_link); + updateSelection(); + } else { + selectedLinks.clear(); + } + } + redraw(); + return; + } + + resetMouseVars(); + hideDragLines(); + if (select_link) { + selectedLinks.clear(); + selectedLinks.add(select_link); + } + mousedown_link = select_link; + if (select_link) { + updateSelection(); + } + redraw(); + } + } + + var portLabelHoverTimeout = null; + var portLabelHover = null; + + + function getElementPosition(node) { + var d3Node = d3.select(node); + if (d3Node.attr('class') === 'red-ui-workspace-chart-event-layer') { + return [0,0]; + } + var result = []; + var localPos = [0,0]; + if (node.nodeName.toLowerCase() === 'g') { + var transform = d3Node.attr("transform"); + if (transform) { + localPos = d3.transform(transform).translate; + } + } else { + localPos = [d3Node.attr("x")||0,d3Node.attr("y")||0]; + } + var parentPos = getElementPosition(node.parentNode); + return [localPos[0]+parentPos[0],localPos[1]+parentPos[1]] + + } + + function getPortLabel(node,portType,portIndex) { + var result; + var nodePortLabels = (portType === PORT_TYPE_INPUT)?node.inputLabels:node.outputLabels; + if (nodePortLabels && nodePortLabels[portIndex]) { + return nodePortLabels[portIndex]; + } + var portLabels = (portType === PORT_TYPE_INPUT)?node._def.inputLabels:node._def.outputLabels; + if (typeof portLabels === 'string') { + result = portLabels; + } else if (typeof portLabels === 'function') { + try { + result = portLabels.call(node,portIndex); + } catch(err) { + console.log("Definition error: "+node.type+"."+((portType === PORT_TYPE_INPUT)?"inputLabels":"outputLabels"),err); + result = null; + } + } else if ($.isArray(portLabels)) { + result = portLabels[portIndex]; + } + return result; + } + function showTooltip(x,y,content,direction) { + var tooltip = eventLayer.append("g") + .attr("transform","translate("+x+","+y+")") + .attr("class","red-ui-flow-port-tooltip"); + + // First check for a user-provided newline - "\\n " + var newlineIndex = content.indexOf("\\n "); + if (newlineIndex > -1 && content[newlineIndex-1] !== '\\') { + content = content.substring(0,newlineIndex)+"..."; + } + + var lines = content.split("\n"); + var labelWidth = 6; + var labelHeight = 12; + var labelHeights = []; + var lineHeight = 0; + lines.forEach(function(l,i) { + var labelDimensions = calculateTextDimensions(l||" ", "red-ui-flow-port-tooltip-label"); + labelWidth = Math.max(labelWidth,labelDimensions[0] + 14); + labelHeights.push(labelDimensions[1]); + if (i === 0) { + lineHeight = labelDimensions[1]; + } + labelHeight += labelDimensions[1]; + }); + var labelWidth1 = (labelWidth/2)-5-2; + var labelWidth2 = labelWidth - 4; + + var labelHeight1 = (labelHeight/2)-5-2; + var labelHeight2 = labelHeight - 4; + var path; + var lx; + var ly = -labelHeight/2; + var anchor; + if (direction === "left") { + path = "M0 0 l -5 -5 v -"+(labelHeight1)+" q 0 -2 -2 -2 h -"+labelWidth+" q -2 0 -2 2 v "+(labelHeight2)+" q 0 2 2 2 h "+labelWidth+" q 2 0 2 -2 v -"+(labelHeight1)+" l 5 -5"; + lx = -14; + anchor = "end"; + } else if (direction === "right") { + path = "M0 0 l 5 -5 v -"+(labelHeight1)+" q 0 -2 2 -2 h "+labelWidth+" q 2 0 2 2 v "+(labelHeight2)+" q 0 2 -2 2 h -"+labelWidth+" q -2 0 -2 -2 v -"+(labelHeight1)+" l -5 -5" + lx = 14; + anchor = "start"; + } else if (direction === "top") { + path = "M0 0 l 5 -5 h "+(labelWidth1)+" q 2 0 2 -2 v -"+labelHeight+" q 0 -2 -2 -2 h -"+(labelWidth2)+" q -2 0 -2 2 v "+labelHeight+" q 0 2 2 2 h "+(labelWidth1)+" l 5 5" + lx = -labelWidth/2 + 6; + ly = -labelHeight-lineHeight+12; + anchor = "start"; + } + tooltip.append("path").attr("d",path); + lines.forEach(function(l,i) { + ly += labelHeights[i]; + // tooltip.append("path").attr("d","M "+(lx-10)+" "+ly+" l 20 0 m -10 -5 l 0 10 ").attr('r',2).attr("stroke","#f00").attr("stroke-width","1").attr("fill","none") + tooltip.append("svg:text").attr("class","red-ui-flow-port-tooltip-label") + .attr("x", lx) + .attr("y", ly) + .attr("text-anchor",anchor) + .text(l||" ") + }); + return tooltip; + } + + function portMouseOver(port,d,portType,portIndex) { + if (mouse_mode === RED.state.SELECTING_NODE) { + d3.event.stopPropagation(); + return; + } + clearTimeout(portLabelHoverTimeout); + var active = (mouse_mode!=RED.state.JOINING && mouse_mode != RED.state.QUICK_JOINING) || // Not currently joining - all ports active + ( + drag_lines.length > 0 && // Currently joining + drag_lines[0].portType !== portType && // INPUT->OUTPUT OUTPUT->INPUT + ( + !drag_lines[0].virtualLink || // Not a link wire + (drag_lines[0].node.type === 'link in' && d.type === 'link out') || + (drag_lines[0].node.type === 'link out' && d.type === 'link in') + ) + ) + + if (active && ((portType === PORT_TYPE_INPUT && ((d._def && d._def.inputLabels)||d.inputLabels)) || (portType === PORT_TYPE_OUTPUT && ((d._def && d._def.outputLabels)||d.outputLabels)))) { + portLabelHoverTimeout = setTimeout(function() { + var tooltip = getPortLabel(d,portType,portIndex); + if (!tooltip) { + return; + } + var pos = getElementPosition(port.node()); + portLabelHoverTimeout = null; + portLabelHover = showTooltip( + (pos[0]+(portType===PORT_TYPE_INPUT?-2:12)), + (pos[1]+5), + tooltip, + portType===PORT_TYPE_INPUT?"left":"right" + ); + },500); + } + port.classed("red-ui-flow-port-hovered",active); + } + function portMouseOut(port,d,portType,portIndex) { + if (mouse_mode === RED.state.SELECTING_NODE) { + d3.event.stopPropagation(); + return; + } + clearTimeout(portLabelHoverTimeout); + if (portLabelHover) { + portLabelHover.remove(); + portLabelHover = null; + } + port.classed("red-ui-flow-port-hovered",false); + } + + function junctionMouseOver(junction, d, portType) { + var active = (portType === undefined) || + (mouse_mode !== RED.state.JOINING && mouse_mode !== RED.state.QUICK_JOINING) || + (drag_lines.length > 0 && drag_lines[0].portType !== portType && !drag_lines[0].virtualLink) + junction.classed("red-ui-flow-junction-hovered", active); + } + function junctionMouseOut(junction, d) { + junction.classed("red-ui-flow-junction-hovered",false); + } + + function prepareDrag(mouse) { + mouse_mode = RED.state.MOVING; + // Called when movingSet should be prepared to be dragged + for (i=0;i 0 && clickElapsed < dblClickInterval) { + mouse_mode = RED.state.DEFAULT; + if (d.type != "subflow") { + if (/^subflow:/.test(d.type) && (d3.event.ctrlKey || d3.event.metaKey)) { + RED.workspaces.show(d.type.substring(8)); + } else { + RED.editor.edit(d); + } + } else { + RED.editor.editSubflow(activeSubflow); + } + clickElapsed = 0; + d3.event.stopPropagation(); + return; + } + if (mouse_mode === RED.state.MOVING) { + // Moving primed, but not active. + if (!groupNodeSelectPrimed && !d.selected && d.g && RED.nodes.group(d.g).selected) { + clearSelection(); + + selectGroup(RED.nodes.group(d.g), false); + enterActiveGroup(RED.nodes.group(d.g)) + + mousedown_node.selected = true; + movingSet.add(mousedown_node); + var mouse = d3.touches(this)[0]||d3.mouse(this); + mouse[0] += d.x-d.w/2; + mouse[1] += d.y-d.h/2; + prepareDrag(mouse); + updateSelection(); + return; + } + } + + groupNodeSelectPrimed = false; + + var direction = d._def? (d.inputs > 0 ? 1: 0) : (d.direction == "in" ? 0: 1) + var wasJoining = false; + if (mouse_mode === RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { + wasJoining = true; + if (drag_lines.length > 0) { + if (drag_lines[0].virtualLink) { + if (d.type === 'link in') { + direction = 1; + } else if (d.type === 'link out') { + direction = 0; + } + } else { + if (drag_lines[0].portType === 1) { + direction = PORT_TYPE_OUTPUT; + } else { + direction = PORT_TYPE_INPUT; + } + } + } + } + + portMouseUp(d, direction, 0); + if (wasJoining) { + d3.selectAll(".red-ui-flow-port-hovered").classed("red-ui-flow-port-hovered",false); + } + } + + + document.addEventListener('pointerlockchange', changeCallback, false); + document.addEventListener('mozpointerlockchange', changeCallback, false); + document.addEventListener('webkitpointerlockchange', changeCallback, false); + document.exitPointerLock = document.exitPointerLock || document.mozExitPointerLock || document.webkitExitPointerLock; + function getPosition(canvas, event) { + var x, y; + + if (event.x != undefined && event.y != undefined) { + x = event.x; + y = event.y; + } else // Firefox method to get the position + { + x = event.clientX + document.body.scrollLeft + + document.documentElement.scrollLeft; + y = event.clientY + document.body.scrollTop + + document.documentElement.scrollTop; + } + + x -= canvas.offsetLeft == null ? canvas.clientLeft : canvas.offsetLeft; + y -= canvas.offsetTop == null ? canvas.clientTop : canvas.offsetTop; + + return {x:x, y:y}; + } + //temporary proxy for when mouse is locked + function canvasMouseMove_locked(e) { + + var canvas = $(eventLayer[0])[0]; + + + // if we enter this for the first time, get the initial position + if (entryCoordinates.x == -1) { + entryCoordinates = getPosition(canvas, e); + } + + + //get a reference to the canvas + var movementX = e.movementX || + e.mozMovementX || + e.webkitMovementX || + 0; + + var movementY = e.movementY || + e.mozMovementY || + e.webkitMovementY || + 0; + + + // calculate the new coordinates where we should draw the ship + entryCoordinates.x = entryCoordinates.x + movementX; + entryCoordinates.y = entryCoordinates.y + movementY; + + if (entryCoordinates.x > chart.width() -65) { + entryCoordinates.x = chart.width()-65; + } else if (entryCoordinates.x < 0) { + entryCoordinates.x = 0; + } + + if (entryCoordinates.y > chart.height() - 85) { + entryCoordinates.y = chart.height() - 85; + } else if (entryCoordinates.y < 0) { + entryCoordinates.y = 0; + } + + + // determine the direction + var direction = 0; + if (movementX > 0) { + direction = 1; + } else if (movementX < 0) { + direction = -1; + } + // console.log(entryCoordinates) + + + d3.event = e + canvasMouseMove.call(this,e); + //canvasMouseMove.call(document.querySelector("g.red-ui-workspace-chart-event-layer")); + } + function changeCallback(e) { + var canvas = $(eventLayer[0])[0] + // var workspaceChart = $("g.red-ui-workspace-chart-event-layer")[0]; + // var workspaceChart = $("#red-ui-workspace-chart")[0]; + if (document.pointerLockElement === canvas || + document.mozPointerLockElement === canvas || + document.webkitPointerLockElement === canvas) { + + // we've got a pointerlock for our element, add a mouselistener + canvas.addEventListener("mousemove", canvasMouseMove_locked, false); + // outer.on("mousemove", canvasMouseMove_locked) + // document.addEventListener("mousemove", canvasMouseMove_locked, false); + } else { + + // pointer lock is no longer active, remove the callback + canvas.removeEventListener("mousemove", canvasMouseMove_locked, false); + // document.removeEventListener("mousemove", canvasMouseMove_locked, false); + // if(outer.off) {outer.off("mousemove", canvasMouseMove_locked)} + // if(outer.removeEventListener) {outer.removeEventListener("mousemove", canvasMouseMove_locked)} + // and reset the entry coordinates + entryCoordinates = {x:-1, y:-1}; + } + } + + function nodeMouseDown(d) { + if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); } + try { + // workspaceChart = $("g.red-ui-workspace-chart-event-layer")[0] + var canvas = $(eventLayer[0])[0] + canvas.requestPointerLock = canvas.requestPointerLock || + canvas.mozRequestPointerLock || + canvas.webkitRequestPointerLock; + canvas.requestPointerLock() + console.log("got pointer lock") + } catch (error) { + console.error(error) + } + + focusView(); + if (d3.event.button === 1) { + return; + } + //var touch0 = d3.event; + //var pos = [touch0.pageX,touch0.pageY]; + //RED.touch.radialMenu.show(d3.select(this),pos); + if (mouse_mode == RED.state.IMPORT_DRAGGING || mouse_mode == RED.state.DETACHED_DRAGGING) { + var historyEvent = RED.history.peek(); + if (activeSpliceLink) { + // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp + var spliceLink = d3.select(activeSpliceLink).data()[0]; + RED.nodes.removeLink(spliceLink); + var link1 = { + source:spliceLink.source, + sourcePort:spliceLink.sourcePort, + target: movingSet.get(0).n + }; + var link2 = { + source:movingSet.get(0).n, + sourcePort:0, + target: spliceLink.target + }; + RED.nodes.addLink(link1); + RED.nodes.addLink(link2); + + historyEvent.links = [link1,link2]; + historyEvent.removedLinks = [spliceLink]; + updateActiveNodes(); + } + + if (activeHoverGroup) { + for (var j=0;j 30 ? 25 : (mousedown_node.w > 0 ? 8 : 3); + if (edgeDelta < targetEdgeDelta) { + if (clickPosition < 0) { + cnodes = [mousedown_node].concat(RED.nodes.getAllUpstreamNodes(mousedown_node)); + } else { + cnodes = [mousedown_node].concat(RED.nodes.getAllDownstreamNodes(mousedown_node)); + } + } else { + cnodes = RED.nodes.getAllFlowNodes(mousedown_node); + } + for (var n=0;n 0) { + var selectClass; + var portType; + if ((drag_lines[0].virtualLink && drag_lines[0].portType === PORT_TYPE_INPUT) || drag_lines[0].portType === PORT_TYPE_OUTPUT) { + selectClass = ".red-ui-flow-port-input .red-ui-flow-port"; + portType = PORT_TYPE_INPUT; + } else { + selectClass = ".red-ui-flow-port-output .red-ui-flow-port"; + portType = PORT_TYPE_OUTPUT; + } + portMouseOver(d3.select(this.parentNode).selectAll(selectClass),d,portType,0); + } + } + } + function nodeMouseOut(d) { + if (RED.view.DEBUG) { console.warn("nodeMouseOut", mouse_mode,d); } + this.parentNode.classList.remove("red-ui-flow-node-hovered"); + clearTimeout(portLabelHoverTimeout); + if (portLabelHover) { + portLabelHover.remove(); + portLabelHover = null; + } + if (mouse_mode === RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { + if (drag_lines.length > 0) { + var selectClass; + var portType; + if ((drag_lines[0].virtualLink && drag_lines[0].portType === PORT_TYPE_INPUT) || drag_lines[0].portType === PORT_TYPE_OUTPUT) { + selectClass = ".red-ui-flow-port-input .red-ui-flow-port"; + portType = PORT_TYPE_INPUT; + } else { + selectClass = ".red-ui-flow-port-output .red-ui-flow-port"; + portType = PORT_TYPE_OUTPUT; + } + portMouseOut(d3.select(this.parentNode).selectAll(selectClass),d,portType,0); + } + } + } + + function portMouseDownProxy(e) { portMouseDown(this.__data__,this.__portType__,this.__portIndex__, e); } + function portTouchStartProxy(e) { portMouseDown(this.__data__,this.__portType__,this.__portIndex__, e); e.preventDefault() } + function portMouseUpProxy(e) { portMouseUp(this.__data__,this.__portType__,this.__portIndex__, e); } + function portTouchEndProxy(e) { portMouseUp(this.__data__,this.__portType__,this.__portIndex__, e); e.preventDefault() } + function portMouseOverProxy(e) { portMouseOver(d3.select(this), this.__data__,this.__portType__,this.__portIndex__, e); } + function portMouseOutProxy(e) { portMouseOut(d3.select(this), this.__data__,this.__portType__,this.__portIndex__, e); } + + function junctionMouseOverProxy(e) { junctionMouseOver(d3.select(this), this.__data__, this.__portType__) } + function junctionMouseOutProxy(e) { junctionMouseOut(d3.select(this), this.__data__) } + + function linkMouseDown(d) { + if (RED.view.DEBUG) { + console.warn("linkMouseDown", { mouse_mode, point: d3.mouse(this), event: d3.event }); + } + if (mouse_mode === RED.state.SELECTING_NODE) { + d3.event.stopPropagation(); + return; + } + if (d3.event.button === 2) { + return + } + mousedown_link = d; + + if (!(d3.event.metaKey || d3.event.ctrlKey)) { + clearSelection(); + } + if (d3.event.metaKey || d3.event.ctrlKey) { + if (!selectedLinks.has(mousedown_link)) { + selectedLinks.add(mousedown_link); + } else { + if (selectedLinks.length() !== 1) { + selectedLinks.remove(mousedown_link); + } + } + } else { + selectedLinks.add(mousedown_link); + } + updateSelection(); + redraw(); + focusView(); + d3.event.stopPropagation(); + if (!mousedown_link.link && movingSet.length() === 0 && (d3.event.touches || d3.event.button === 0) && selectedLinks.length() === 1 && selectedLinks.has(mousedown_link) && (d3.event.metaKey || d3.event.ctrlKey)) { + d3.select(this).classed("red-ui-flow-link-splice",true); + var point = d3.mouse(this); + var clickedGroup = getGroupAt(point[0],point[1]); + showQuickAddDialog({position:point, splice:mousedown_link, group:clickedGroup}); + } + } + function linkTouchStart(d) { + if (mouse_mode === RED.state.SELECTING_NODE) { + d3.event.stopPropagation(); + return; + } + mousedown_link = d; + clearSelection(); + selectedLinks.clear(); + selectedLinks.add(mousedown_link); + updateSelection(); + redraw(); + focusView(); + d3.event.stopPropagation(); + + var obj = d3.select(document.body); + var touch0 = d3.event.touches.item(0); + var pos = [touch0.pageX,touch0.pageY]; + touchStartTime = setTimeout(function() { + touchStartTime = null; + showTouchMenu(obj,pos); + },touchLongPressTimeout); + d3.event.preventDefault(); + } + + function groupMouseUp(g) { + if (dblClickPrimed && mousedown_group == g && clickElapsed > 0 && clickElapsed < dblClickInterval) { + mouse_mode = RED.state.DEFAULT; + RED.editor.editGroup(g); + d3.event.stopPropagation(); + return; + } + + } + + function groupMouseDown(g) { + var mouse = d3.touches(this.parentNode)[0]||d3.mouse(this.parentNode); + // if (! (mouse[0] < g.x+10 || mouse[0] > g.x+g.w-10 || mouse[1] < g.y+10 || mouse[1] > g.y+g.h-10) ) { + // return + // } + + focusView(); + if (d3.event.button === 1) { + return; + } + + if (mouse_mode == RED.state.QUICK_JOINING) { + d3.event.stopPropagation(); + return; + } else if (mouse_mode === RED.state.SELECTING_NODE) { + d3.event.stopPropagation(); + return; + } + + mousedown_group = g; + + var now = Date.now(); + clickElapsed = now-clickTime; + clickTime = now; + + dblClickPrimed = ( + lastClickNode == g && + (d3.event.touches || d3.event.button === 0) && + !d3.event.shiftKey && !d3.event.metaKey && !d3.event.altKey && !d3.event.ctrlKey && + clickElapsed < dblClickInterval + ); + lastClickNode = g; + + if (g.selected && (d3.event.ctrlKey||d3.event.metaKey)) { + if (g === activeGroup) { + exitActiveGroup(); + } + deselectGroup(g); + d3.event.stopPropagation(); + } else { + if (!g.selected) { + if (!d3.event.ctrlKey && !d3.event.metaKey) { + var ag = activeGroup; + clearSelection(); + if (ag && g.g === ag.id) { + enterActiveGroup(ag); + activeGroup.selected = true; + } + } + if (activeGroup) { + if (!RED.group.contains(activeGroup,g)) { + // Clicked on a group that is outside the activeGroup + exitActiveGroup(); + } else { + } + } + selectGroup(g,true);//!wasSelected); + } else if (activeGroup && g.g !== activeGroup.id){ + exitActiveGroup(); + } + + + if (d3.event.button != 2) { + var d = g.nodes[0]; + prepareDrag(mouse); + mousedown_group.dx = mousedown_group.x - mouse[0]; + mousedown_group.dy = mousedown_group.y - mouse[1]; + } + } + + updateSelection(); + redraw(); + d3.event.stopPropagation(); + } + + function selectGroup(g, includeNodes, addToMovingSet) { + if (!g.selected) { + g.selected = true; + g.dirty = true; + } + if (addToMovingSet !== false) { + movingSet.add(g); + } + if (includeNodes) { + var currentSet = new Set(movingSet.nodes()); + var allNodes = RED.group.getNodes(g,true); + allNodes.forEach(function(n) { + if (!currentSet.has(n)) { + movingSet.add(n) + // n.selected = true; + } + n.dirty = true; + }) + } + } + function enterActiveGroup(group) { + if (activeGroup) { + exitActiveGroup(); + } + group.active = true; + group.dirty = true; + activeGroup = group; + movingSet.remove(group); + } + function exitActiveGroup() { + if (activeGroup) { + activeGroup.active = false; + activeGroup.dirty = true; + deselectGroup(activeGroup); + selectGroup(activeGroup,true); + activeGroup = null; + } + } + function deselectGroup(g) { + if (g.selected) { + g.selected = false; + g.dirty = true; + } + var nodeSet = new Set(g.nodes); + nodeSet.add(g); + for (var i = movingSet.length()-1; i >= 0; i -= 1) { + var msn = movingSet.get(i); + if (nodeSet.has(msn.n) || msn.n === g) { + msn.n.selected = false; + msn.n.dirty = true; + movingSet.remove(msn.n,i) + } + } + } + function getGroupAt(x,y) { + // x,y expected to be in node-co-ordinate space + var candidateGroups = {}; + for (var i=0;i= g.x && x <= g.x + g.w && y >= g.y && y <= g.y + g.h) { + candidateGroups[g.id] = g; + } + } + var ids = Object.keys(candidateGroups); + if (ids.length > 1) { + ids.forEach(function(id) { + if (candidateGroups[id] && candidateGroups[id].g) { + delete candidateGroups[candidateGroups[id].g] + } + }) + ids = Object.keys(candidateGroups); + } + if (ids.length === 0) { + return null; + } else { + return candidateGroups[ids[ids.length-1]] + } + } + + function isButtonEnabled(d) { + var buttonEnabled = true; + var ws = RED.nodes.workspace(RED.workspaces.active()); + if (ws && !ws.disabled && !d.d) { + if (d._def.button.hasOwnProperty('enabled')) { + if (typeof d._def.button.enabled === "function") { + buttonEnabled = d._def.button.enabled.call(d); + } else { + buttonEnabled = d._def.button.enabled; + } + } + } else { + buttonEnabled = false; + } + return buttonEnabled; + } + + function nodeButtonClicked(d) { + if (mouse_mode === RED.state.SELECTING_NODE) { + if (d3.event) { + d3.event.stopPropagation(); + } + return; + } + var activeWorkspace = RED.workspaces.active(); + var ws = RED.nodes.workspace(activeWorkspace); + if (ws && !ws.disabled && !d.d) { + if (d._def.button.toggle) { + d[d._def.button.toggle] = !d[d._def.button.toggle]; + d.dirty = true; + } + if (d._def.button.onclick) { + try { + d._def.button.onclick.call(d); + } catch(err) { + console.log("Definition error: "+d.type+".onclick",err); + } + } + if (d.dirty) { + redraw(); + } + } else { + if (activeSubflow) { + RED.notify(RED._("notification.warning", {message:RED._("notification.warnings.nodeActionDisabledSubflow")}),"warning"); + } else { + RED.notify(RED._("notification.warning", {message:RED._("notification.warnings.nodeActionDisabled")}),"warning"); + } + } + if (d3.event) { + d3.event.preventDefault(); + } + } + + function showTouchMenu(obj,pos) { + var mdn = mousedown_node; + var options = []; + options.push({name:"delete",disabled:(movingSet.length()===0 && selectedLinks.length() === 0),onselect:function() {deleteSelection();}}); + options.push({name:"cut",disabled:(movingSet.length()===0),onselect:function() {copySelection();deleteSelection();}}); + options.push({name:"copy",disabled:(movingSet.length()===0),onselect:function() {copySelection();}}); + options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard, {generateIds: true, touchImport: true});}}); + options.push({name:"edit",disabled:(movingSet.length() != 1),onselect:function() { RED.editor.edit(mdn);}}); + options.push({name:"select",onselect:function() {selectAll();}}); + options.push({name:"undo",disabled:(RED.history.depth() === 0),onselect:function() {RED.history.pop();}}); + options.push({name:"add",onselect:function() { + chartPos = chart.offset(); + showQuickAddDialog({ + position:[pos[0]-chartPos.left+chart.scrollLeft(),pos[1]-chartPos.top+chart.scrollTop()], + touchTrigger:true + }) + }}); + + RED.touch.radialMenu.show(obj,pos,options); + resetMouseVars(); + } + + function createIconAttributes(iconUrl, icon_group, d) { + var fontAwesomeUnicode = null; + if (iconUrl.indexOf("font-awesome/") === 0) { + var iconName = iconUrl.substr(13); + var fontAwesomeUnicode = RED.nodes.fontAwesome.getIconUnicode(iconName); + if (!fontAwesomeUnicode) { + var iconPath = RED.utils.getDefaultNodeIcon(d._def, d); + iconUrl = RED.settings.apiRootUrl+"icons/"+iconPath.module+"/"+iconPath.file; + } + } + if (fontAwesomeUnicode) { + // Since Node-RED workspace uses SVG, i tag cannot be used for font-awesome icon. + // On SVG, use text tag as an alternative. + icon_group.append("text") + .attr("xlink:href",iconUrl) + .attr("class","fa-lg") + .attr("x",15) + .text(fontAwesomeUnicode); + } else { + var icon = icon_group.append("image") + .style("display","none") + .attr("xlink:href",iconUrl) + .attr("class","red-ui-flow-node-icon") + .attr("x",0) + .attr("width","30") + .attr("height","30"); + + var img = new Image(); + img.src = iconUrl; + img.onload = function() { + if (!iconUrl.match(/\.svg$/)) { + var largestEdge = Math.max(img.width,img.height); + var scaleFactor = 1; + if (largestEdge > 30) { + scaleFactor = 30/largestEdge; + } + var width = img.width * scaleFactor; + var height = img.height * scaleFactor; + icon.attr("width",width); + icon.attr("height",height); + icon.attr("x",15-width/2); + } + icon.attr("xlink:href",iconUrl); + icon.style("display",null); + //if ("right" == d._def.align) { + // icon.attr("x",function(d){return d.w-img.width-1-(d.outputs>0?5:0);}); + // icon_shade.attr("x",function(d){return d.w-30}); + // icon_shade_border.attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2);}); + //} + } + } + } + + function redrawStatus(d,nodeEl) { + if (d.z !== RED.workspaces.active()) { + return; + } + if (!nodeEl) { + nodeEl = document.getElementById(d.id); + } + if (nodeEl) { + // Do not show node status if: + // - global flag set + // - node has no status + // - node is disabled + if (!showStatus || !d.status || d.d === true) { + nodeEl.__statusGroup__.style.display = "none"; + } else { + nodeEl.__statusGroup__.style.display = "inline"; + var fill = status_colours[d.status.fill]; // Only allow our colours for now + if (d.status.shape == null && fill == null) { + nodeEl.__statusShape__.style.display = "none"; + nodeEl.__statusGroup__.setAttribute("transform","translate(-14,"+(d.h+3)+")"); + } else { + nodeEl.__statusGroup__.setAttribute("transform","translate(3,"+(d.h+3)+")"); + var statusClass = "red-ui-flow-node-status-"+(d.status.shape||"dot")+"-"+d.status.fill; + nodeEl.__statusShape__.style.display = "inline"; + nodeEl.__statusShape__.setAttribute("class","red-ui-flow-node-status "+statusClass); + } + if (d.status.hasOwnProperty('text')) { + nodeEl.__statusLabel__.textContent = d.status.text; + } else { + nodeEl.__statusLabel__.textContent = ""; + } + } + delete d.dirtyStatus; + } + } + + var pendingRedraw; + + function redraw() { + if (RED.view.DEBUG_SYNC_REDRAW) { + _redraw(); + } else { + if (pendingRedraw) { + cancelAnimationFrame(pendingRedraw); + } + pendingRedraw = requestAnimationFrame(_redraw); + } + } + + function _redraw() { + eventLayer.attr("transform","scale("+scaleFactor+")"); + outer.attr("width", space_width*scaleFactor).attr("height", space_height*scaleFactor); + + // Don't bother redrawing nodes if we're drawing links + if (showAllLinkPorts !== -1 || mouse_mode != RED.state.JOINING) { + + var dirtyNodes = {}; + + if (activeSubflow) { + var subflowOutputs = nodeLayer.selectAll(".red-ui-flow-subflow-port-output").data(activeSubflow.out,function(d,i){ return d.id;}); + subflowOutputs.exit().remove(); + var outGroup = subflowOutputs.enter().insert("svg:g").attr("class","red-ui-flow-node red-ui-flow-subflow-port-output") + outGroup.each(function(d,i) { + var node = d3.select(this); + var nodeContents = document.createDocumentFragment(); + + d.h = 40; + d.resize = true; + d.dirty = true; + + var mainRect = document.createElementNS("http://www.w3.org/2000/svg","rect"); + mainRect.__data__ = d; + mainRect.setAttribute("class", "red-ui-flow-subflow-port"); + mainRect.setAttribute("rx", 8); + mainRect.setAttribute("ry", 8); + mainRect.setAttribute("width", 40); + mainRect.setAttribute("height", 40); + node[0][0].__mainRect__ = mainRect; + d3.select(mainRect) + .on("mouseup",nodeMouseUp) + .on("mousedown",nodeMouseDown) + .on("touchstart",nodeTouchStart) + .on("touchend",nodeTouchEnd) + nodeContents.appendChild(mainRect); + + var output_groupEl = document.createElementNS("http://www.w3.org/2000/svg","g"); + output_groupEl.setAttribute("x",0); + output_groupEl.setAttribute("y",0); + node[0][0].__outputLabelGroup__ = output_groupEl; + + var output_output = document.createElementNS("http://www.w3.org/2000/svg","text"); + output_output.setAttribute("class","red-ui-flow-port-label"); + output_output.style["font-size"] = "10px"; + output_output.textContent = "output"; + output_groupEl.appendChild(output_output); + node[0][0].__outputOutput__ = output_output; + + var output_number = document.createElementNS("http://www.w3.org/2000/svg","text"); + output_number.setAttribute("class","red-ui-flow-port-label red-ui-flow-port-index"); + output_number.setAttribute("x",0); + output_number.setAttribute("y",0); + output_number.textContent = d.i+1; + output_groupEl.appendChild(output_number); + node[0][0].__outputNumber__ = output_number; + + var output_border = document.createElementNS("http://www.w3.org/2000/svg","path"); + output_border.setAttribute("d","M 40 1 l 0 38") + output_border.setAttribute("class", "red-ui-flow-node-icon-shade-border") + output_groupEl.appendChild(output_border); + node[0][0].__outputBorder__ = output_border; + + nodeContents.appendChild(output_groupEl); + + var text = document.createElementNS("http://www.w3.org/2000/svg","g"); + text.setAttribute("class","red-ui-flow-port-label"); + text.setAttribute("transform","translate(38,0)"); + text.setAttribute('style', 'fill : #888'); // hard coded here! + node[0][0].__textGroup__ = text; + nodeContents.append(text); + + var portEl = document.createElementNS("http://www.w3.org/2000/svg","g"); + portEl.setAttribute('transform','translate(-5,15)') + + var port = document.createElementNS("http://www.w3.org/2000/svg","rect"); + port.setAttribute("class","red-ui-flow-port"); + port.setAttribute("rx",3); + port.setAttribute("ry",3); + port.setAttribute("width",10); + port.setAttribute("height",10); + portEl.appendChild(port); + port.__data__ = d; + + d3.select(port) + .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);} ) + .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} ) + .on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);}) + .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} ) + .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_INPUT,0);}) + .on("mouseout",function(d){portMouseOut(d3.select(this),d,PORT_TYPE_INPUT,0);}); + + node[0][0].__port__ = portEl + nodeContents.appendChild(portEl); + node[0][0].appendChild(nodeContents); + }); + + var subflowInputs = nodeLayer.selectAll(".red-ui-flow-subflow-port-input").data(activeSubflow.in,function(d,i){ return d.id;}); + subflowInputs.exit().remove(); + var inGroup = subflowInputs.enter().insert("svg:g").attr("class","red-ui-flow-node red-ui-flow-subflow-port-input").attr("transform",function(d) { return "translate("+(d.x-20)+","+(d.y-20)+")"}); + inGroup.each(function(d,i) { + d.w=40; + d.h=40; + }); + inGroup.append("rect").attr("class","red-ui-flow-subflow-port").attr("rx",8).attr("ry",8).attr("width",40).attr("height",40) + // TODO: This is exactly the same set of handlers used for regular nodes - DRY + .on("mouseup",nodeMouseUp) + .on("mousedown",nodeMouseDown) + .on("touchstart",nodeTouchStart) + .on("touchend", nodeTouchEnd); + + inGroup.append("g").attr('transform','translate(35,15)').append("rect").attr("class","red-ui-flow-port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10) + .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_OUTPUT,i);} ) + .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_OUTPUT,i);d3.event.preventDefault();} ) + .on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE_OUTPUT,i);}) + .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_OUTPUT,i);d3.event.preventDefault();} ) + .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_OUTPUT,0);}) + .on("mouseout",function(d) {portMouseOut(d3.select(this),d,PORT_TYPE_OUTPUT,0);}); + + inGroup.append("svg:text").attr("class","red-ui-flow-port-label").attr("x",18).attr("y",20).style("font-size","10px").text("input"); + + var subflowStatus = nodeLayer.selectAll(".red-ui-flow-subflow-port-status").data(activeSubflow.status?[activeSubflow.status]:[],function(d,i){ return d.id;}); + subflowStatus.exit().remove(); + + var statusGroup = subflowStatus.enter().insert("svg:g").attr("class","red-ui-flow-node red-ui-flow-subflow-port-status").attr("transform",function(d) { return "translate("+(d.x-20)+","+(d.y-20)+")"}); + statusGroup.each(function(d,i) { + d.w=40; + d.h=40; + }); + statusGroup.append("rect").attr("class","red-ui-flow-subflow-port").attr("rx",8).attr("ry",8).attr("width",40).attr("height",40) + // TODO: This is exactly the same set of handlers used for regular nodes - DRY + .on("mouseup",nodeMouseUp) + .on("mousedown",nodeMouseDown) + .on("touchstart",nodeTouchStart) + .on("touchend", nodeTouchEnd); + + statusGroup.append("g").attr('transform','translate(-5,15)').append("rect").attr("class","red-ui-flow-port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10) + .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);} ) + .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} ) + .on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);}) + .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} ) + .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_INPUT,0);}) + .on("mouseout",function(d){portMouseOut(d3.select(this),d,PORT_TYPE_INPUT,0);}); + + statusGroup.append("svg:text").attr("class","red-ui-flow-port-label").attr("x",22).attr("y",20).style("font-size","10px").text("status"); + + subflowOutputs.each(function(d,i) { + if (d.dirty) { + + var port_height = 40; + + var self = this; + var thisNode = d3.select(this); + + dirtyNodes[d.id] = d; + + var label = getPortLabel(activeSubflow, PORT_TYPE_OUTPUT, d.i) || ""; + var hideLabel = (label.length < 1) + + var labelParts; + if (d.resize || this.__hideLabel__ !== hideLabel || this.__label__ !== label) { + labelParts = getLabelParts(label, "red-ui-flow-node-label"); + if (labelParts.lines.length !== this.__labelLineCount__ || this.__label__ !== label) { + d.resize = true; + } + this.__label__ = label; + this.__labelLineCount__ = labelParts.lines.length; + + if (hideLabel) { + d.h = Math.max(port_height,(d.outputs || 0) * 15); + } else { + d.h = Math.max(6+24*labelParts.lines.length,(d.outputs || 0) * 15, port_height); + } + this.__hideLabel__ = hideLabel; + } + + if (d.resize) { + var ow = d.w; + if (hideLabel) { + d.w = port_height; + } else { + d.w = Math.max(port_height,20*(Math.ceil((labelParts.width+50+7)/20)) ); + } + if (ow !== undefined) { + d.x += (d.w-ow)/2; + } + d.resize = false; + } + + this.setAttribute("transform", "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"); + // This might be the first redraw after a node has been click-dragged to start a move. + // So its selected state might have changed since the last redraw. + this.classList.toggle("red-ui-flow-node-selected", !!d.selected ) + if (mouse_mode != RED.state.MOVING_ACTIVE) { + this.classList.toggle("red-ui-flow-node-disabled", d.d === true); + this.__mainRect__.setAttribute("width", d.w) + this.__mainRect__.setAttribute("height", d.h) + this.__mainRect__.classList.toggle("red-ui-flow-node-highlighted",!!d.highlighted ); + + if (labelParts) { + // The label has changed + var sa = labelParts.lines; + var sn = labelParts.lines.length; + var textLines = this.__textGroup__.childNodes; + while(textLines.length > sn) { + textLines[textLines.length-1].remove(); + } + for (var i=0; i0?7:0))/20)) ); + } + if (ow !== undefined) { + d.x += (d.w-ow)/2; + } + d.resize = false; + } + if (d._colorChanged) { + var newColor = RED.utils.getNodeColor(d.type,d._def); + this.__mainRect__.setAttribute("fill",newColor); + if (this.__buttonGroupButton__) { + this.__buttonGroupButton__.settAttribute("fill",newColor); + } + delete d._colorChanged; + } + //thisNode.selectAll(".centerDot").attr({"cx":function(d) { return d.w/2;},"cy":function(d){return d.h/2}}); + this.setAttribute("transform", "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"); + // This might be the first redraw after a node has been click-dragged to start a move. + // So its selected state might have changed since the last redraw. + this.classList.toggle("red-ui-flow-node-selected", !!d.selected ) + if (mouse_mode != RED.state.MOVING_ACTIVE) { + this.classList.toggle("red-ui-flow-node-disabled", d.d === true); + this.__mainRect__.setAttribute("width", d.w) + this.__mainRect__.setAttribute("height", d.h) + this.__mainRect__.classList.toggle("red-ui-flow-node-highlighted",!!d.highlighted ); + + if (labelParts) { + // The label has changed + var sa = labelParts.lines; + var sn = labelParts.lines.length; + var textLines = this.__textGroup__.childNodes; + while(textLines.length > sn) { + textLines[textLines.length-1].remove(); + } + for (var i=0; i numOutputs) { + var port = this.__outputs__.pop(); + RED.hooks.trigger("viewRemovePort",{ + node:d, + el:this, + port:port, + portType: "output", + portIndex: this.__outputs__.length + }) + port.remove(); + } + for(var portIndex = 0; portIndex < numOutputs; portIndex++ ) { + var portGroup; + if (portIndex === this.__outputs__.length) { + portGroup = document.createElementNS("http://www.w3.org/2000/svg","g"); + portGroup.setAttribute("class","red-ui-flow-port-output"); + var portPort; + if (d.type === "link out") { + portPort = document.createElementNS("http://www.w3.org/2000/svg","circle"); + portPort.setAttribute("cx",11); + portPort.setAttribute("cy",5); + portPort.setAttribute("r",5); + portPort.setAttribute("class","red-ui-flow-port red-ui-flow-link-port"); + } else { + portPort = document.createElementNS("http://www.w3.org/2000/svg","rect"); + portPort.setAttribute("rx",3); + portPort.setAttribute("ry",3); + portPort.setAttribute("width",10); + portPort.setAttribute("height",10); + portPort.setAttribute("class","red-ui-flow-port"); + } + portGroup.appendChild(portPort); + portGroup.__port__ = portPort; + portPort.__data__ = this.__data__; + portPort.__portType__ = PORT_TYPE_OUTPUT; + portPort.__portIndex__ = portIndex; + portPort.addEventListener("mousedown", portMouseDownProxy); + portPort.addEventListener("touchstart", portTouchStartProxy); + portPort.addEventListener("mouseup", portMouseUpProxy); + portPort.addEventListener("touchend", portTouchEndProxy); + portPort.addEventListener("mouseover", portMouseOverProxy); + portPort.addEventListener("mouseout", portMouseOutProxy); + + this.appendChild(portGroup); + this.__outputs__.push(portGroup); + RED.hooks.trigger("viewAddPort",{node:d,el: this, port: portGroup, portType: "output", portIndex: portIndex}) + } else { + portGroup = this.__outputs__[portIndex]; + } + var x = d.w - 5; + var y = (d.h/2)-((numOutputs-1)/2)*13; + portGroup.setAttribute("transform","translate("+x+","+((y+13*portIndex)-5)+")") + } + if (d._def.icon) { + var icon = thisNode.select(".red-ui-flow-node-icon"); + var faIcon = thisNode.select(".fa-lg"); + var current_url; + if (!icon.empty()) { + current_url = icon.attr("xlink:href"); + } else { + current_url = faIcon.attr("xlink:href"); + } + var new_url = RED.utils.getNodeIcon(d._def,d); + if (new_url !== current_url) { + if (!icon.empty()) { + icon.remove(); + } else { + faIcon.remove(); + } + var iconGroup = thisNode.select(".red-ui-flow-node-icon-group"); + createIconAttributes(new_url, iconGroup, d); + icon = thisNode.select(".red-ui-flow-node-icon"); + faIcon = thisNode.select(".fa-lg"); + } + + icon.attr("y",function(){return (d.h-d3.select(this).attr("height"))/2;}); + this.__iconShade__.setAttribute("height", d.h ); + this.__iconShadeBorder__.setAttribute("d", + "M " + (((!d._def.align && d.inputs !== 0 && d.outputs === 0) || "right" === d._def.align) ? 0 : 30) + " 1 l 0 " + (d.h - 2) + ); + faIcon.attr("y",(d.h+13)/2); + } + // this.__changeBadge__.setAttribute("transform", "translate("+(d.w-10)+", -2)"); + // this.__changeBadge__.classList.toggle("hide", !(d.changed||d.moved)); + // this.__errorBadge__.setAttribute("transform", "translate("+(d.w-10-((d.changed||d.moved)?14:0))+", -2)"); + // this.__errorBadge__.classList.toggle("hide", d.valid); + + thisNode.selectAll(".red-ui-flow-port-input").each(function(d,i) { + var port = d3.select(this); + port.attr("transform",function(d){return "translate(-5,"+((d.h/2)-5)+")";}) + }); + + if (d._def.button) { + var buttonEnabled = isButtonEnabled(d); + this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-disabled", !buttonEnabled); + if(RED.runtime && Object.hasOwn(RED.runtime,'started')) { + this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-stopped", !RED.runtime.started); + } + + var x = d._def.align == "right"?d.w-6:-25; + if (d._def.button.toggle && !d[d._def.button.toggle]) { + x = x - (d._def.align == "right"?8:-8); + } + this.__buttonGroup__.setAttribute("transform", "translate("+x+",2)"); + + if (d._def.button.toggle) { + this.__buttonGroupButton__.setAttribute("fill-opacity",d[d._def.button.toggle]?1:0.2) + this.__buttonGroupBackground__.setAttribute("fill-opacity",d[d._def.button.toggle]?1:0.2) + } + + if (typeof d._def.button.visible === "function") { // is defined and a function... + if (d._def.button.visible.call(d) === false) { + this.__buttonGroup__.style.display = "none"; + } + else { + this.__buttonGroup__.style.display = "inherit"; + } + } + } + // thisNode.selectAll(".node_badge_group").attr("transform",function(d){return "translate("+(d.w-40)+","+(d.h+3)+")";}); + // thisNode.selectAll("text.node_badge_label").text(function(d,i) { + // if (d._def.badge) { + // if (typeof d._def.badge == "function") { + // try { + // return d._def.badge.call(d); + // } catch(err) { + // console.log("Definition error: "+d.type+".badge",err); + // return ""; + // } + // } else { + // return d._def.badge; + // } + // } + // return ""; + // }); + } + + if (d.dirtyStatus) { + redrawStatus(d,this); + } + d.dirty = false; + if (d.g) { + if (!dirtyGroups[d.g]) { + var gg = d.g; + while (gg && !dirtyGroups[gg]) { + dirtyGroups[gg] = RED.nodes.group(gg); + gg = dirtyGroups[gg].g; + } + } + } + } + + RED.hooks.trigger("viewRedrawNode",{node:d,el:this}) + }); + + if (nodesReordered) { + node.sort(function(a,b) { + return a._index - b._index; + }) + } + + var junction = junctionLayer.selectAll(".red-ui-flow-junction").data( + activeJunctions, + d => d.id + ) + var junctionEnter = junction.enter().insert("svg:g").attr("class","red-ui-flow-junction") + junctionEnter.each(function(d,i) { + var junction = d3.select(this); + var contents = document.createDocumentFragment(); + // d.added = true; + var junctionBack = document.createElementNS("http://www.w3.org/2000/svg","rect"); + junctionBack.setAttribute("class","red-ui-flow-junction-background"); + junctionBack.setAttribute("x",-5); + junctionBack.setAttribute("y",-5); + junctionBack.setAttribute("width",10); + junctionBack.setAttribute("height",10); + junctionBack.setAttribute("rx",3); + junctionBack.setAttribute("ry",3); + junctionBack.__data__ = d; + this.__junctionBack__ = junctionBack; + contents.appendChild(junctionBack); + + var junctionInput = document.createElementNS("http://www.w3.org/2000/svg","rect"); + junctionInput.setAttribute("class","red-ui-flow-junction-port red-ui-flow-junction-port-input"); + junctionInput.setAttribute("x",-5); + junctionInput.setAttribute("y",-5); + junctionInput.setAttribute("width",10); + junctionInput.setAttribute("height",10); + junctionInput.setAttribute("rx",3); + junctionInput.setAttribute("ry",3); + junctionInput.__data__ = d; + junctionInput.__portType__ = PORT_TYPE_INPUT; + junctionInput.__portIndex__ = 0; + this.__junctionInput__ = junctionOutput; + contents.appendChild(junctionInput); + junctionInput.addEventListener("mouseup", portMouseUpProxy); + junctionInput.addEventListener("mousedown", portMouseDownProxy); + + + this.__junctionInput__ = junctionInput; + contents.appendChild(junctionInput); + var junctionOutput = document.createElementNS("http://www.w3.org/2000/svg","rect"); + junctionOutput.setAttribute("class","red-ui-flow-junction-port red-ui-flow-junction-port-output"); + junctionOutput.setAttribute("x",-5); + junctionOutput.setAttribute("y",-5); + junctionOutput.setAttribute("width",10); + junctionOutput.setAttribute("height",10); + junctionOutput.setAttribute("rx",3); + junctionOutput.setAttribute("ry",3); + junctionOutput.__data__ = d; + junctionOutput.__portType__ = PORT_TYPE_OUTPUT; + junctionOutput.__portIndex__ = 0; + this.__junctionOutput__ = junctionOutput; + contents.appendChild(junctionOutput); + junctionOutput.addEventListener("mouseup", portMouseUpProxy); + junctionOutput.addEventListener("mousedown", portMouseDownProxy); + + junctionOutput.addEventListener("mouseover", junctionMouseOverProxy); + junctionOutput.addEventListener("mouseout", junctionMouseOutProxy); + junctionInput.addEventListener("mouseover", junctionMouseOverProxy); + junctionInput.addEventListener("mouseout", junctionMouseOutProxy); + junctionBack.addEventListener("mouseover", junctionMouseOverProxy); + junctionBack.addEventListener("mouseout", junctionMouseOutProxy); + + // These handlers expect to be registered as d3 events + d3.select(junctionBack).on("mousedown", nodeMouseDown).on("mouseup", nodeMouseUp); + + junction[0][0].appendChild(contents); + }) + junction.exit().remove(); + junction.each(function(d) { + var junction = d3.select(this); + this.setAttribute("transform", "translate(" + (d.x) + "," + (d.y) + ")"); + if (d.dirty) { + junction.classed("red-ui-flow-junction-dragging", mouse_mode === RED.state.MOVING_ACTIVE && movingSet.has(d)) + junction.classed("selected", !!d.selected) + dirtyNodes[d.id] = d; + + if (d.g) { + if (!dirtyGroups[d.g]) { + var gg = d.g; + while (gg && !dirtyGroups[gg]) { + dirtyGroups[gg] = RED.nodes.group(gg); + gg = dirtyGroups[gg].g; + } + } + } + + } + + }) + + var link = linkLayer.selectAll(".red-ui-flow-link").data( + activeLinks, + function(d) { + return d.source.id+":"+d.sourcePort+":"+d.target.id+":"+d.target.i; + } + ); + var linkEnter = link.enter().insert("g",".red-ui-flow-node").attr("class","red-ui-flow-link"); + + linkEnter.each(function(d,i) { + var l = d3.select(this); + var pathContents = document.createDocumentFragment(); + + d.added = true; + var pathBack = document.createElementNS("http://www.w3.org/2000/svg","path"); + pathBack.__data__ = d; + pathBack.setAttribute("class","red-ui-flow-link-background red-ui-flow-link-path"+(d.link?" red-ui-flow-link-link":"")); + this.__pathBack__ = pathBack; + pathContents.appendChild(pathBack); + d3.select(pathBack) + .on("mousedown",linkMouseDown) + .on("touchstart",linkTouchStart) + .on("mousemove", function(d) { + if (mouse_mode === RED.state.SLICING) { + + selectedLinks.add(d) + l.classed("red-ui-flow-link-splice",true) + redraw() + } else if (mouse_mode === RED.state.SLICING_JUNCTION && !d.link) { + if (!l.classed("red-ui-flow-link-splice")) { + // Find intersection point + var lineLength = pathLine.getTotalLength(); + var pos; + var delta = Infinity; + for (var i = 0; i < lineLength; i++) { + var linePos = pathLine.getPointAtLength(i); + var posDeltaX = Math.abs(linePos.x-d3.event.offsetX) + var posDeltaY = Math.abs(linePos.y-d3.event.offsetY) + var posDelta = posDeltaX*posDeltaX + posDeltaY*posDeltaY + if (posDelta < delta) { + pos = linePos + delta = posDelta + } + } + d._sliceLocation = pos + selectedLinks.add(d) + l.classed("red-ui-flow-link-splice",true) + redraw() + } + } + }) + + var pathOutline = document.createElementNS("http://www.w3.org/2000/svg","path"); + pathOutline.__data__ = d; + pathOutline.setAttribute("class","red-ui-flow-link-outline red-ui-flow-link-path"); + this.__pathOutline__ = pathOutline; + pathContents.appendChild(pathOutline); + + var pathLine = document.createElementNS("http://www.w3.org/2000/svg","path"); + pathLine.__data__ = d; + pathLine.setAttribute("class","red-ui-flow-link-line red-ui-flow-link-path"+ + (d.link?" red-ui-flow-link-link":(activeSubflow?" red-ui-flow-subflow-link":""))); + this.__pathLine__ = pathLine; + pathContents.appendChild(pathLine); + + l[0][0].appendChild(pathContents); + }); + + link.exit().remove(); + link.each(function(d) { + var link = d3.select(this); + if (d.added || d.selected || dirtyNodes[d.source.id] || dirtyNodes[d.target.id]) { + var numOutputs = d.source.outputs || 1; + var sourcePort = d.sourcePort || 0; + var y = -((numOutputs-1)/2)*13 +13*sourcePort; + d.x1 = d.source.x+(d.source.w/2||0); + d.y1 = d.source.y+y; + d.x2 = d.target.x-(d.target.w/2||0); + d.y2 = d.target.y; + + // return "M "+d.x1+" "+d.y1+ + // " C "+(d.x1+scale*node_width)+" "+(d.y1+scaleY*node_height)+" "+ + // (d.x2-scale*node_width)+" "+(d.y2-scaleY*node_height)+" "+ + // d.x2+" "+d.y2; + var path = generateLinkPath(d.x1,d.y1,d.x2,d.y2,1); + if (/NaN/.test(path)) { + path = "" + } + this.__pathBack__.setAttribute("d",path); + this.__pathOutline__.setAttribute("d",path); + this.__pathLine__.setAttribute("d",path); + this.__pathLine__.classList.toggle("red-ui-flow-node-disabled",!!(d.source.d || d.target.d)); + this.__pathLine__.classList.toggle("red-ui-flow-subflow-link", !d.link && activeSubflow); + } + + this.classList.toggle("red-ui-flow-link-selected", !!d.selected); + + var connectedToUnknown = !!(d.target.type == "unknown" || d.source.type == "unknown"); + this.classList.toggle("red-ui-flow-link-unknown",!!(d.target.type == "unknown" || d.source.type == "unknown")) + delete d.added; + }) + var offLinks = linkLayer.selectAll(".red-ui-flow-link-off-flow").data( + activeFlowLinks, + function(d) { + return d.node.id+":"+d.refresh + } + ); + + var offLinksEnter = offLinks.enter().insert("g",".red-ui-flow-node").attr("class","red-ui-flow-link-off-flow"); + offLinksEnter.each(function(d,i) { + var g = d3.select(this); + var s = 1; + var labelAnchor = "start"; + if (d.node.type === "link in") { + s = -1; + labelAnchor = "end"; + } + var stemLength = s*30; + var branchLength = s*20; + var l = g.append("svg:path").attr("class","red-ui-flow-link-link").attr("d","M 0 0 h "+stemLength); + var links = d.links; + var flows = Object.keys(links); + var tabOrder = RED.nodes.getWorkspaceOrder(); + flows.sort(function(A,B) { + return tabOrder.indexOf(A) - tabOrder.indexOf(B); + }); + var linkWidth = 10; + var h = node_height; + var y = -(flows.length-1)*h/2; + var linkGroups = g.selectAll(".red-ui-flow-link-group").data(flows); + var enterLinkGroups = linkGroups.enter().append("g").attr("class","red-ui-flow-link-group") + .on('mouseover', function() { if (mouse_mode !== 0) { return } d3.select(this).classed('red-ui-flow-link-group-active',true)}) + .on('mouseout', function() {if (mouse_mode !== 0) { return } d3.select(this).classed('red-ui-flow-link-group-active',false)}) + .on('mousedown', function() { d3.event.preventDefault(); d3.event.stopPropagation(); }) + .on('mouseup', function(f) { + if (mouse_mode !== 0) { + return + } + d3.event.stopPropagation(); + var targets = d.links[f]; + RED.workspaces.show(f); + targets.forEach(function(n) { + n.selected = true; + n.dirty = true; + movingSet.add(n); + if (targets.length === 1) { + RED.view.reveal(n.id); + } + }); + updateSelection(); + redraw(); + }); + enterLinkGroups.each(function(f) { + var linkG = d3.select(this); + linkG.append("svg:path") + .attr("class","red-ui-flow-link-link") + .attr("d", + "M "+stemLength+" 0 "+ + "C "+(stemLength+(1.7*branchLength))+" "+0+ + " "+(stemLength+(0.1*branchLength))+" "+y+" "+ + (stemLength+branchLength*1.5)+" "+y+" " + ); + linkG.append("svg:path") + .attr("class","red-ui-flow-link-port") + .attr("d", + "M "+(stemLength+branchLength*1.5+s*(linkWidth+7))+" "+(y-12)+" "+ + "h "+(-s*linkWidth)+" "+ + "a 3 3 45 0 "+(s===1?"0":"1")+" "+(s*-3)+" 3 "+ + "v 18 "+ + "a 3 3 45 0 "+(s===1?"0":"1")+" "+(s*3)+" 3 "+ + "h "+(s*linkWidth) + ); + linkG.append("svg:path") + .attr("class","red-ui-flow-link-port") + .attr("d", + "M "+(stemLength+branchLength*1.5+s*(linkWidth+10))+" "+(y-12)+" "+ + "h "+(s*(linkWidth*3))+" "+ + "M "+(stemLength+branchLength*1.5+s*(linkWidth+10))+" "+(y+12)+" "+ + "h "+(s*(linkWidth*3)) + ).style("stroke-dasharray","12 3 8 4 3"); + linkG.append("rect").attr("class","red-ui-flow-port red-ui-flow-link-port") + .attr("x",stemLength+branchLength*1.5-4+(s*4)) + .attr("y",y-4) + .attr("rx",2) + .attr("ry",2) + .attr("width",8) + .attr("height",8); + linkG.append("rect") + .attr("x",stemLength+branchLength*1.5-(s===-1?node_width:0)) + .attr("y",y-12) + .attr("width",node_width) + .attr("height",24) + .style("stroke","none") + .style("fill","transparent") + var tab = RED.nodes.workspace(f); + var label; + if (tab) { + label = tab.label || tab.id; + } + linkG.append("svg:text") + .attr("class","red-ui-flow-port-label") + .attr("x",stemLength+branchLength*1.5+(s*15)) + .attr("y",y+1) + .style("font-size","10px") + .style("text-anchor",labelAnchor) + .text(label); + + y += h; + }); + linkGroups.exit().remove(); + }); + offLinks.exit().remove(); + offLinks = linkLayer.selectAll(".red-ui-flow-link-off-flow"); + offLinks.each(function(d) { + var s = 1; + if (d.node.type === "link in") { + s = -1; + } + var link = d3.select(this); + link.attr("transform", function(d) { return "translate(" + (d.node.x+(s*d.node.w/2)) + "," + (d.node.y) + ")"; }); + + }) + + var group = groupLayer.selectAll(".red-ui-flow-group").data(activeGroups,function(d) { return d.id }); + group.exit().each(function(d,i) { + document.getElementById("group_select_"+d.id).remove() + }).remove(); + var groupEnter = group.enter().insert("svg:g").attr("class", "red-ui-flow-group") + var addedGroups = false; + groupEnter.each(function(d,i) { + addedGroups = true; + var g = d3.select(this); + g.attr("id",d.id); + + var groupBorderRadius = 4; + + var selectGroup = groupSelectLayer.append('g').attr("class", "red-ui-flow-group").attr("id","group_select_"+d.id); + selectGroup.append('rect').classed("red-ui-flow-group-outline-select",true) + .classed("red-ui-flow-group-outline-select-background",true) + .attr('rx',groupBorderRadius).attr('ry',groupBorderRadius) + .attr("x",-4) + .attr("y",-4); + + + selectGroup.append('rect').classed("red-ui-flow-group-outline-select",true) + .attr('rx',groupBorderRadius).attr('ry',groupBorderRadius) + .attr("x",-4) + .attr("y",-4) + selectGroup.on("mousedown", function() {groupMouseDown.call(g[0][0],d)}); + selectGroup.on("mouseup", function() {groupMouseUp.call(g[0][0],d)}); + selectGroup.on("touchstart", function() {groupMouseDown.call(g[0][0],d); d3.event.preventDefault();}); + selectGroup.on("touchend", function() {groupMouseUp.call(g[0][0],d); d3.event.preventDefault();}); + + g.append('rect').classed("red-ui-flow-group-outline",true).attr('rx',0.5).attr('ry',0.5); + + g.append('rect').classed("red-ui-flow-group-body",true) + .attr('rx',groupBorderRadius).attr('ry',groupBorderRadius).style({ + "fill":d.fill||"none", + "stroke": d.stroke||"none", + }) + g.on("mousedown",groupMouseDown).on("mouseup",groupMouseUp) + g.on("touchstart", function() {groupMouseDown.call(g[0][0],d); d3.event.preventDefault();}); + g.on("touchend", function() {groupMouseUp.call(g[0][0],d); d3.event.preventDefault();}); + + g.append('svg:text').attr("class","red-ui-flow-group-label"); + d.dirty = true; + }); + if (addedGroups) { + group.sort(function(a,b) { + if (a._root === b._root) { + return a._depth - b._depth; + } else { + return a._index - b._index; + } + }) + } + group[0].reverse(); + var groupOpCount=0; + group.each(function(d,i) { + groupOpCount++ + if (d.resize) { + d.minWidth = 0; + delete d.resize; + } + if (d.dirty || dirtyGroups[d.id]) { + var g = d3.select(this); + var recalculateLabelOffsets = false; + if (d.nodes.length > 0) { + // If the group was just moved, all of its contents was + // also moved - so no need to recalculate its bounding box + if (!d.groupMoved) { + var minX = Number.POSITIVE_INFINITY; + var minY = Number.POSITIVE_INFINITY; + var maxX = 0; + var maxY = 0; + var margin = 26; + d.nodes.forEach(function(n) { + groupOpCount++ + if (n.type !== "group") { + minX = Math.min(minX,n.x-n.w/2-margin-((n._def.button && n._def.align!=="right")?20:0)); + minY = Math.min(minY,n.y-n.h/2-margin); + maxX = Math.max(maxX,n.x+n.w/2+margin+((n._def.button && n._def.align=="right")?20:0)); + maxY = Math.max(maxY,n.y+n.h/2+margin); + } else { + minX = Math.min(minX,n.x-margin) + minY = Math.min(minY,n.y-margin) + maxX = Math.max(maxX,n.x+n.w+margin) + maxY = Math.max(maxY,n.y+n.h+margin) + } + }); + + d.x = minX; + d.y = minY; + d.w = maxX - minX; + d.h = maxY - minY; + recalculateLabelOffsets = true; + // if set explicitly to false, this group has just been + // imported so needed this initial resize calculation. + // Now that's done, delete the flag so the normal + // logic kicks in. + if (d.groupMoved === false) { + delete d.groupMoved; + } + } else { + delete d.groupMoved; + } + } else { + d.w = 40; + d.h = 40; + recalculateLabelOffsets = true; + } + if (recalculateLabelOffsets) { + if (!d.minWidth) { + if (d.style.label && d.name) { + var labelParts = getLabelParts(d.name||"","red-ui-flow-group-label"); + d.minWidth = labelParts.width + 8; + d.labels = labelParts.lines; + } else { + d.minWidth = 40; + d.labels = []; + } + } + d.w = Math.max(d.minWidth,d.w); + if (d.style.label && d.labels.length > 0) { + var labelPos = d.style["label-position"] || "nw"; + var h = (d.labels.length-1) * 16; + if (labelPos[0] === "s") { + h += 8; + } + d.h += h; + if (labelPos[0] === "n") { + if (d.nodes.length > 0) { + d.y -= h; + } + } + } + } + + g.attr("transform","translate("+d.x+","+d.y+")") + g.selectAll(".red-ui-flow-group-outline") + .attr("width",d.w) + .attr("height",d.h) + + + var selectGroup = document.getElementById("group_select_"+d.id); + selectGroup.setAttribute("transform","translate("+d.x+","+d.y+")"); + if (d.hovered) { + selectGroup.classList.add("red-ui-flow-group-hovered") + } else { + selectGroup.classList.remove("red-ui-flow-group-hovered") + } + var selectGroupRect = selectGroup.children[0]; + selectGroupRect.setAttribute("width",d.w+8) + selectGroupRect.setAttribute("height",d.h+8) + selectGroupRect.style.strokeOpacity = (d.active || d.selected || d.highlighted)?0.8:0; + selectGroupRect.style.strokeDasharray = (d.active)?"10 4":""; + selectGroupRect = selectGroup.children[1]; + selectGroupRect.setAttribute("width",d.w+8) + selectGroupRect.setAttribute("height",d.h+8) + selectGroupRect.style.strokeOpacity = (d.active || d.selected || d.highlighted)?0.8:0; + selectGroupRect.style.strokeDasharray = (d.active)?"10 4":""; + + if (d.highlighted) { + selectGroup.classList.add("red-ui-flow-node-highlighted"); + } else { + selectGroup.classList.remove("red-ui-flow-node-highlighted"); + } + + + g.selectAll(".red-ui-flow-group-body") + .attr("width",d.w) + .attr("height",d.h) + .style("stroke", d.style.stroke || "") + .style("stroke-opacity", d.style.hasOwnProperty('stroke-opacity') ? d.style['stroke-opacity'] : "") + .style("fill", d.style.fill || "") + .style("fill-opacity", d.style.hasOwnProperty('fill-opacity') ? d.style['fill-opacity'] : "") + + var label = g.selectAll(".red-ui-flow-group-label"); + label.classed("hide",!!!d.style.label) + if (d.style.label) { + var labelPos = d.style["label-position"] || "nw"; + var labelX = 0; + var labelY = 0; + + if (labelPos[0] === 'n') { + labelY = 0+15; // Allow for font-height + } else { + labelY = d.h - 5 -(d.labels.length -1) * 16; + } + if (labelPos[1] === 'w') { + labelX = 5; + labelAnchor = "start" + } else if (labelPos[1] === 'e') { + labelX = d.w-5; + labelAnchor = "end" + } else { + labelX = d.w/2; + labelAnchor = "middle" + } + if (d.style.hasOwnProperty('color')) { + label.style("fill",d.style.color) + } else { + label.style("fill",null) + } + label.attr("transform","translate("+labelX+","+labelY+")") + .attr("text-anchor",labelAnchor); + if (d.labels) { + var ypos = 0; + g.selectAll(".red-ui-flow-group-label-text").remove(); + d.labels.forEach(function (name) { + label.append("tspan") + .classed("red-ui-flow-group-label-text", true) + .text(name) + .attr("x", 0) + .attr("y", ypos); + ypos += 16; + }); + } else { + g.selectAll(".red-ui-flow-group-label-text").remove(); + } + } + + delete dirtyGroups[d.id]; + delete d.dirty; + } + }) + } else { + // JOINING - unselect any selected links + linkLayer.selectAll(".red-ui-flow-link-selected").data( + activeLinks, + function(d) { + return d.source.id+":"+d.sourcePort+":"+d.target.id+":"+d.target.i; + } + ).classed("red-ui-flow-link-selected", false); + } + RED.view.navigator.refresh(); + if (d3.event) { + d3.event.preventDefault(); + } + } + + function focusView() { + try { + // Workaround for browser unexpectedly scrolling iframe into full + // view - record the parent scroll position and restore it after + // setting the focus + var scrollX = window.parent.window.scrollX; + var scrollY = window.parent.window.scrollY; + chart.trigger("focus"); + window.parent.window.scrollTo(scrollX,scrollY); + } catch(err) { + // In case we're iframed into a page of a different origin, just focus + // the view following the inevitable DOMException + chart.trigger("focus"); + } + } + + + /** + * Imports a new collection of nodes from a JSON String. + * + * - all get new IDs assigned + * - all "selected" + * - attached to mouse for placing - "IMPORT_DRAGGING" + * @param {String/Array} newNodesObj nodes to import + * @param {Object} options options object + * + * Options: + * - addFlow - whether to import nodes to a new tab + * - touchImport - whether this is a touch import. If not, imported nodes are + * attachedto mouse for placing - "IMPORT_DRAGGING" state + * - generateIds - whether to automatically generate new ids for all imported nodes + * - generateDefaultNames - whether to automatically update any nodes with clashing + * default names + */ + function importNodes(newNodesObj,options) { + options = options || { + addFlow: false, + touchImport: false, + generateIds: false, + generateDefaultNames: false + } + var addNewFlow = options.addFlow + var touchImport = options.touchImport; + + if (mouse_mode === RED.state.SELECTING_NODE) { + return; + } + + var nodesToImport; + if (typeof newNodesObj === "string") { + if (newNodesObj === "") { + return; + } + try { + nodesToImport = JSON.parse(newNodesObj); + } catch(err) { + var e = new Error(RED._("clipboard.invalidFlow",{message:err.message})); + e.code = "NODE_RED"; + throw e; + } + } else { + nodesToImport = newNodesObj; + } + + if (!$.isArray(nodesToImport)) { + nodesToImport = [nodesToImport]; + } + if (options.generateDefaultNames) { + RED.actions.invoke("core:generate-node-names", nodesToImport, { + renameBlank: false, + renameClash: true, + generateHistory: false + }) + } + + try { + var activeSubflowChanged; + if (activeSubflow) { + activeSubflowChanged = activeSubflow.changed; + } + var result = RED.nodes.import(nodesToImport,{generateIds:options.generateIds, addFlow: addNewFlow, importMap: options.importMap}); + 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 (addNewFlow && new_default_workspace) { + RED.workspaces.show(new_default_workspace.id); + } + var new_ms = new_nodes.filter(function(n) { return n.hasOwnProperty("x") && n.hasOwnProperty("y") && n.z == RED.workspaces.active() }); + new_ms = new_ms.concat(new_groups.filter(function(g) { return g.z === RED.workspaces.active()})) + new_ms = new_ms.concat(new_junctions.filter(function(j) { return j.z === RED.workspaces.active()})) + var new_node_ids = new_nodes.map(function(n){ n.changed = true; return n.id; }); + + clearSelection(); + movingSet.clear(); + movingSet.add(new_ms); + + + // TODO: pick a more sensible root node + if (movingSet.length() > 0) { + if (mouse_position == null) { + mouse_position = [0,0]; + } + + var dx = mouse_position[0]; + var dy = mouse_position[1]; + if (movingSet.length() > 0) { + var root_node = movingSet.get(0).n; + dx = root_node.x; + dy = root_node.y; + } + + var minX = 0; + var minY = 0; + var i; + var node,group; + var l =movingSet.length(); + for (i=0;i 0) || (!node.n.hasOwnProperty("inputs") && node.n._def.inputs > 0)) && + ((node.n.hasOwnProperty("outputs") && node.n.outputs > 0) || (!node.n.hasOwnProperty("outputs") && node.n._def.outputs > 0)) + + + } + } + + } + + var historyEvent = { + t:"add", + nodes:new_node_ids, + links:new_links, + groups:new_groups, + junctions: new_junctions, + workspaces:new_workspaces, + subflows:new_subflows, + dirty:RED.nodes.dirty() + }; + if (movingSet.length() === 0) { + RED.nodes.dirty(true); + } + if (activeSubflow) { + var subflowRefresh = RED.subflow.refresh(true); + if (subflowRefresh) { + historyEvent.subflow = { + id:activeSubflow.id, + changed: activeSubflowChanged, + instances: subflowRefresh.instances + } + } + } + if (removedNodes) { + var replaceEvent = { + t: "replace", + config: removedNodes + } + historyEvent = { + t:"multi", + events: [ + replaceEvent, + historyEvent + ] + } + } + + RED.history.push(historyEvent); + + updateActiveNodes(); + redraw(); + + var counts = []; + var newNodeCount = 0; + var newConfigNodeCount = 0; + new_nodes.forEach(function(n) { + if (n.hasOwnProperty("x") && n.hasOwnProperty("y")) { + newNodeCount++; + } else { + newConfigNodeCount++; + } + }) + var newGroupCount = new_groups.length; + var newJunctionCount = new_junctions.length; + if (new_workspaces.length > 0) { + counts.push(RED._("clipboard.flow",{count:new_workspaces.length})); + } + if (newNodeCount > 0) { + counts.push(RED._("clipboard.node",{count:newNodeCount})); + } + if (newGroupCount > 0) { + counts.push(RED._("clipboard.group",{count:newGroupCount})); + } + if (newConfigNodeCount > 0) { + counts.push(RED._("clipboard.configNode",{count:newConfigNodeCount})); + } + if (new_subflows.length > 0) { + counts.push(RED._("clipboard.subflow",{count:new_subflows.length})); + } + if (removedNodes && removedNodes.length > 0) { + counts.push(RED._("clipboard.replacedNodes",{count:removedNodes.length})); + } + if (counts.length > 0) { + var countList = "
    • "+counts.join("
    • ")+"
    "; + RED.notify("

    "+RED._("clipboard.nodesImported")+"

    "+countList,{id:"clipboard"}); + } + + } + } catch(error) { + if (error.code === "import_conflict") { + // Pass this up for the called to resolve + throw error; + } else if (error.code != "NODE_RED") { + console.log(error.stack); + RED.notify(RED._("notification.error",{message:error.toString()}),"error"); + } else { + RED.notify(RED._("notification.error",{message:error.message}),"error"); + } + } + } + + function toggleShowGrid(state) { + if (state) { + gridLayer.style("visibility","visible"); + } else { + gridLayer.style("visibility","hidden"); + } + } + function toggleSnapGrid(state) { + snapGrid = state; + redraw(); + } + function toggleStatus(s) { + showStatus = s; + RED.nodes.eachNode(function(n) { n.dirtyStatus = true; n.dirty = true;}); + //TODO: subscribe/unsubscribe here + redraw(); + } + function setSelectedNodeState(isDisabled) { + if (mouse_mode === RED.state.SELECTING_NODE) { + return; + } + var workspaceSelection = RED.workspaces.selection(); + var changed = false; + if (workspaceSelection.length > 0) { + // TODO: toggle workspace state + } else if (movingSet.length() > 0) { + var historyEvents = []; + for (var i=0;i 0) { + RED.history.push({ + t:"multi", + events: historyEvents, + dirty:RED.nodes.dirty() + }) + RED.nodes.dirty(true) + } + } + RED.view.redraw(); + + } + function getSelection() { + var selection = {}; + + var allNodes = new Set(); + + if (movingSet.length() > 0) { + movingSet.forEach(function(n) { + if (n.n.type !== 'group') { + allNodes.add(n.n); + } + }); + } + var selectedGroups = activeGroups.filter(function(g) { return g.selected && !g.active }); + if (selectedGroups.length > 0) { + if (selectedGroups.length === 1 && selectedGroups[0].active) { + // Let nodes be nodes + } else { + selectedGroups.forEach(function(g) { + var groupNodes = RED.group.getNodes(g,true); + groupNodes.forEach(function(n) { + allNodes.delete(n); + }); + allNodes.add(g); + }); + } + } + if (allNodes.size > 0) { + selection.nodes = Array.from(allNodes); + } + if (selectedLinks.length() > 0) { + selection.links = selectedLinks.toArray(); + selection.link = selection.links[0]; + } + return selection; + } + + /** + * Create a node from a type string. + * **NOTE:** Can throw on error - use `try` `catch` block when calling + * @param {string} type The node type to create + * @param {number} [x] (optional) The horizontal position on the workspace + * @param {number} [y] (optional)The vertical on the workspace + * @param {string} [z] (optional) The flow tab this node will belong to. Defaults to active workspace. + * @returns An object containing the `node` and a `historyEvent` + * @private + */ + function createNode(type, x, y, z) { + var m = /^subflow:(.+)$/.exec(type); + var activeSubflow = z ? RED.nodes.subflow(z) : null; + if (activeSubflow && m) { + var subflowId = m[1]; + if (subflowId === activeSubflow.id) { + throw new Error(RED._("notification.error", { message: RED._("notification.errors.cannotAddSubflowToItself") })) + } + if (RED.nodes.subflowContains(m[1], activeSubflow.id)) { + throw new Error(RED._("notification.error", { message: RED._("notification.errors.cannotAddCircularReference") })) + } + } + + var nn = { id: RED.nodes.id(), z: z || RED.workspaces.active() }; + + nn.type = type; + nn._def = RED.nodes.getType(nn.type); + + if (!m) { + nn.inputs = nn._def.inputs || 0; + nn.outputs = nn._def.outputs; + + for (var d in nn._def.defaults) { + if (nn._def.defaults.hasOwnProperty(d)) { + if (nn._def.defaults[d].value !== undefined) { + nn[d] = JSON.parse(JSON.stringify(nn._def.defaults[d].value)); + } + } + } + + if (nn._def.onadd) { + try { + nn._def.onadd.call(nn); + } catch (err) { + console.log("Definition error: " + nn.type + ".onadd:", err); + } + } + } else { + var subflow = RED.nodes.subflow(m[1]); + nn.name = ""; + nn.inputs = subflow.in.length; + nn.outputs = subflow.out.length; + } + + nn.changed = true; + nn.moved = true; + + nn.w = RED.view.node_width; + nn.h = Math.max(RED.view.node_height, (nn.outputs || 0) * 15); + nn.resize = true; + if (x != null && typeof x == "number" && x >= 0) { + nn.x = x; + } + if (y != null && typeof y == "number" && y >= 0) { + nn.y = y; + } + var historyEvent = { + t: "add", + nodes: [nn.id], + dirty: RED.nodes.dirty() + } + if (activeSubflow) { + var subflowRefresh = RED.subflow.refresh(true); + if (subflowRefresh) { + historyEvent.subflow = { + id: activeSubflow.id, + changed: activeSubflow.changed, + instances: subflowRefresh.instances + } + } + } + return { + node: nn, + historyEvent: historyEvent + } + } + + function calculateNodeDimensions(node) { + var result = [node_width,node_height]; + try { + var isLink = (node.type === "link in" || node.type === "link out") + var hideLabel = node.hasOwnProperty('l')?!node.l : isLink; + var label = RED.utils.getNodeLabel(node, node.type); + var labelParts = getLabelParts(label, "red-ui-flow-node-label"); + if (hideLabel) { + result[1] = Math.max(node_height,(node.outputs || 0) * 15); + } else { + result[1] = Math.max(6+24*labelParts.lines.length,(node.outputs || 0) * 15, 30); + } + if (hideLabel) { + result[0] = node_height; + } else { + result[0] = Math.max(node_width,20*(Math.ceil((labelParts.width+50+(node._def.inputs>0?7:0))/20)) ); + } + }catch(err) { + console.log("Error",node); + } + return result; + } + + + function flashNode(n) { + let node = n; + if(typeof node === "string") { node = RED.nodes.node(n); } + if(!node) { return; } + + const flashingNode = flashingNodeId && RED.nodes.node(flashingNodeId); + if(flashingNode) { + //cancel current flashing node before flashing new node + clearInterval(flashingNode.__flashTimer); + delete flashingNode.__flashTimer; + flashingNode.dirty = true; + flashingNode.highlighted = false; + } + node.__flashTimer = setInterval(function(flashEndTime, n) { + n.dirty = true; + if (flashEndTime >= Date.now()) { + n.highlighted = !n.highlighted; + } else { + clearInterval(n.__flashTimer); + delete n.__flashTimer; + flashingNodeId = null; + n.highlighted = false; + } + RED.view.redraw(); + }, 100, Date.now() + 2200, node) + flashingNodeId = node.id; + node.highlighted = true; + RED.view.redraw(); + } + return { + init: init, + state:function(state) { + if (state == null) { + return mouse_mode + } else { + mouse_mode = state; + } + }, + + updateActive: updateActiveNodes, + redraw: function(updateActive, syncRedraw) { + if (updateActive) { + updateActiveNodes(); + updateSelection(); + } + if (syncRedraw) { + _redraw(); + } else { + redraw(); + } + }, + focus: focusView, + importNodes: importNodes, + calculateTextWidth: calculateTextWidth, + select: function(selection) { + if (typeof selection !== "undefined") { + clearSelection(); + if (typeof selection == "string") { + var selectedNode = RED.nodes.node(selection); + if (selectedNode) { + selectedNode.selected = true; + selectedNode.dirty = true; + movingSet.clear(); + movingSet.add(selectedNode); + } + } else if (selection) { + if (selection.nodes) { + updateActiveNodes(); + movingSet.clear(); + // TODO: this selection group span groups + // - if all in one group -> activate the group + // - if in multiple groups (or group/no-group) + // -> select the first 'set' of things in the same group/no-group + selection.nodes.forEach(function(n) { + if (n.type !== "group") { + n.selected = true; + n.dirty = true; + movingSet.add(n); + } else { + selectGroup(n,true); + } + }) + } + } + } + updateSelection(); + redraw(true); + }, + selection: getSelection, + clearSelection: clearSelection, + createNode: createNode, + /** default node width */ + get node_width() { + return node_width; + }, + /** default node height */ + get node_height() { + return node_height; + }, + /** snap to grid option state */ + get snapGrid() { + return snapGrid; + }, + /** gets the current scale factor */ + scale: function() { + return scaleFactor; + }, + getLinksAtPoint: function(x,y) { + // x,y must be in SVG co-ordinate space + // if they come from a node.x/y, they will need to be scaled using + // scaleFactor first. + var result = []; + var links = outer.selectAll(".red-ui-flow-link-background")[0]; + for (var i=0;i= bb.x && y >= bb.y && x <= bb.x+bb.width && y <= bb.y+bb.height) { + result.push(links[i]) + } + } + return result; + }, + getGroupAtPoint: getGroupAt, + getActiveGroup: function() { return activeGroup }, + reveal: function(id,triggerHighlight) { + if (RED.nodes.workspace(id) || RED.nodes.subflow(id)) { + RED.workspaces.show(id, null, null, true); + } else { + var node = RED.nodes.node(id) || RED.nodes.group(id); + if (node) { + if (node.z && (node.type === "group" || node._def.category !== 'config')) { + node.dirty = true; + RED.workspaces.show(node.z); + + var screenSize = [chart[0].clientWidth/scaleFactor,chart[0].clientHeight/scaleFactor]; + var scrollPos = [chart.scrollLeft()/scaleFactor,chart.scrollTop()/scaleFactor]; + var cx = node.x; + var cy = node.y; + if (node.type === "group") { + cx += node.w/2; + cy += node.h/2; + } + if (cx < scrollPos[0] || cy < scrollPos[1] || cx > screenSize[0]+scrollPos[0] || cy > screenSize[1]+scrollPos[1]) { + var deltaX = '-='+(((scrollPos[0] - cx) + screenSize[0]/2)*scaleFactor); + var deltaY = '-='+(((scrollPos[1] - cy) + screenSize[1]/2)*scaleFactor); + chart.animate({ + scrollLeft: deltaX, + scrollTop: deltaY + },200); + } + if (triggerHighlight !== false) { + flashNode(node); + } + } else if (node._def.category === 'config') { + RED.sidebar.config.show(id); + } + } + } + }, + gridSize: function(v) { + if (v === undefined) { + return gridSize; + } else { + gridSize = Math.max(5,v); + updateGrid(); + } + }, + getActiveNodes: function() { + return activeNodes; + }, + getSubflowPorts: function() { + var result = []; + if (activeSubflow) { + var subflowOutputs = nodeLayer.selectAll(".red-ui-flow-subflow-port-output").data(activeSubflow.out,function(d,i){ return d.id;}); + subflowOutputs.each(function(d,i) { result.push(d) }) + var subflowInputs = nodeLayer.selectAll(".red-ui-flow-subflow-port-input").data(activeSubflow.in,function(d,i){ return d.id;}); + subflowInputs.each(function(d,i) { result.push(d) }) + var subflowStatus = nodeLayer.selectAll(".red-ui-flow-subflow-port-status").data(activeSubflow.status?[activeSubflow.status]:[],function(d,i){ return d.id;}); + subflowStatus.each(function(d,i) { result.push(d) }) + } + return result; + }, + selectNodes: function(options) { + $("#red-ui-workspace-tabs-shade").show(); + $("#red-ui-palette-shade").show(); + $("#red-ui-sidebar-shade").show(); + $("#red-ui-header-shade").show(); + $("#red-ui-workspace").addClass("red-ui-workspace-select-mode"); + + mouse_mode = RED.state.SELECTING_NODE; + clearSelection(); + if (options.selected) { + options.selected.forEach(function(id) { + var n = RED.nodes.node(id); + if (n) { + n.selected = true; + n.dirty = true; + movingSet.add(n); + } + }) + } + redraw(); + selectNodesOptions = options||{}; + var closeNotification = function() { + clearSelection(); + $("#red-ui-workspace-tabs-shade").hide(); + $("#red-ui-palette-shade").hide(); + $("#red-ui-sidebar-shade").hide(); + $("#red-ui-header-shade").hide(); + $("#red-ui-workspace").removeClass("red-ui-workspace-select-mode"); + resetMouseVars(); + notification.close(); + } + selectNodesOptions.done = function(selection) { + closeNotification(); + if (selectNodesOptions.onselect) { + selectNodesOptions.onselect(selection); + } + } + var buttons = [{ + text: RED._("common.label.cancel"), + click: function(e) { + closeNotification(); + if (selectNodesOptions.oncancel) { + selectNodesOptions.oncancel(); + } + } + }]; + if (!selectNodesOptions.single) { + buttons.push({ + text: RED._("common.label.done"), + class: "primary", + click: function(e) { + var selection = movingSet.nodes() + selectNodesOptions.done(selection); + } + }); + } + var notification = RED.notify(selectNodesOptions.prompt || RED._("workspace.selectNodes"),{ + modal: false, + fixed: true, + type: "compact", + buttons: buttons + }) + }, + scroll: function(x,y) { + chart.scrollLeft(chart.scrollLeft()+x); + chart.scrollTop(chart.scrollTop()+y) + }, + clickNodeButton: function(n) { + if (n._def.button) { + nodeButtonClicked(n); + } + }, + clipboard: function() { + return clipboard + }, + redrawStatus: redrawStatus, + showQuickAddDialog:showQuickAddDialog, + calculateNodeDimensions: calculateNodeDimensions, + getElementPosition:getElementPosition, + showTooltip:showTooltip + }; +})(); diff --git a/packages/node_modules/@node-red/editor-client/src/sass/header.scss b/packages/node_modules/@node-red/editor-client/src/sass/header.scss index 697a90729..bfe5c283d 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/header.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/header.scss @@ -219,7 +219,7 @@ span.red-ui-menu-sublabel { color: $header-menu-sublabel-color; font-size: 13px; - display: inline-block; + display: block; text-indent: 0px; } } diff --git a/packages/node_modules/@node-red/runtime/lib/api/flows.js b/packages/node_modules/@node-red/runtime/lib/api/flows.js index b3c471a5a..2e71cbdca 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/flows.js +++ b/packages/node_modules/@node-red/runtime/lib/api/flows.js @@ -335,7 +335,7 @@ var api = module.exports = { throw (makeError(err, err.code, 500)) } default: - throw (makeError("Cannot set runtime state. Invalid state", "invalid_run_state", 400)) + throw (makeError(`Cannot change flows runtime state to '${opts.requestedState}'}`, "invalid_run_state", 400)) } }, } From 68c1e49f6289616f770514db27fa2576a23aa777 Mon Sep 17 00:00:00 2001 From: Stephen McLaughlin <44235289+Steve-Mcl@users.noreply.github.com> Date: Mon, 27 Jun 2022 18:12:45 +0100 Subject: [PATCH 045/237] Delete view_copy.js Remove file that slipped through the net --- .../editor-client/src/js/ui/view_copy.js | 6287 ----------------- 1 file changed, 6287 deletions(-) delete mode 100644 packages/node_modules/@node-red/editor-client/src/js/ui/view_copy.js diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view_copy.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view_copy.js deleted file mode 100644 index 569fee1d5..000000000 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view_copy.js +++ /dev/null @@ -1,6287 +0,0 @@ -/** - * Copyright JS Foundation and other contributors, http://js.foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - **/ - - - /*
    #red-ui-workspace-chart - * \- "outer" - * \- - * \- .red-ui-workspace-chart-event-layer "eventLayer" - * |- .red-ui-workspace-chart-background - * |- .red-ui-workspace-chart-grid "gridLayer" - * |- "groupLayer" - * |- "groupSelectLayer" - * |- "linkLayer" - * |- "junctionLayer" - * |- "dragGroupLayer" - * |- "nodeLayer" - */ - -RED.view = (function() { - var space_width = 5000, - space_height = 5000, - lineCurveScale = 0.75, - scaleFactor = 1, - node_width = 100, - node_height = 30, - dblClickInterval = 650; - - var touchLongPressTimeout = 1000, - startTouchDistance = 0, - startTouchCenter = [], - moveTouchCenter = [], - touchStartTime = 0; - - var workspaceScrollPositions = {}; - var entryCoordinates = {x:-1, y:-1}; - var gridSize = 20; - var snapGrid = false; - - var activeSpliceLink; - var spliceActive = false; - var spliceTimer; - var groupHoverTimer; - - var activeSubflow = null; - var activeNodes = []; - var activeLinks = []; - var activeJunctions = []; - var activeFlowLinks = []; - var activeLinkNodes = {}; - var activeGroup = null; - var activeHoverGroup = null; - var activeGroups = []; - var dirtyGroups = {}; - - var mousedown_link = null; - var mousedown_node = null; - var mousedown_group = null; - var mousedown_port_type = null; - var mousedown_port_index = 0; - var mouseup_node = null; - var mouse_offset = [0,0]; - var mouse_position = null; - var mouse_mode = 0; - var mousedown_group_handle = null; - var lasso = null; - var slicePath = null; - var slicePathLast = null; - var ghostNode = null; - var showStatus = false; - var lastClickNode = null; - var dblClickPrimed = null; - var clickTime = 0; - var clickElapsed = 0; - var scroll_position = []; - var quickAddActive = false; - var quickAddLink = null; - var showAllLinkPorts = -1; - var groupNodeSelectPrimed = false; - var lastClickPosition = []; - var selectNodesOptions; - - let flashingNodeId; - - var clipboard = ""; - - // Note: these are the permitted status colour aliases. The actual RGB values - // are set in the CSS - flow.scss/colors.scss - var status_colours = { - "red": "#c00", - "green": "#5a8", - "yellow": "#F9DF31", - "blue": "#53A3F3", - "grey": "#d3d3d3", - "gray": "#d3d3d3" - } - - var PORT_TYPE_INPUT = 1; - var PORT_TYPE_OUTPUT = 0; - - var chart; - var outer; - var eventLayer; - var gridLayer; - var linkLayer; - var junctionLayer; - var dragGroupLayer; - var groupSelectLayer; - var nodeLayer; - var groupLayer; - var drag_lines; - - var movingSet = (function() { - var setIds = new Set(); - var set = []; - var api = { - add: function(node) { - if (Array.isArray(node)) { - for (var i=0;i1) { - clearTimeout(touchStartTime); - touchStartTime = null; - d3.event.preventDefault(); - touch0 = d3.event.touches.item(0); - var touch1 = d3.event.touches.item(1); - var a = touch0["pageY"]-touch1["pageY"]; - var b = touch0["pageX"]-touch1["pageX"]; - - var offset = chart.offset(); - var scrollPos = [chart.scrollLeft(),chart.scrollTop()]; - startTouchCenter = [ - (touch1["pageX"]+(b/2)-offset.left+scrollPos[0])/scaleFactor, - (touch1["pageY"]+(a/2)-offset.top+scrollPos[1])/scaleFactor - ]; - moveTouchCenter = [ - touch1["pageX"]+(b/2), - touch1["pageY"]+(a/2) - ] - startTouchDistance = Math.sqrt((a*a)+(b*b)); - } else { - var obj = d3.select(document.body); - touch0 = d3.event.touches.item(0); - var pos = [touch0.pageX,touch0.pageY]; - startTouchCenter = [touch0.pageX,touch0.pageY]; - startTouchDistance = 0; - var point = d3.touches(this)[0]; - touchStartTime = setTimeout(function() { - touchStartTime = null; - showTouchMenu(obj,pos); - //lasso = eventLayer.append("rect") - // .attr("ox",point[0]) - // .attr("oy",point[1]) - // .attr("rx",2) - // .attr("ry",2) - // .attr("x",point[0]) - // .attr("y",point[1]) - // .attr("width",0) - // .attr("height",0) - // .attr("class","nr-ui-view-lasso"); - },touchLongPressTimeout); - } - d3.event.preventDefault(); - }) - .on("touchmove", function(){ - if (RED.touch.radialMenu.active()) { - d3.event.preventDefault(); - return; - } - if (RED.view.DEBUG) { console.warn("eventLayer.touchmove", mouse_mode, mousedown_node); } - var touch0; - if (d3.event.touches.length<2) { - if (touchStartTime) { - touch0 = d3.event.touches.item(0); - var dx = (touch0.pageX-startTouchCenter[0]); - var dy = (touch0.pageY-startTouchCenter[1]); - var d = Math.abs(dx*dx+dy*dy); - if (d > 64) { - clearTimeout(touchStartTime); - touchStartTime = null; - if (!mousedown_node && !mousedown_group) { - mouse_mode = RED.state.PANNING; - mouse_position = [touch0.pageX,touch0.pageY] - scroll_position = [chart.scrollLeft(),chart.scrollTop()]; - } - - } - } else if (lasso) { - d3.event.preventDefault(); - } - canvasMouseMove.call(this); - } else { - touch0 = d3.event.touches.item(0); - var touch1 = d3.event.touches.item(1); - var a = touch0["pageY"]-touch1["pageY"]; - var b = touch0["pageX"]-touch1["pageX"]; - var offset = chart.offset(); - var scrollPos = [chart.scrollLeft(),chart.scrollTop()]; - var moveTouchDistance = Math.sqrt((a*a)+(b*b)); - var touchCenter = [ - touch1["pageX"]+(b/2), - touch1["pageY"]+(a/2) - ]; - - if (!isNaN(moveTouchDistance)) { - oldScaleFactor = scaleFactor; - scaleFactor = Math.min(2,Math.max(0.3, scaleFactor + (Math.floor(((moveTouchDistance*100)-(startTouchDistance*100)))/10000))); - - var deltaTouchCenter = [ // Try to pan whilst zooming - not 100% - startTouchCenter[0]*(scaleFactor-oldScaleFactor),//-(touchCenter[0]-moveTouchCenter[0]), - startTouchCenter[1]*(scaleFactor-oldScaleFactor) //-(touchCenter[1]-moveTouchCenter[1]) - ]; - - startTouchDistance = moveTouchDistance; - moveTouchCenter = touchCenter; - - chart.scrollLeft(scrollPos[0]+deltaTouchCenter[0]); - chart.scrollTop(scrollPos[1]+deltaTouchCenter[1]); - redraw(); - } - } - d3.event.preventDefault(); - }); - - // Workspace Background - eventLayer.append("svg:rect") - .attr("class","red-ui-workspace-chart-background") - .attr("width", space_width) - .attr("height", space_height); - - gridLayer = eventLayer.append("g").attr("class","red-ui-workspace-chart-grid"); - updateGrid(); - - groupLayer = eventLayer.append("g"); - groupSelectLayer = eventLayer.append("g"); - linkLayer = eventLayer.append("g"); - dragGroupLayer = eventLayer.append("g"); - junctionLayer = eventLayer.append("g"); - nodeLayer = eventLayer.append("g"); - - drag_lines = []; - - RED.events.on("workspace:change",function(event) { - if (event.old !== 0) { - workspaceScrollPositions[event.old] = { - left:chart.scrollLeft(), - top:chart.scrollTop() - }; - } - var scrollStartLeft = chart.scrollLeft(); - var scrollStartTop = chart.scrollTop(); - - activeSubflow = RED.nodes.subflow(event.workspace); - - RED.menu.setDisabled("menu-item-workspace-edit", activeSubflow || event.workspace === 0); - RED.menu.setDisabled("menu-item-workspace-delete",event.workspace === 0 || RED.workspaces.count() == 1 || activeSubflow); - - if (workspaceScrollPositions[event.workspace]) { - chart.scrollLeft(workspaceScrollPositions[event.workspace].left); - chart.scrollTop(workspaceScrollPositions[event.workspace].top); - } else { - chart.scrollLeft(0); - chart.scrollTop(0); - } - var scrollDeltaLeft = chart.scrollLeft() - scrollStartLeft; - var scrollDeltaTop = chart.scrollTop() - scrollStartTop; - if (mouse_position != null) { - mouse_position[0] += scrollDeltaLeft; - mouse_position[1] += scrollDeltaTop; - } - if (RED.workspaces.selection().length === 0) { - resetMouseVars(); - clearSelection(); - } - RED.nodes.eachNode(function(n) { - n.dirty = true; - n.dirtyStatus = true; - }); - updateSelection(); - updateActiveNodes(); - redraw(); - }); - - RED.statusBar.add({ - id: "view-zoom-controls", - align: "right", - element: $(''+ - ''+ - ''+ - ''+ - '') - }) - - $("#red-ui-view-zoom-out").on("click", zoomOut); - RED.popover.tooltip($("#red-ui-view-zoom-out"),RED._('actions.zoom-out'),'core:zoom-out'); - $("#red-ui-view-zoom-zero").on("click", zoomZero); - RED.popover.tooltip($("#red-ui-view-zoom-zero"),RED._('actions.zoom-reset'),'core:zoom-reset'); - $("#red-ui-view-zoom-in").on("click", zoomIn); - RED.popover.tooltip($("#red-ui-view-zoom-in"),RED._('actions.zoom-in'),'core:zoom-in'); - chart.on("DOMMouseScroll mousewheel", function (evt) { - if ( evt.altKey ) { - evt.preventDefault(); - evt.stopPropagation(); - var move = -(evt.originalEvent.detail) || evt.originalEvent.wheelDelta; - if (move <= 0) { zoomOut(); } - else { zoomIn(); } - } - }); - - //add search to status-toolbar - RED.statusBar.add({ - id: "view-search-tools", - align: "left", - hidden: false, - element: $(''+ - '' + - '' + - '' + - '? of ?' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '') - }) - $("#red-ui-view-searchtools-search").on("click", searchFlows); - RED.popover.tooltip($("#red-ui-view-searchtools-search"),RED._('actions.search-flows'),'core:search'); - $("#red-ui-view-searchtools-prev").on("click", searchPrev); - RED.popover.tooltip($("#red-ui-view-searchtools-prev"),RED._('actions.search-prev'),'core:search-previous'); - $("#red-ui-view-searchtools-next").on("click", searchNext); - RED.popover.tooltip($("#red-ui-view-searchtools-next"),RED._('actions.search-next'),'core:search-next'); - RED.popover.tooltip($("#red-ui-view-searchtools-close"),RED._('common.label.close')); - - // Handle nodes dragged from the palette - chart.droppable({ - accept:".red-ui-palette-node", - drop: function( event, ui ) { - d3.event = event; - var selected_tool = $(ui.draggable[0]).attr("data-palette-type"); - var result = createNode(selected_tool); - if (!result) { - return; - } - var historyEvent = result.historyEvent; - var nn = result.node; - - RED.nodes.add(nn); - - var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label"); - if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) { - nn.l = showLabel; - } - - var helperOffset = d3.touches(ui.helper.get(0))[0]||d3.mouse(ui.helper.get(0)); - var helperWidth = ui.helper.width(); - var helperHeight = ui.helper.height(); - var mousePos = d3.touches(this)[0]||d3.mouse(this); - - try { - var isLink = (nn.type === "link in" || nn.type === "link out") - var hideLabel = nn.hasOwnProperty('l')?!nn.l : isLink; - - var label = RED.utils.getNodeLabel(nn, nn.type); - var labelParts = getLabelParts(label, "red-ui-flow-node-label"); - if (hideLabel) { - nn.w = node_height; - nn.h = Math.max(node_height,(nn.outputs || 0) * 15); - } else { - nn.w = Math.max(node_width,20*(Math.ceil((labelParts.width+50+(nn._def.inputs>0?7:0))/20)) ); - nn.h = Math.max(6+24*labelParts.lines.length,(nn.outputs || 0) * 15, 30); - } - } catch(err) { - } - - mousePos[1] += this.scrollTop + ((helperHeight/2)-helperOffset[1]); - mousePos[0] += this.scrollLeft + ((helperWidth/2)-helperOffset[0]); - mousePos[1] /= scaleFactor; - mousePos[0] /= scaleFactor; - - nn.x = mousePos[0]; - nn.y = mousePos[1]; - - if (snapGrid) { - var gridOffset = RED.view.tools.calculateGridSnapOffsets(nn); - nn.x -= gridOffset.x; - nn.y -= gridOffset.y; - } - - var spliceLink = $(ui.helper).data("splice"); - if (spliceLink) { - // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp/showQuickAddDialog - RED.nodes.removeLink(spliceLink); - var link1 = { - source:spliceLink.source, - sourcePort:spliceLink.sourcePort, - target: nn - }; - var link2 = { - source:nn, - sourcePort:0, - target: spliceLink.target - }; - RED.nodes.addLink(link1); - RED.nodes.addLink(link2); - historyEvent.links = [link1,link2]; - historyEvent.removedLinks = [spliceLink]; - } - - - var group = $(ui.helper).data("group"); - if (group) { - RED.group.addToGroup(group, nn); - historyEvent = { - t: 'multi', - events: [historyEvent], - - } - historyEvent.events.push({ - t: "addToGroup", - group: group, - nodes: nn - }) - } - - RED.history.push(historyEvent); - RED.editor.validateNode(nn); - RED.nodes.dirty(true); - // auto select dropped node - so info shows (if visible) - exitActiveGroup(); - clearSelection(); - nn.selected = true; - movingSet.add(nn); - if (group) { - selectGroup(group,false); - enterActiveGroup(group); - activeGroup = group; - } - updateActiveNodes(); - updateSelection(); - redraw(); - - if (nn._def.autoedit) { - RED.editor.edit(nn); - } - } - }); - chart.on("focus", function() { - $("#red-ui-workspace-tabs").addClass("red-ui-workspace-focussed"); - }); - chart.on("blur", function() { - $("#red-ui-workspace-tabs").removeClass("red-ui-workspace-focussed"); - }); - - RED.actions.add("core:copy-selection-to-internal-clipboard",copySelection); - RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection();deleteSelection();}); - RED.actions.add("core:paste-from-internal-clipboard",function(){importNodes(clipboard,{generateIds: true, generateDefaultNames: true});}); - - RED.actions.add("core:detach-selected-nodes", function() { detachSelectedNodes() }) - - RED.events.on("view:selection-changed", function(selection) { - var hasSelection = (selection.nodes && selection.nodes.length > 0); - var hasMultipleSelection = hasSelection && selection.nodes.length > 1; - RED.menu.setDisabled("menu-item-edit-cut",!hasSelection); - RED.menu.setDisabled("menu-item-edit-copy",!hasSelection); - RED.menu.setDisabled("menu-item-edit-select-connected",!hasSelection); - RED.menu.setDisabled("menu-item-view-tools-move-to-back",!hasSelection); - RED.menu.setDisabled("menu-item-view-tools-move-to-front",!hasSelection); - RED.menu.setDisabled("menu-item-view-tools-move-backwards",!hasSelection); - RED.menu.setDisabled("menu-item-view-tools-move-forwards",!hasSelection); - - RED.menu.setDisabled("menu-item-view-tools-align-left",!hasMultipleSelection); - RED.menu.setDisabled("menu-item-view-tools-align-center",!hasMultipleSelection); - RED.menu.setDisabled("menu-item-view-tools-align-right",!hasMultipleSelection); - RED.menu.setDisabled("menu-item-view-tools-align-top",!hasMultipleSelection); - RED.menu.setDisabled("menu-item-view-tools-align-middle",!hasMultipleSelection); - RED.menu.setDisabled("menu-item-view-tools-align-bottom",!hasMultipleSelection); - RED.menu.setDisabled("menu-item-view-tools-distribute-horizontally",!hasMultipleSelection); - RED.menu.setDisabled("menu-item-view-tools-distribute-veritcally",!hasMultipleSelection); - }) - - RED.actions.add("core:delete-selection",deleteSelection); - RED.actions.add("core:delete-selection-and-reconnect",function() { deleteSelection(true) }); - RED.actions.add("core:edit-selected-node",editSelection); - RED.actions.add("core:go-to-selection",function() { - if (movingSet.length() > 0) { - var node = movingSet.get(0).n; - if (/^subflow:/.test(node.type)) { - RED.workspaces.show(node.type.substring(8)) - } else if (node.type === 'group') { - enterActiveGroup(node); - redraw(); - } - } - }); - RED.actions.add("core:undo",RED.history.pop); - RED.actions.add("core:redo",RED.history.redo); - RED.actions.add("core:select-all-nodes",selectAll); - RED.actions.add("core:select-none", selectNone); - RED.actions.add("core:zoom-in",zoomIn); - RED.actions.add("core:zoom-out",zoomOut); - RED.actions.add("core:zoom-reset",zoomZero); - RED.actions.add("core:enable-selected-nodes", function() { setSelectedNodeState(false)}); - RED.actions.add("core:disable-selected-nodes", function() { setSelectedNodeState(true)}); - - RED.actions.add("core:toggle-show-grid",function(state) { - if (state === undefined) { - RED.userSettings.toggle("view-show-grid"); - } else { - toggleShowGrid(state); - } - }); - RED.actions.add("core:toggle-snap-grid",function(state) { - if (state === undefined) { - RED.userSettings.toggle("view-snap-grid"); - } else { - toggleSnapGrid(state); - } - }); - RED.actions.add("core:toggle-status",function(state) { - if (state === undefined) { - RED.userSettings.toggle("view-node-status"); - } else { - toggleStatus(state); - } - }); - - RED.view.annotations.init(); - RED.view.navigator.init(); - RED.view.tools.init(); - - - RED.view.annotations.register("red-ui-flow-node-changed",{ - type: "badge", - class: "red-ui-flow-node-changed", - element: function() { - var changeBadge = document.createElementNS("http://www.w3.org/2000/svg","circle"); - changeBadge.setAttribute("cx",5); - changeBadge.setAttribute("cy",5); - changeBadge.setAttribute("r",5); - return changeBadge; - }, - show: function(n) { return n.changed||n.moved } - }) - - RED.view.annotations.register("red-ui-flow-node-error",{ - type: "badge", - class: "red-ui-flow-node-error", - element: function(d) { - var errorBadge = document.createElementNS("http://www.w3.org/2000/svg","path"); - errorBadge.setAttribute("d","M 0,9 l 10,0 -5,-8 z"); - return errorBadge - }, - tooltip: function(d) { - if (d.validationErrors && d.validationErrors.length > 0) { - return RED._("editor.errors.invalidProperties")+"\n - "+d.validationErrors.join("\n - ") - } - }, - show: function(n) { return !n.valid } - }) - - if (RED.settings.get("editor.view.view-store-zoom")) { - var userZoomLevel = parseFloat(RED.settings.getLocal('zoom-level')) - if (!isNaN(userZoomLevel)) { - scaleFactor = userZoomLevel - } - } - - var onScrollTimer = null; - function storeScrollPosition() { - workspaceScrollPositions[RED.workspaces.active()] = { - left:chart.scrollLeft(), - top:chart.scrollTop() - }; - RED.settings.setLocal('scroll-positions', JSON.stringify(workspaceScrollPositions) ) - } - chart.on("scroll", function() { - if (RED.settings.get("editor.view.view-store-position")) { - if (onScrollTimer) { - clearTimeout(onScrollTimer) - } - onScrollTimer = setTimeout(storeScrollPosition, 200); - } - }) - - if (RED.settings.get("editor.view.view-store-position")) { - var scrollPositions = RED.settings.getLocal('scroll-positions') - if (scrollPositions) { - try { - workspaceScrollPositions = JSON.parse(scrollPositions) - } catch(err) { - } - } - } - } - - - - function updateGrid() { - var gridTicks = []; - for (var i=0;i 0) { - if (delta < node_width) { - scale = 0.75-0.75*((node_width-delta)/node_width); - // scale += 2*(Math.min(5*node_width,Math.abs(dx))/(5*node_width)); - // if (Math.abs(dy) < 3*node_height) { - // scaleY = ((dy>0)?0.5:-0.5)*(((3*node_height)-Math.abs(dy))/(3*node_height))*(Math.min(node_width,Math.abs(dx))/(node_width)) ; - // } - } - } else { - scale = 0.4-0.2*(Math.max(0,(node_width-Math.min(Math.abs(dx),Math.abs(dy)))/node_width)); - } - if (dx*sc > 0) { - return "M "+origX+" "+origY+ - " C "+(origX+sc*(node_width*scale))+" "+(origY+scaleY*node_height)+" "+ - (destX-sc*(scale)*node_width)+" "+(destY-scaleY*node_height)+" "+ - destX+" "+destY - } else { - - var midX = Math.floor(destX-dx/2); - var midY = Math.floor(destY-dy/2); - // - if (dy === 0) { - midY = destY + node_height; - } - var cp_height = node_height/2; - var y1 = (destY + midY)/2 - var topX =origX + sc*node_width*scale; - var topY = dy>0?Math.min(y1 - dy/2 , origY+cp_height):Math.max(y1 - dy/2 , origY-cp_height); - var bottomX = destX - sc*node_width*scale; - var bottomY = dy>0?Math.max(y1, destY-cp_height):Math.min(y1, destY+cp_height); - var x1 = (origX+topX)/2; - var scy = dy>0?1:-1; - var cp = [ - // Orig -> Top - [x1,origY], - [topX,dy>0?Math.max(origY, topY-cp_height):Math.min(origY, topY+cp_height)], - // Top -> Mid - // [Mirror previous cp] - [x1,dy>0?Math.min(midY, topY+cp_height):Math.max(midY, topY-cp_height)], - // Mid -> Bottom - // [Mirror previous cp] - [bottomX,dy>0?Math.max(midY, bottomY-cp_height):Math.min(midY, bottomY+cp_height)], - // Bottom -> Dest - // [Mirror previous cp] - [(destX+bottomX)/2,destY] - ]; - if (cp[2][1] === topY+scy*cp_height) { - if (Math.abs(dy) < cp_height*10) { - cp[1][1] = topY-scy*cp_height/2; - cp[3][1] = bottomY-scy*cp_height/2; - } - cp[2][0] = topX; - } - return "M "+origX+" "+origY+ - " C "+ - cp[0][0]+" "+cp[0][1]+" "+ - cp[1][0]+" "+cp[1][1]+" "+ - topX+" "+topY+ - " S "+ - cp[2][0]+" "+cp[2][1]+" "+ - midX+" "+midY+ - " S "+ - cp[3][0]+" "+cp[3][1]+" "+ - bottomX+" "+bottomY+ - " S "+ - cp[4][0]+" "+cp[4][1]+" "+ - destX+" "+destY - } - } - - function canvasMouseDown() { - if (RED.view.DEBUG) { - console.warn("canvasMouseDown", { mouse_mode, point: d3.mouse(this), event: d3.event }); - } - if (mouse_mode === RED.state.SELECTING_NODE) { - d3.event.stopPropagation(); - return; - } - - if (d3.event.button === 1) { - // Middle Click pan - mouse_mode = RED.state.PANNING; - mouse_position = [d3.event.pageX,d3.event.pageY] - scroll_position = [chart.scrollLeft(),chart.scrollTop()]; - return; - } - if (!mousedown_node && !mousedown_link && !mousedown_group) { - selectedLinks.clear(); - updateSelection(); - } - if (mouse_mode === 0 && lasso) { - lasso.remove(); - lasso = null; - } - if (d3.event.touches || d3.event.button === 0) { - if ((mouse_mode === 0 || mouse_mode === RED.state.QUICK_JOINING) && (d3.event.metaKey || d3.event.ctrlKey) && !(d3.event.altKey || d3.event.shiftKey)) { - // Trigger quick add dialog - d3.event.stopPropagation(); - clearSelection(); - const point = d3.mouse(this); - var clickedGroup = getGroupAt(point[0], point[1]); - if (drag_lines.length > 0) { - clickedGroup = clickedGroup || RED.nodes.group(drag_lines[0].node.g) - } - showQuickAddDialog({ position: point, group: clickedGroup }); - } else if (mouse_mode === 0 && !(d3.event.metaKey || d3.event.ctrlKey)) { - // CTRL not being held - if (!d3.event.altKey) { - // ALT not held (shift is allowed) Trigger lasso - if (!touchStartTime) { - const point = d3.mouse(this); - lasso = eventLayer.append("rect") - .attr("ox", point[0]) - .attr("oy", point[1]) - .attr("rx", 1) - .attr("ry", 1) - .attr("x", point[0]) - .attr("y", point[1]) - .attr("width", 0) - .attr("height", 0) - .attr("class", "nr-ui-view-lasso"); - d3.event.preventDefault(); - } - } else if (d3.event.altKey) { - //Alt [+shift] held - Begin slicing - clearSelection(); - mouse_mode = (d3.event.shiftKey) ? RED.state.SLICING_JUNCTION : RED.state.SLICING; - const point = d3.mouse(this); - slicePath = eventLayer.append("path").attr("class", "nr-ui-view-slice").attr("d", `M${point[0]} ${point[1]}`) - slicePathLast = point; - RED.view.redraw(); - } - } - } - } - - function showQuickAddDialog(options) { - options = options || {}; - var point = options.position || lastClickPosition; - var spliceLink = options.splice; - var targetGroup = options.group; - var touchTrigger = options.touchTrigger; - - if (targetGroup && !targetGroup.active) { - selectGroup(targetGroup,false); - enterActiveGroup(targetGroup); - RED.view.redraw(); - } - - var ox = point[0]; - var oy = point[1]; - - if (RED.settings.get("editor").view['view-snap-grid']) { - // eventLayer.append("circle").attr("cx",point[0]).attr("cy",point[1]).attr("r","2").attr('fill','red') - point[0] = Math.round(point[0] / gridSize) * gridSize; - point[1] = Math.round(point[1] / gridSize) * gridSize; - // eventLayer.append("circle").attr("cx",point[0]).attr("cy",point[1]).attr("r","2").attr('fill','blue') - } - - var mainPos = $("#red-ui-main-container").position(); - - if (mouse_mode !== RED.state.QUICK_JOINING) { - mouse_mode = RED.state.QUICK_JOINING; - $(window).on('keyup',disableQuickJoinEventHandler); - } - quickAddActive = true; - - if (ghostNode) { - ghostNode.remove(); - } - ghostNode = eventLayer.append("g").attr('transform','translate('+(point[0] - node_width/2)+','+(point[1] - node_height/2)+')'); - ghostNode.append("rect") - .attr("class","red-ui-flow-node-placeholder") - .attr("rx", 5) - .attr("ry", 5) - .attr("width",node_width) - .attr("height",node_height) - .attr("fill","none") - // var ghostLink = ghostNode.append("svg:path") - // .attr("class","red-ui-flow-link-link") - // .attr("d","M 0 "+(node_height/2)+" H "+(gridSize * -2)) - // .attr("opacity",0); - - var filter; - if (drag_lines.length > 0) { - if (drag_lines[0].virtualLink) { - filter = {type:drag_lines[0].node.type === 'link in'?'link out':'link in'} - } else if (drag_lines[0].portType === PORT_TYPE_OUTPUT) { - filter = {input:true} - } else { - filter = {output:true} - } - - quickAddLink = { - node: drag_lines[0].node, - port: drag_lines[0].port, - portType: drag_lines[0].portType, - } - if (drag_lines[0].virtualLink) { - quickAddLink.virtualLink = true; - } - hideDragLines(); - } - if (spliceLink) { - filter = {input:true, output:true} - } - - var rebuildQuickAddLink = function() { - if (!quickAddLink) { - return; - } - if (!quickAddLink.el) { - quickAddLink.el = dragGroupLayer.append("svg:path").attr("class", "red-ui-flow-drag-line"); - } - var numOutputs = (quickAddLink.portType === PORT_TYPE_OUTPUT)?(quickAddLink.node.outputs || 1):1; - var sourcePort = quickAddLink.port; - var portY = -((numOutputs-1)/2)*13 +13*sourcePort; - var sc = (quickAddLink.portType === PORT_TYPE_OUTPUT)?1:-1; - quickAddLink.el.attr("d",generateLinkPath(quickAddLink.node.x+sc*quickAddLink.node.w/2,quickAddLink.node.y+portY,point[0]-sc*node_width/2,point[1],sc)); - } - if (quickAddLink) { - rebuildQuickAddLink(); - } - - - var lastAddedX; - var lastAddedWidth; - - RED.typeSearch.show({ - x:d3.event.clientX-mainPos.left-node_width/2 - (ox-point[0]), - y:d3.event.clientY-mainPos.top+ node_height/2 + 5 - (oy-point[1]), - disableFocus: touchTrigger, - filter: filter, - move: function(dx,dy) { - if (ghostNode) { - var pos = d3.transform(ghostNode.attr("transform")).translate; - ghostNode.attr("transform","translate("+(pos[0]+dx)+","+(pos[1]+dy)+")") - point[0] += dx; - point[1] += dy; - rebuildQuickAddLink(); - } - }, - cancel: function() { - if (quickAddLink) { - if (quickAddLink.el) { - quickAddLink.el.remove(); - } - quickAddLink = null; - } - quickAddActive = false; - if (ghostNode) { - ghostNode.remove(); - } - resetMouseVars(); - updateSelection(); - hideDragLines(); - redraw(); - }, - add: function(type,keepAdding) { - if (touchTrigger) { - keepAdding = false; - resetMouseVars(); - } - - var nn; - var historyEvent; - if (type === 'junction') { - nn = { - _def: {defaults:{}}, - type: 'junction', - z: RED.workspaces.active(), - id: RED.nodes.id(), - x: 0, - y: 0, - w: 0, h: 0, - outputs: 1, - inputs: 1, - dirty: true - } - historyEvent = { - t:'add', - junctions:[nn] - } - } else { - var result = createNode(type); - if (!result) { - return; - } - nn = result.node; - historyEvent = result.historyEvent; - } - if (keepAdding) { - mouse_mode = RED.state.QUICK_JOINING; - } - - nn.x = point[0]; - nn.y = point[1]; - var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label"); - if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) { - nn.l = showLabel; - } - if (quickAddLink) { - var drag_line = quickAddLink; - var src = null,dst,src_port; - if (drag_line.portType === PORT_TYPE_OUTPUT && (nn.inputs > 0 || drag_line.virtualLink) ) { - src = drag_line.node; - src_port = drag_line.port; - dst = nn; - } else if (drag_line.portType === PORT_TYPE_INPUT && (nn.outputs > 0 || drag_line.virtualLink)) { - src = nn; - dst = drag_line.node; - src_port = 0; - } - - if (src !== null) { - // Joining link nodes via virual wires. Need to update - // the src and dst links property - if (drag_line.virtualLink) { - historyEvent = { - t:'multi', - events: [historyEvent] - } - var oldSrcLinks = $.extend(true,{},{v:src.links}).v - var oldDstLinks = $.extend(true,{},{v:dst.links}).v - src.links.push(dst.id); - dst.links.push(src.id); - src.dirty = true; - dst.dirty = true; - - historyEvent.events.push({ - t:'edit', - node: src, - dirty: RED.nodes.dirty(), - changed: src.changed, - changes: { - links:oldSrcLinks - } - }); - historyEvent.events.push({ - t:'edit', - node: dst, - dirty: RED.nodes.dirty(), - changed: dst.changed, - changes: { - links:oldDstLinks - } - }); - src.changed = true; - dst.changed = true; - } else { - var link = {source: src, sourcePort:src_port, target: dst}; - RED.nodes.addLink(link); - historyEvent.links = [link]; - } - if (!keepAdding) { - quickAddLink.el.remove(); - quickAddLink = null; - if (mouse_mode === RED.state.QUICK_JOINING) { - if (drag_line.portType === PORT_TYPE_OUTPUT && nn.outputs > 0) { - showDragLines([{node:nn,port:0,portType:PORT_TYPE_OUTPUT}]); - } else if (!quickAddLink && drag_line.portType === PORT_TYPE_INPUT && nn.inputs > 0) { - showDragLines([{node:nn,port:0,portType:PORT_TYPE_INPUT}]); - } else { - resetMouseVars(); - } - } - } else { - quickAddLink.node = nn; - quickAddLink.port = 0; - } - } else { - hideDragLines(); - resetMouseVars(); - } - } else { - if (!keepAdding) { - if (mouse_mode === RED.state.QUICK_JOINING) { - if (nn.outputs > 0) { - showDragLines([{node:nn,port:0,portType:PORT_TYPE_OUTPUT}]); - } else if (nn.inputs > 0) { - showDragLines([{node:nn,port:0,portType:PORT_TYPE_INPUT}]); - } else { - resetMouseVars(); - } - } - } else { - if (nn.outputs > 0) { - quickAddLink = { - node: nn, - port: 0, - portType: PORT_TYPE_OUTPUT - } - } else if (nn.inputs > 0) { - quickAddLink = { - node: nn, - port: 0, - portType: PORT_TYPE_INPUT - } - } else { - resetMouseVars(); - } - } - } - if (nn.type === 'junction') { - RED.nodes.addJunction(nn); - } else { - RED.nodes.add(nn); - } - RED.editor.validateNode(nn); - - if (targetGroup) { - RED.group.addToGroup(targetGroup, nn); - if (historyEvent.t !== "multi") { - historyEvent = { - t:'multi', - events: [historyEvent] - } - } - historyEvent.events.push({ - t: "addToGroup", - group: targetGroup, - nodes: nn - }) - - } - - if (spliceLink) { - resetMouseVars(); - // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp/showQuickAddDialog - RED.nodes.removeLink(spliceLink); - var link1 = { - source:spliceLink.source, - sourcePort:spliceLink.sourcePort, - target: nn - }; - var link2 = { - source:nn, - sourcePort:0, - target: spliceLink.target - }; - RED.nodes.addLink(link1); - RED.nodes.addLink(link2); - historyEvent.links = (historyEvent.links || []).concat([link1,link2]); - historyEvent.removedLinks = [spliceLink]; - } - RED.history.push(historyEvent); - RED.nodes.dirty(true); - // auto select dropped node - so info shows (if visible) - clearSelection(); - nn.selected = true; - if (targetGroup) { - selectGroup(targetGroup,false); - enterActiveGroup(targetGroup); - } - movingSet.add(nn); - updateActiveNodes(); - updateSelection(); - redraw(); - // At this point the newly added node will have a real width, - // so check if the position needs nudging - if (lastAddedX !== undefined) { - var lastNodeRHEdge = lastAddedX + lastAddedWidth/2; - var thisNodeLHEdge = nn.x - nn.w/2; - var gap = thisNodeLHEdge - lastNodeRHEdge; - if (gap != gridSize *2) { - nn.x = nn.x + gridSize * 2 - gap; - nn.dirty = true; - nn.x = Math.ceil(nn.x / gridSize) * gridSize; - redraw(); - } - } - if (keepAdding) { - if (lastAddedX === undefined) { - // ghostLink.attr("opacity",1); - setTimeout(function() { - RED.typeSearch.refresh({filter:{input:true}}); - },100); - } - - lastAddedX = nn.x; - lastAddedWidth = nn.w; - - point[0] = nn.x + nn.w/2 + node_width/2 + gridSize * 2; - ghostNode.attr('transform','translate('+(point[0] - node_width/2)+','+(point[1] - node_height/2)+')'); - rebuildQuickAddLink(); - } else { - quickAddActive = false; - ghostNode.remove(); - } - } - }); - - updateActiveNodes(); - updateSelection(); - redraw(); - } - - function canvasMouseMove() { - var i; - var node; - // Prevent touch scrolling... - //if (d3.touches(this)[0]) { - // d3.event.preventDefault(); - //} - - // TODO: auto scroll the container - //var point = d3.mouse(this); - //if (point[0]-container.scrollLeft < 30 && container.scrollLeft > 0) { container.scrollLeft -= 15; } - //console.log(d3.mouse(this),container.offsetWidth,container.offsetHeight,container.scrollLeft,container.scrollTop); - - if (mouse_mode === RED.state.PANNING) { - var pos = [d3.event.pageX,d3.event.pageY]; - if (d3.event.touches) { - var touch0 = d3.event.touches.item(0); - pos = [touch0.pageX, touch0.pageY]; - } - var deltaPos = [ - mouse_position[0]-pos[0], - mouse_position[1]-pos[1] - ]; - - chart.scrollLeft(scroll_position[0]+deltaPos[0]) - chart.scrollTop(scroll_position[1]+deltaPos[1]) - return - } - if (entryCoordinates.x != -1) { - mouse_position = [entryCoordinates.x, entryCoordinates.y] - } else { - mouse_position = d3.touches(this)[0]||d3.mouse(this); - } -if(RED.view.DEBUG) { console.log(`mousemove ${JSON.stringify(mouse_position)}`)} - if (lasso) { - var ox = parseInt(lasso.attr("ox")); - var oy = parseInt(lasso.attr("oy")); - var x = parseInt(lasso.attr("x")); - var y = parseInt(lasso.attr("y")); - var w; - var h; - if (mouse_position[0] < ox) { - x = mouse_position[0]; - w = ox-x; - } else { - w = mouse_position[0]-x; - } - if (mouse_position[1] < oy) { - y = mouse_position[1]; - h = oy-y; - } else { - h = mouse_position[1]-y; - } - lasso - .attr("x",x) - .attr("y",y) - .attr("width",w) - .attr("height",h) - ; - return; - } else if (mouse_mode === RED.state.SLICING || mouse_mode === RED.state.SLICING_JUNCTION) { - if (slicePath) { - var delta = Math.max(1,Math.abs(slicePathLast[0]-mouse_position[0]))*Math.max(1,Math.abs(slicePathLast[1]-mouse_position[1])) - if (delta > 20) { - var currentPath = slicePath.attr("d") - currentPath += " L"+mouse_position[0]+" "+mouse_position[1] - slicePath.attr("d",currentPath); - slicePathLast = mouse_position - } - } - return - } - - if (mouse_mode === RED.state.SELECTING_NODE) { - d3.event.stopPropagation(); - return; - } - - if (mouse_mode != RED.state.QUICK_JOINING && mouse_mode != RED.state.IMPORT_DRAGGING && mouse_mode != RED.state.DETACHED_DRAGGING && !mousedown_node && !mousedown_group && selectedLinks.length() === 0) { - return; - } - - var mousePos; - // if (mouse_mode === RED.state.GROUP_RESIZE) { - // mousePos = mouse_position; - // var nx = mousePos[0] + mousedown_group.dx; - // var ny = mousePos[1] + mousedown_group.dy; - // switch(mousedown_group.activeHandle) { - // case 0: mousedown_group.pos.x0 = nx; mousedown_group.pos.y0 = ny; break; - // case 1: mousedown_group.pos.x1 = nx; mousedown_group.pos.y0 = ny; break; - // case 2: mousedown_group.pos.x1 = nx; mousedown_group.pos.y1 = ny; break; - // case 3: mousedown_group.pos.x0 = nx; mousedown_group.pos.y1 = ny; break; - // } - // mousedown_group.dirty = true; - // } - if (mouse_mode == RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { - // update drag line - if (drag_lines.length === 0 && mousedown_port_type !== null) { - if (d3.event.shiftKey) { - // Get all the wires we need to detach. - var links = []; - var existingLinks = []; - if (selectedLinks.length() > 0) { - selectedLinks.forEach(function(link) { - if (((mousedown_port_type === PORT_TYPE_OUTPUT && - link.source === mousedown_node && - link.sourcePort === mousedown_port_index - ) || - (mousedown_port_type === PORT_TYPE_INPUT && - link.target === mousedown_node - ))) { - existingLinks.push(link); - } - }) - } else { - var filter; - if (mousedown_port_type === PORT_TYPE_OUTPUT) { - filter = { - source:mousedown_node, - sourcePort: mousedown_port_index - } - } else { - filter = { - target: mousedown_node - } - } - existingLinks = RED.nodes.filterLinks(filter); - } - for (i=0;i 3 && !dblClickPrimed) || (dblClickPrimed && d > 10)) { - mouse_mode = RED.state.MOVING_ACTIVE; - clickElapsed = 0; - spliceActive = false; - if (movingSet.length() === 1) { - node = movingSet.get(0); - spliceActive = node.n.hasOwnProperty("_def") && - ((node.n.hasOwnProperty("inputs") && node.n.inputs > 0) || (!node.n.hasOwnProperty("inputs") && node.n._def.inputs > 0)) && - ((node.n.hasOwnProperty("outputs") && node.n.outputs > 0) || (!node.n.hasOwnProperty("outputs") && node.n._def.outputs > 0)) && - RED.nodes.filterLinks({ source: node.n }).length === 0 && - RED.nodes.filterLinks({ target: node.n }).length === 0; - } - } - } else if (mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.IMPORT_DRAGGING || mouse_mode == RED.state.DETACHED_DRAGGING) { - mousePos = mouse_position; - var minX = 0; - var minY = 0; - var maxX = space_width; - var maxY = space_height; - if(RED.view.DEBUG) { console.log(`mousemove - MOVING_ACTIVE ${JSON.stringify(mousePos)}`)} - for (var n = 0; n 0) { - var i = 0; - - // Prefer to snap nodes to the grid if there is one in the selection - do { - node = movingSet.get(i++); - } while(i 0) { - historyEvent = { - t:"delete", - links: removedLinks, - dirty:RED.nodes.dirty() - }; - RED.history.push(historyEvent); - RED.nodes.dirty(true); - } - hideDragLines(); - } - if (lasso) { - var x = parseInt(lasso.attr("x")); - var y = parseInt(lasso.attr("y")); - var x2 = x+parseInt(lasso.attr("width")); - var y2 = y+parseInt(lasso.attr("height")); - var ag = activeGroup; - if (!d3.event.shiftKey) { - clearSelection(); - if (ag) { - if (x < ag.x+ag.w && x2 > ag.x && y < ag.y+ag.h && y2 > ag.y) { - // There was an active group and the lasso intersects with it, - // so reenter the group - enterActiveGroup(ag); - activeGroup.selected = true; - } - } - } - activeGroups.forEach(function(g) { - if (!g.selected) { - if (g.x > x && g.x+g.w < x2 && g.y > y && g.y+g.h < y2) { - if (!activeGroup || RED.group.contains(activeGroup,g)) { - while (g.g && (!activeGroup || g.g !== activeGroup.id)) { - g = RED.nodes.group(g.g); - } - if (!g.selected) { - selectGroup(g,true); - } - } - } - } - }) - - activeNodes.forEach(function(n) { - if (!n.selected) { - if (n.x > x && n.x < x2 && n.y > y && n.y < y2) { - if (!activeGroup || RED.group.contains(activeGroup,n)) { - if (n.g && (!activeGroup || n.g !== activeGroup.id)) { - var group = RED.nodes.group(n.g); - while (group.g && (!activeGroup || group.g !== activeGroup.id)) { - group = RED.nodes.group(group.g); - } - if (!group.selected) { - selectGroup(group,true); - } - } else { - n.selected = true; - n.dirty = true; - movingSet.add(n); - } - } - } - } - }); - activeJunctions.forEach(function(n) { - if (!n.selected) { - if (n.x > x && n.x < x2 && n.y > y && n.y < y2) { - n.selected = true; - n.dirty = true; - movingSet.add(n); - } - } - }) - - - - // var selectionChanged = false; - // do { - // selectionChanged = false; - // selectedGroups.forEach(function(g) { - // if (g.g && g.selected && RED.nodes.group(g.g).selected) { - // g.selected = false; - // selectionChanged = true; - // } - // }) - // } while(selectionChanged); - - if (activeSubflow) { - activeSubflow.in.forEach(function(n) { - n.selected = (n.x > x && n.x < x2 && n.y > y && n.y < y2); - if (n.selected) { - n.dirty = true; - movingSet.add(n); - } - }); - activeSubflow.out.forEach(function(n) { - n.selected = (n.x > x && n.x < x2 && n.y > y && n.y < y2); - if (n.selected) { - n.dirty = true; - movingSet.add(n); - } - }); - if (activeSubflow.status) { - activeSubflow.status.selected = (activeSubflow.status.x > x && activeSubflow.status.x < x2 && activeSubflow.status.y > y && activeSubflow.status.y < y2); - if (activeSubflow.status.selected) { - activeSubflow.status.dirty = true; - movingSet.add(activeSubflow.status); - } - } - } - updateSelection(); - lasso.remove(); - lasso = null; - } else if (mouse_mode == RED.state.DEFAULT && mousedown_link == null && !d3.event.ctrlKey && !d3.event.metaKey ) { - clearSelection(); - updateSelection(); - } else if (mouse_mode == RED.state.SLICING) { - deleteSelection(); - slicePath.remove(); - slicePath = null; - RED.view.redraw(true); - } else if (mouse_mode == RED.state.SLICING_JUNCTION) { - var removedLinks = new Set() - var addedLinks = [] - var addedJunctions = [] - - var groupedLinks = {} - selectedLinks.forEach(function(l) { - var sourceId = l.source.id+":"+l.sourcePort - groupedLinks[sourceId] = groupedLinks[sourceId] || [] - groupedLinks[sourceId].push(l) - - groupedLinks[l.target.id] = groupedLinks[l.target.id] || [] - groupedLinks[l.target.id].push(l) - }); - var linkGroups = Object.keys(groupedLinks) - linkGroups.sort(function(A,B) { - return groupedLinks[B].length - groupedLinks[A].length - }) - linkGroups.forEach(function(gid) { - var links = groupedLinks[gid] - var junction = { - _def: {defaults:{}}, - type: 'junction', - z: RED.workspaces.active(), - id: RED.nodes.id(), - x: 0, - y: 0, - w: 0, h: 0, - outputs: 1, - inputs: 1, - dirty: true - } - links = links.filter(function(l) { return !removedLinks.has(l) }) - if (links.length === 0) { - return - } - links.forEach(function(l) { - junction.x += l._sliceLocation.x - junction.y += l._sliceLocation.y - }) - junction.x = Math.round(junction.x/links.length) - junction.y = Math.round(junction.y/links.length) - if (snapGrid) { - junction.x = (gridSize*Math.round(junction.x/gridSize)); - junction.y = (gridSize*Math.round(junction.y/gridSize)); - } - - var nodeGroups = new Set() - - RED.nodes.addJunction(junction) - addedJunctions.push(junction) - let newLink - if (gid === links[0].source.id+":"+links[0].sourcePort) { - newLink = { - source: links[0].source, - sourcePort: links[0].sourcePort, - target: junction - } - } else { - newLink = { - source: junction, - sourcePort: 0, - target: links[0].target - } - } - addedLinks.push(newLink) - RED.nodes.addLink(newLink) - links.forEach(function(l) { - removedLinks.add(l) - RED.nodes.removeLink(l) - let newLink - if (gid === l.target.id) { - newLink = { - source: l.source, - sourcePort: l.sourcePort, - target: junction - } - } else { - newLink = { - source: junction, - sourcePort: 0, - target: l.target - } - } - addedLinks.push(newLink) - RED.nodes.addLink(newLink) - nodeGroups.add(l.source.g || "__NONE__") - nodeGroups.add(l.target.g || "__NONE__") - }) - if (nodeGroups.size === 1) { - var group = nodeGroups.values().next().value - if (group !== "__NONE__") { - RED.group.addToGroup(RED.nodes.group(group), junction) - } - } - }) - slicePath.remove(); - slicePath = null; - - if (addedJunctions.length > 0) { - RED.history.push({ - t: 'add', - links: addedLinks, - junctions: addedJunctions, - removedLinks: Array.from(removedLinks) - }) - RED.nodes.dirty(true) - } - RED.view.redraw(true); - } - if (mouse_mode == RED.state.MOVING_ACTIVE) { - if (movingSet.length() > 0) { - var addedToGroup = null; - if (activeHoverGroup) { - for (var j=0;j 0 && mouse_mode == RED.state.MOVING_ACTIVE) { - historyEvent = {t:"move",nodes:ns,dirty:RED.nodes.dirty()}; - if (activeSpliceLink) { - // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp - var spliceLink = d3.select(activeSpliceLink).data()[0]; - RED.nodes.removeLink(spliceLink); - var link1 = { - source:spliceLink.source, - sourcePort:spliceLink.sourcePort, - target: movingSet.get(0).n - }; - var link2 = { - source:movingSet.get(0).n, - sourcePort:0, - target: spliceLink.target - }; - RED.nodes.addLink(link1); - RED.nodes.addLink(link2); - historyEvent.links = [link1,link2]; - historyEvent.removedLinks = [spliceLink]; - updateActiveNodes(); - } - if (addedToGroup) { - historyEvent.addToGroup = addedToGroup; - } - RED.nodes.dirty(true); - RED.history.push(historyEvent); - } - } - } - // if (mouse_mode === RED.state.MOVING && mousedown_node && mousedown_node.g) { - // if (mousedown_node.gSelected) { - // delete mousedown_node.gSelected - // } else { - // if (!d3.event.ctrlKey && !d3.event.metaKey) { - // clearSelection(); - // } - // RED.nodes.group(mousedown_node.g).selected = true; - // mousedown_node.selected = true; - // mousedown_node.dirty = true; - // movingSet.add(mousedown_node); - // } - // } - if (mouse_mode == RED.state.MOVING || mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.DETACHED_DRAGGING) { - // if (mousedown_node) { - // delete mousedown_node.gSelected; - // } - if (mouse_mode === RED.state.DETACHED_DRAGGING) { - var ns = []; - for (var j=0;j 0.3) { - zoomView(scaleFactor-0.1); - } - } - function zoomZero() { zoomView(1); } - function searchFlows() { RED.actions.invoke("core:search", $(this).data("term")); } - function searchPrev() { RED.actions.invoke("core:search-previous"); } - function searchNext() { RED.actions.invoke("core:search-next"); } - - - function zoomView(factor) { - var screenSize = [chart.width(),chart.height()]; - var scrollPos = [chart.scrollLeft(),chart.scrollTop()]; - var center = [(scrollPos[0] + screenSize[0]/2)/scaleFactor,(scrollPos[1] + screenSize[1]/2)/scaleFactor]; - scaleFactor = factor; - var newCenter = [(scrollPos[0] + screenSize[0]/2)/scaleFactor,(scrollPos[1] + screenSize[1]/2)/scaleFactor]; - var delta = [(newCenter[0]-center[0])*scaleFactor,(newCenter[1]-center[1])*scaleFactor] - chart.scrollLeft(scrollPos[0]-delta[0]); - chart.scrollTop(scrollPos[1]-delta[1]); - - RED.view.navigator.resize(); - redraw(); - if (RED.settings.get("editor.view.view-store-zoom")) { - RED.settings.setLocal('zoom-level', factor.toFixed(1)) - } - } - - function selectNone() { - if (mouse_mode === RED.state.MOVING || mouse_mode === RED.state.MOVING_ACTIVE) { - return; - } - if (mouse_mode === RED.state.DETACHED_DRAGGING) { - for (var j=0;j 0) { - activeFlowLinks.push({ - refresh: Math.floor(Math.random()*10000), - node: linkNode, - links: offFlowLinks//offFlows.map(function(i) { return {id:i,links:offFlowLinks[i]};}) - }); - } - } - } - if (activeFlowLinks.length === 0 && selectedLinks.length() > 0) { - selectedLinks.forEach(function(link) { - if (link.link) { - activeLinks.push(link); - activeLinkNodes[link.source.id] = link.source; - link.source.dirty = true; - activeLinkNodes[link.target.id] = link.target; - link.target.dirty = true; - } - }) - } - } else { - selection.flows = workspaceSelection; - } - } - var selectionJSON = activeWorkspace+":"+JSON.stringify(selection,function(key,value) { - if (key === 'nodes' || key === 'flows') { - return value.map(function(n) { return n.id }) - } else if (key === 'link') { - return value.source.id+":"+value.sourcePort+":"+value.target.id; - } else if (key === 'links') { - return value.map(function(link) { - return link.source.id+":"+link.sourcePort+":"+link.target.id; - }); - } - return value; - }); - if (selectionJSON !== lastSelection) { - lastSelection = selectionJSON; - RED.events.emit("view:selection-changed",selection); - } - } - - function editSelection() { - if (movingSet.length() > 0) { - var node = movingSet.get(0).n; - if (node.type === "subflow") { - RED.editor.editSubflow(activeSubflow); - } else if (node.type === "group") { - RED.editor.editGroup(node); - } else { - RED.editor.edit(node); - } - } - } - function deleteSelection(reconnectWires) { - if (mouse_mode === RED.state.SELECTING_NODE) { - return; - } - if (portLabelHover) { - portLabelHover.remove(); - portLabelHover = null; - } - var workspaceSelection = RED.workspaces.selection(); - if (workspaceSelection.length > 0) { - var workspaceCount = 0; - workspaceSelection.forEach(function(ws) { if (ws.type === 'tab') { workspaceCount++ } }); - if (workspaceCount === RED.workspaces.count()) { - // Cannot delete all workspaces - return; - } - var historyEvent = { - t: 'delete', - dirty: RED.nodes.dirty(), - nodes: [], - links: [], - groups: [], - junctions: [], - workspaces: [], - subflows: [] - } - var workspaceOrder = RED.nodes.getWorkspaceOrder().slice(0); - - for (var i=0;i 0 || selectedLinks.length() > 0) { - var result; - var node; - var removedNodes = []; - var removedLinks = []; - var removedGroups = []; - var removedJunctions = []; - var removedSubflowOutputs = []; - var removedSubflowInputs = []; - var removedSubflowStatus; - var subflowInstances = []; - var historyEvents = []; - var addToRemovedLinks = function(links) { - if(!links) { return; } - var _links = Array.isArray(links) ? links : [links]; - _links.forEach(function(l) { - removedLinks.push(l); - selectedLinks.remove(l); - }) - } - if (reconnectWires) { - var reconnectResult = RED.nodes.detachNodes(movingSet.nodes()) - var addedLinks = reconnectResult.newLinks; - if (addedLinks.length > 0) { - historyEvents.push({ t:'add', links: addedLinks }) - } - addToRemovedLinks(reconnectResult.removedLinks) - } - - var startDirty = RED.nodes.dirty(); - var startChanged = false; - var selectedGroups = []; - if (movingSet.length() > 0) { - - for (var i=0;i=0; i--) { - var g = selectedGroups[i]; - removedGroups.push(g); - RED.nodes.removeGroup(g); - } - if (removedSubflowOutputs.length > 0) { - result = RED.subflow.removeOutput(removedSubflowOutputs); - if (result) { - addToRemovedLinks(result.links); - } - } - // Assume 0/1 inputs - if (removedSubflowInputs.length == 1) { - result = RED.subflow.removeInput(); - if (result) { - addToRemovedLinks(result.links); - } - } - if (removedSubflowStatus) { - result = RED.subflow.removeStatus(); - if (result) { - addToRemovedLinks(result.links); - } - } - - var instances = RED.subflow.refresh(true); - if (instances) { - subflowInstances = instances.instances; - } - movingSet.clear(); - if (removedNodes.length > 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0 || removedSubflowStatus || removedGroups.length > 0 || removedJunctions.length > 0) { - RED.nodes.dirty(true); - } - } - - if (selectedLinks.length() > 0) { - selectedLinks.forEach(function(link) { - if (link.link) { - var sourceId = link.source.id; - var targetId = link.target.id; - var sourceIdIndex = link.target.links.indexOf(sourceId); - var targetIdIndex = link.source.links.indexOf(targetId); - historyEvents.push({ - t: "edit", - node: link.source, - changed: link.source.changed, - changes: { - links: $.extend(true,{},{v:link.source.links}).v - } - }) - historyEvents.push({ - t: "edit", - node: link.target, - changed: link.target.changed, - changes: { - links: $.extend(true,{},{v:link.target.links}).v - } - }) - link.source.changed = true; - link.target.changed = true; - link.target.links.splice(sourceIdIndex,1); - link.source.links.splice(targetIdIndex,1); - link.source.dirty = true; - link.target.dirty = true; - - } else { - RED.nodes.removeLink(link); - removedLinks.push(link); - } - }) - } - RED.nodes.dirty(true); - var historyEvent = { - t:"delete", - nodes:removedNodes, - links:removedLinks, - groups: removedGroups, - junctions: removedJunctions, - subflowOutputs:removedSubflowOutputs, - subflowInputs:removedSubflowInputs, - subflow: { - id: activeSubflow?activeSubflow.id:undefined, - instances: subflowInstances - }, - dirty:startDirty - }; - if (removedSubflowStatus) { - historyEvent.subflow.status = removedSubflowStatus; - } - if (historyEvents.length > 0) { - historyEvents.unshift(historyEvent); - RED.history.push({ - t:"multi", - events: historyEvents - }) - } else { - RED.history.push(historyEvent); - } - - selectedLinks.clear(); - updateActiveNodes(); - updateSelection(); - redraw(); - } - } - - function copySelection() { - if (mouse_mode === RED.state.SELECTING_NODE) { - return; - } - var nodes = []; - var selection = RED.workspaces.selection(); - if (selection.length > 0) { - nodes = []; - selection.forEach(function(n) { - if (n.type === 'tab') { - nodes.push(n); - nodes = nodes.concat(RED.nodes.groups(n.id)); - nodes = nodes.concat(RED.nodes.filterNodes({z:n.id})); - } - }); - } else { - selection = RED.view.selection(); - if (selection.nodes) { - selection.nodes.forEach(function(n) { - nodes.push(n); - if (n.type === 'group') { - nodes = nodes.concat(RED.group.getNodes(n,true)); - } - }) - } - } - - if (nodes.length > 0) { - var nns = []; - var nodeCount = 0; - var groupCount = 0; - var junctionCount = 0; - var handled = {}; - for (var n=0;n 0) { - RED.notify(RED._("clipboard.nodeCopied",{count:nodeCount}),{id:"clipboard"}); - } else if (groupCount > 0) { - RED.notify(RED._("clipboard.groupCopied",{count:groupCount}),{id:"clipboard"}); - } - } - } - - - function detachSelectedNodes() { - var selection = RED.view.selection(); - if (selection.nodes) { - const {newLinks, removedLinks} = RED.nodes.detachNodes(selection.nodes); - if (removedLinks.length || newLinks.length) { - RED.history.push({ - t: "multi", - events: [ - { t:'delete', links: removedLinks }, - { t:'add', links: newLinks } - ], - dirty: RED.nodes.dirty() - }) - RED.nodes.dirty(true) - } - prepareDrag([selection.nodes[0].x,selection.nodes[0].y]); - mouse_mode = RED.state.DETACHED_DRAGGING; - RED.view.redraw(true); - } - } - - function calculateTextWidth(str, className) { - var result = convertLineBreakCharacter(str); - var width = 0; - for (var i=0;i 1) { - var i=0; - for (i=0;i 0) { - if (drag_lines[0].node === d) { - // Cannot quick-join to self - return - } - if (drag_lines[0].virtualLink && - ( - (drag_lines[0].node.type === 'link in' && d.type !== 'link out') || - (drag_lines[0].node.type === 'link out' && d.type !== 'link in') - ) - ) { - return - } - } - document.body.style.cursor = ""; - if (mouse_mode == RED.state.JOINING || mouse_mode == RED.state.QUICK_JOINING) { - if (typeof TouchEvent != "undefined" && evt instanceof TouchEvent) { - var found = false; - RED.nodes.eachNode(function(n) { - if (n.z == RED.workspaces.active()) { - var hw = n.w/2; - var hh = n.h/2; - if (n.x-hw mouse_position[0] && - n.y-hhmouse_position[1]) { - found = true; - mouseup_node = n; - portType = mouseup_node.inputs>0?PORT_TYPE_INPUT:PORT_TYPE_OUTPUT; - portIndex = 0; - } - } - }); - if (!found && activeSubflow) { - var subflowPorts = []; - if (activeSubflow.status) { - subflowPorts.push(activeSubflow.status) - } - if (activeSubflow.in) { - subflowPorts = subflowPorts.concat(activeSubflow.in) - } - if (activeSubflow.out) { - subflowPorts = subflowPorts.concat(activeSubflow.out) - } - for (var i=0;i mouse_position[0] && - n.y-hhmouse_position[1]) { - found = true; - mouseup_node = n; - portType = mouseup_node.direction === "in"?PORT_TYPE_OUTPUT:PORT_TYPE_INPUT; - portIndex = 0; - break; - } - } - } - } else { - mouseup_node = d; - } - var addedLinks = []; - var removedLinks = []; - var modifiedNodes = []; // joining link nodes - - var select_link = null; - - for (i=0;i 0 || removedLinks.length > 0 || modifiedNodes.length > 0) { - // console.log(addedLinks); - // console.log(removedLinks); - // console.log(modifiedNodes); - var historyEvent; - if (modifiedNodes.length > 0) { - historyEvent = { - t:"multi", - events: linkEditEvents, - dirty:RED.nodes.dirty() - }; - } else { - historyEvent = { - t:"add", - links:addedLinks, - removedLinks: removedLinks, - dirty:RED.nodes.dirty() - }; - } - if (activeSubflow) { - var subflowRefresh = RED.subflow.refresh(true); - if (subflowRefresh) { - historyEvent.subflow = { - id:activeSubflow.id, - changed: activeSubflow.changed, - instances: subflowRefresh.instances - } - } - } - RED.history.push(historyEvent); - updateActiveNodes(); - RED.nodes.dirty(true); - } - if (mouse_mode === RED.state.QUICK_JOINING) { - if (addedLinks.length > 0 || modifiedNodes.length > 0) { - hideDragLines(); - if (portType === PORT_TYPE_INPUT && d.outputs > 0) { - showDragLines([{node:d,port:0,portType:PORT_TYPE_OUTPUT}]); - } else if (portType === PORT_TYPE_OUTPUT && d.inputs > 0) { - showDragLines([{node:d,port:0,portType:PORT_TYPE_INPUT}]); - } else { - resetMouseVars(); - } - mousedown_link = select_link; - if (select_link) { - selectedLinks.clear(); - selectedLinks.add(select_link); - updateSelection(); - } else { - selectedLinks.clear(); - } - } - redraw(); - return; - } - - resetMouseVars(); - hideDragLines(); - if (select_link) { - selectedLinks.clear(); - selectedLinks.add(select_link); - } - mousedown_link = select_link; - if (select_link) { - updateSelection(); - } - redraw(); - } - } - - var portLabelHoverTimeout = null; - var portLabelHover = null; - - - function getElementPosition(node) { - var d3Node = d3.select(node); - if (d3Node.attr('class') === 'red-ui-workspace-chart-event-layer') { - return [0,0]; - } - var result = []; - var localPos = [0,0]; - if (node.nodeName.toLowerCase() === 'g') { - var transform = d3Node.attr("transform"); - if (transform) { - localPos = d3.transform(transform).translate; - } - } else { - localPos = [d3Node.attr("x")||0,d3Node.attr("y")||0]; - } - var parentPos = getElementPosition(node.parentNode); - return [localPos[0]+parentPos[0],localPos[1]+parentPos[1]] - - } - - function getPortLabel(node,portType,portIndex) { - var result; - var nodePortLabels = (portType === PORT_TYPE_INPUT)?node.inputLabels:node.outputLabels; - if (nodePortLabels && nodePortLabels[portIndex]) { - return nodePortLabels[portIndex]; - } - var portLabels = (portType === PORT_TYPE_INPUT)?node._def.inputLabels:node._def.outputLabels; - if (typeof portLabels === 'string') { - result = portLabels; - } else if (typeof portLabels === 'function') { - try { - result = portLabels.call(node,portIndex); - } catch(err) { - console.log("Definition error: "+node.type+"."+((portType === PORT_TYPE_INPUT)?"inputLabels":"outputLabels"),err); - result = null; - } - } else if ($.isArray(portLabels)) { - result = portLabels[portIndex]; - } - return result; - } - function showTooltip(x,y,content,direction) { - var tooltip = eventLayer.append("g") - .attr("transform","translate("+x+","+y+")") - .attr("class","red-ui-flow-port-tooltip"); - - // First check for a user-provided newline - "\\n " - var newlineIndex = content.indexOf("\\n "); - if (newlineIndex > -1 && content[newlineIndex-1] !== '\\') { - content = content.substring(0,newlineIndex)+"..."; - } - - var lines = content.split("\n"); - var labelWidth = 6; - var labelHeight = 12; - var labelHeights = []; - var lineHeight = 0; - lines.forEach(function(l,i) { - var labelDimensions = calculateTextDimensions(l||" ", "red-ui-flow-port-tooltip-label"); - labelWidth = Math.max(labelWidth,labelDimensions[0] + 14); - labelHeights.push(labelDimensions[1]); - if (i === 0) { - lineHeight = labelDimensions[1]; - } - labelHeight += labelDimensions[1]; - }); - var labelWidth1 = (labelWidth/2)-5-2; - var labelWidth2 = labelWidth - 4; - - var labelHeight1 = (labelHeight/2)-5-2; - var labelHeight2 = labelHeight - 4; - var path; - var lx; - var ly = -labelHeight/2; - var anchor; - if (direction === "left") { - path = "M0 0 l -5 -5 v -"+(labelHeight1)+" q 0 -2 -2 -2 h -"+labelWidth+" q -2 0 -2 2 v "+(labelHeight2)+" q 0 2 2 2 h "+labelWidth+" q 2 0 2 -2 v -"+(labelHeight1)+" l 5 -5"; - lx = -14; - anchor = "end"; - } else if (direction === "right") { - path = "M0 0 l 5 -5 v -"+(labelHeight1)+" q 0 -2 2 -2 h "+labelWidth+" q 2 0 2 2 v "+(labelHeight2)+" q 0 2 -2 2 h -"+labelWidth+" q -2 0 -2 -2 v -"+(labelHeight1)+" l -5 -5" - lx = 14; - anchor = "start"; - } else if (direction === "top") { - path = "M0 0 l 5 -5 h "+(labelWidth1)+" q 2 0 2 -2 v -"+labelHeight+" q 0 -2 -2 -2 h -"+(labelWidth2)+" q -2 0 -2 2 v "+labelHeight+" q 0 2 2 2 h "+(labelWidth1)+" l 5 5" - lx = -labelWidth/2 + 6; - ly = -labelHeight-lineHeight+12; - anchor = "start"; - } - tooltip.append("path").attr("d",path); - lines.forEach(function(l,i) { - ly += labelHeights[i]; - // tooltip.append("path").attr("d","M "+(lx-10)+" "+ly+" l 20 0 m -10 -5 l 0 10 ").attr('r',2).attr("stroke","#f00").attr("stroke-width","1").attr("fill","none") - tooltip.append("svg:text").attr("class","red-ui-flow-port-tooltip-label") - .attr("x", lx) - .attr("y", ly) - .attr("text-anchor",anchor) - .text(l||" ") - }); - return tooltip; - } - - function portMouseOver(port,d,portType,portIndex) { - if (mouse_mode === RED.state.SELECTING_NODE) { - d3.event.stopPropagation(); - return; - } - clearTimeout(portLabelHoverTimeout); - var active = (mouse_mode!=RED.state.JOINING && mouse_mode != RED.state.QUICK_JOINING) || // Not currently joining - all ports active - ( - drag_lines.length > 0 && // Currently joining - drag_lines[0].portType !== portType && // INPUT->OUTPUT OUTPUT->INPUT - ( - !drag_lines[0].virtualLink || // Not a link wire - (drag_lines[0].node.type === 'link in' && d.type === 'link out') || - (drag_lines[0].node.type === 'link out' && d.type === 'link in') - ) - ) - - if (active && ((portType === PORT_TYPE_INPUT && ((d._def && d._def.inputLabels)||d.inputLabels)) || (portType === PORT_TYPE_OUTPUT && ((d._def && d._def.outputLabels)||d.outputLabels)))) { - portLabelHoverTimeout = setTimeout(function() { - var tooltip = getPortLabel(d,portType,portIndex); - if (!tooltip) { - return; - } - var pos = getElementPosition(port.node()); - portLabelHoverTimeout = null; - portLabelHover = showTooltip( - (pos[0]+(portType===PORT_TYPE_INPUT?-2:12)), - (pos[1]+5), - tooltip, - portType===PORT_TYPE_INPUT?"left":"right" - ); - },500); - } - port.classed("red-ui-flow-port-hovered",active); - } - function portMouseOut(port,d,portType,portIndex) { - if (mouse_mode === RED.state.SELECTING_NODE) { - d3.event.stopPropagation(); - return; - } - clearTimeout(portLabelHoverTimeout); - if (portLabelHover) { - portLabelHover.remove(); - portLabelHover = null; - } - port.classed("red-ui-flow-port-hovered",false); - } - - function junctionMouseOver(junction, d, portType) { - var active = (portType === undefined) || - (mouse_mode !== RED.state.JOINING && mouse_mode !== RED.state.QUICK_JOINING) || - (drag_lines.length > 0 && drag_lines[0].portType !== portType && !drag_lines[0].virtualLink) - junction.classed("red-ui-flow-junction-hovered", active); - } - function junctionMouseOut(junction, d) { - junction.classed("red-ui-flow-junction-hovered",false); - } - - function prepareDrag(mouse) { - mouse_mode = RED.state.MOVING; - // Called when movingSet should be prepared to be dragged - for (i=0;i 0 && clickElapsed < dblClickInterval) { - mouse_mode = RED.state.DEFAULT; - if (d.type != "subflow") { - if (/^subflow:/.test(d.type) && (d3.event.ctrlKey || d3.event.metaKey)) { - RED.workspaces.show(d.type.substring(8)); - } else { - RED.editor.edit(d); - } - } else { - RED.editor.editSubflow(activeSubflow); - } - clickElapsed = 0; - d3.event.stopPropagation(); - return; - } - if (mouse_mode === RED.state.MOVING) { - // Moving primed, but not active. - if (!groupNodeSelectPrimed && !d.selected && d.g && RED.nodes.group(d.g).selected) { - clearSelection(); - - selectGroup(RED.nodes.group(d.g), false); - enterActiveGroup(RED.nodes.group(d.g)) - - mousedown_node.selected = true; - movingSet.add(mousedown_node); - var mouse = d3.touches(this)[0]||d3.mouse(this); - mouse[0] += d.x-d.w/2; - mouse[1] += d.y-d.h/2; - prepareDrag(mouse); - updateSelection(); - return; - } - } - - groupNodeSelectPrimed = false; - - var direction = d._def? (d.inputs > 0 ? 1: 0) : (d.direction == "in" ? 0: 1) - var wasJoining = false; - if (mouse_mode === RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { - wasJoining = true; - if (drag_lines.length > 0) { - if (drag_lines[0].virtualLink) { - if (d.type === 'link in') { - direction = 1; - } else if (d.type === 'link out') { - direction = 0; - } - } else { - if (drag_lines[0].portType === 1) { - direction = PORT_TYPE_OUTPUT; - } else { - direction = PORT_TYPE_INPUT; - } - } - } - } - - portMouseUp(d, direction, 0); - if (wasJoining) { - d3.selectAll(".red-ui-flow-port-hovered").classed("red-ui-flow-port-hovered",false); - } - } - - - document.addEventListener('pointerlockchange', changeCallback, false); - document.addEventListener('mozpointerlockchange', changeCallback, false); - document.addEventListener('webkitpointerlockchange', changeCallback, false); - document.exitPointerLock = document.exitPointerLock || document.mozExitPointerLock || document.webkitExitPointerLock; - function getPosition(canvas, event) { - var x, y; - - if (event.x != undefined && event.y != undefined) { - x = event.x; - y = event.y; - } else // Firefox method to get the position - { - x = event.clientX + document.body.scrollLeft + - document.documentElement.scrollLeft; - y = event.clientY + document.body.scrollTop + - document.documentElement.scrollTop; - } - - x -= canvas.offsetLeft == null ? canvas.clientLeft : canvas.offsetLeft; - y -= canvas.offsetTop == null ? canvas.clientTop : canvas.offsetTop; - - return {x:x, y:y}; - } - //temporary proxy for when mouse is locked - function canvasMouseMove_locked(e) { - - var canvas = $(eventLayer[0])[0]; - - - // if we enter this for the first time, get the initial position - if (entryCoordinates.x == -1) { - entryCoordinates = getPosition(canvas, e); - } - - - //get a reference to the canvas - var movementX = e.movementX || - e.mozMovementX || - e.webkitMovementX || - 0; - - var movementY = e.movementY || - e.mozMovementY || - e.webkitMovementY || - 0; - - - // calculate the new coordinates where we should draw the ship - entryCoordinates.x = entryCoordinates.x + movementX; - entryCoordinates.y = entryCoordinates.y + movementY; - - if (entryCoordinates.x > chart.width() -65) { - entryCoordinates.x = chart.width()-65; - } else if (entryCoordinates.x < 0) { - entryCoordinates.x = 0; - } - - if (entryCoordinates.y > chart.height() - 85) { - entryCoordinates.y = chart.height() - 85; - } else if (entryCoordinates.y < 0) { - entryCoordinates.y = 0; - } - - - // determine the direction - var direction = 0; - if (movementX > 0) { - direction = 1; - } else if (movementX < 0) { - direction = -1; - } - // console.log(entryCoordinates) - - - d3.event = e - canvasMouseMove.call(this,e); - //canvasMouseMove.call(document.querySelector("g.red-ui-workspace-chart-event-layer")); - } - function changeCallback(e) { - var canvas = $(eventLayer[0])[0] - // var workspaceChart = $("g.red-ui-workspace-chart-event-layer")[0]; - // var workspaceChart = $("#red-ui-workspace-chart")[0]; - if (document.pointerLockElement === canvas || - document.mozPointerLockElement === canvas || - document.webkitPointerLockElement === canvas) { - - // we've got a pointerlock for our element, add a mouselistener - canvas.addEventListener("mousemove", canvasMouseMove_locked, false); - // outer.on("mousemove", canvasMouseMove_locked) - // document.addEventListener("mousemove", canvasMouseMove_locked, false); - } else { - - // pointer lock is no longer active, remove the callback - canvas.removeEventListener("mousemove", canvasMouseMove_locked, false); - // document.removeEventListener("mousemove", canvasMouseMove_locked, false); - // if(outer.off) {outer.off("mousemove", canvasMouseMove_locked)} - // if(outer.removeEventListener) {outer.removeEventListener("mousemove", canvasMouseMove_locked)} - // and reset the entry coordinates - entryCoordinates = {x:-1, y:-1}; - } - } - - function nodeMouseDown(d) { - if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); } - try { - // workspaceChart = $("g.red-ui-workspace-chart-event-layer")[0] - var canvas = $(eventLayer[0])[0] - canvas.requestPointerLock = canvas.requestPointerLock || - canvas.mozRequestPointerLock || - canvas.webkitRequestPointerLock; - canvas.requestPointerLock() - console.log("got pointer lock") - } catch (error) { - console.error(error) - } - - focusView(); - if (d3.event.button === 1) { - return; - } - //var touch0 = d3.event; - //var pos = [touch0.pageX,touch0.pageY]; - //RED.touch.radialMenu.show(d3.select(this),pos); - if (mouse_mode == RED.state.IMPORT_DRAGGING || mouse_mode == RED.state.DETACHED_DRAGGING) { - var historyEvent = RED.history.peek(); - if (activeSpliceLink) { - // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp - var spliceLink = d3.select(activeSpliceLink).data()[0]; - RED.nodes.removeLink(spliceLink); - var link1 = { - source:spliceLink.source, - sourcePort:spliceLink.sourcePort, - target: movingSet.get(0).n - }; - var link2 = { - source:movingSet.get(0).n, - sourcePort:0, - target: spliceLink.target - }; - RED.nodes.addLink(link1); - RED.nodes.addLink(link2); - - historyEvent.links = [link1,link2]; - historyEvent.removedLinks = [spliceLink]; - updateActiveNodes(); - } - - if (activeHoverGroup) { - for (var j=0;j 30 ? 25 : (mousedown_node.w > 0 ? 8 : 3); - if (edgeDelta < targetEdgeDelta) { - if (clickPosition < 0) { - cnodes = [mousedown_node].concat(RED.nodes.getAllUpstreamNodes(mousedown_node)); - } else { - cnodes = [mousedown_node].concat(RED.nodes.getAllDownstreamNodes(mousedown_node)); - } - } else { - cnodes = RED.nodes.getAllFlowNodes(mousedown_node); - } - for (var n=0;n 0) { - var selectClass; - var portType; - if ((drag_lines[0].virtualLink && drag_lines[0].portType === PORT_TYPE_INPUT) || drag_lines[0].portType === PORT_TYPE_OUTPUT) { - selectClass = ".red-ui-flow-port-input .red-ui-flow-port"; - portType = PORT_TYPE_INPUT; - } else { - selectClass = ".red-ui-flow-port-output .red-ui-flow-port"; - portType = PORT_TYPE_OUTPUT; - } - portMouseOver(d3.select(this.parentNode).selectAll(selectClass),d,portType,0); - } - } - } - function nodeMouseOut(d) { - if (RED.view.DEBUG) { console.warn("nodeMouseOut", mouse_mode,d); } - this.parentNode.classList.remove("red-ui-flow-node-hovered"); - clearTimeout(portLabelHoverTimeout); - if (portLabelHover) { - portLabelHover.remove(); - portLabelHover = null; - } - if (mouse_mode === RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { - if (drag_lines.length > 0) { - var selectClass; - var portType; - if ((drag_lines[0].virtualLink && drag_lines[0].portType === PORT_TYPE_INPUT) || drag_lines[0].portType === PORT_TYPE_OUTPUT) { - selectClass = ".red-ui-flow-port-input .red-ui-flow-port"; - portType = PORT_TYPE_INPUT; - } else { - selectClass = ".red-ui-flow-port-output .red-ui-flow-port"; - portType = PORT_TYPE_OUTPUT; - } - portMouseOut(d3.select(this.parentNode).selectAll(selectClass),d,portType,0); - } - } - } - - function portMouseDownProxy(e) { portMouseDown(this.__data__,this.__portType__,this.__portIndex__, e); } - function portTouchStartProxy(e) { portMouseDown(this.__data__,this.__portType__,this.__portIndex__, e); e.preventDefault() } - function portMouseUpProxy(e) { portMouseUp(this.__data__,this.__portType__,this.__portIndex__, e); } - function portTouchEndProxy(e) { portMouseUp(this.__data__,this.__portType__,this.__portIndex__, e); e.preventDefault() } - function portMouseOverProxy(e) { portMouseOver(d3.select(this), this.__data__,this.__portType__,this.__portIndex__, e); } - function portMouseOutProxy(e) { portMouseOut(d3.select(this), this.__data__,this.__portType__,this.__portIndex__, e); } - - function junctionMouseOverProxy(e) { junctionMouseOver(d3.select(this), this.__data__, this.__portType__) } - function junctionMouseOutProxy(e) { junctionMouseOut(d3.select(this), this.__data__) } - - function linkMouseDown(d) { - if (RED.view.DEBUG) { - console.warn("linkMouseDown", { mouse_mode, point: d3.mouse(this), event: d3.event }); - } - if (mouse_mode === RED.state.SELECTING_NODE) { - d3.event.stopPropagation(); - return; - } - if (d3.event.button === 2) { - return - } - mousedown_link = d; - - if (!(d3.event.metaKey || d3.event.ctrlKey)) { - clearSelection(); - } - if (d3.event.metaKey || d3.event.ctrlKey) { - if (!selectedLinks.has(mousedown_link)) { - selectedLinks.add(mousedown_link); - } else { - if (selectedLinks.length() !== 1) { - selectedLinks.remove(mousedown_link); - } - } - } else { - selectedLinks.add(mousedown_link); - } - updateSelection(); - redraw(); - focusView(); - d3.event.stopPropagation(); - if (!mousedown_link.link && movingSet.length() === 0 && (d3.event.touches || d3.event.button === 0) && selectedLinks.length() === 1 && selectedLinks.has(mousedown_link) && (d3.event.metaKey || d3.event.ctrlKey)) { - d3.select(this).classed("red-ui-flow-link-splice",true); - var point = d3.mouse(this); - var clickedGroup = getGroupAt(point[0],point[1]); - showQuickAddDialog({position:point, splice:mousedown_link, group:clickedGroup}); - } - } - function linkTouchStart(d) { - if (mouse_mode === RED.state.SELECTING_NODE) { - d3.event.stopPropagation(); - return; - } - mousedown_link = d; - clearSelection(); - selectedLinks.clear(); - selectedLinks.add(mousedown_link); - updateSelection(); - redraw(); - focusView(); - d3.event.stopPropagation(); - - var obj = d3.select(document.body); - var touch0 = d3.event.touches.item(0); - var pos = [touch0.pageX,touch0.pageY]; - touchStartTime = setTimeout(function() { - touchStartTime = null; - showTouchMenu(obj,pos); - },touchLongPressTimeout); - d3.event.preventDefault(); - } - - function groupMouseUp(g) { - if (dblClickPrimed && mousedown_group == g && clickElapsed > 0 && clickElapsed < dblClickInterval) { - mouse_mode = RED.state.DEFAULT; - RED.editor.editGroup(g); - d3.event.stopPropagation(); - return; - } - - } - - function groupMouseDown(g) { - var mouse = d3.touches(this.parentNode)[0]||d3.mouse(this.parentNode); - // if (! (mouse[0] < g.x+10 || mouse[0] > g.x+g.w-10 || mouse[1] < g.y+10 || mouse[1] > g.y+g.h-10) ) { - // return - // } - - focusView(); - if (d3.event.button === 1) { - return; - } - - if (mouse_mode == RED.state.QUICK_JOINING) { - d3.event.stopPropagation(); - return; - } else if (mouse_mode === RED.state.SELECTING_NODE) { - d3.event.stopPropagation(); - return; - } - - mousedown_group = g; - - var now = Date.now(); - clickElapsed = now-clickTime; - clickTime = now; - - dblClickPrimed = ( - lastClickNode == g && - (d3.event.touches || d3.event.button === 0) && - !d3.event.shiftKey && !d3.event.metaKey && !d3.event.altKey && !d3.event.ctrlKey && - clickElapsed < dblClickInterval - ); - lastClickNode = g; - - if (g.selected && (d3.event.ctrlKey||d3.event.metaKey)) { - if (g === activeGroup) { - exitActiveGroup(); - } - deselectGroup(g); - d3.event.stopPropagation(); - } else { - if (!g.selected) { - if (!d3.event.ctrlKey && !d3.event.metaKey) { - var ag = activeGroup; - clearSelection(); - if (ag && g.g === ag.id) { - enterActiveGroup(ag); - activeGroup.selected = true; - } - } - if (activeGroup) { - if (!RED.group.contains(activeGroup,g)) { - // Clicked on a group that is outside the activeGroup - exitActiveGroup(); - } else { - } - } - selectGroup(g,true);//!wasSelected); - } else if (activeGroup && g.g !== activeGroup.id){ - exitActiveGroup(); - } - - - if (d3.event.button != 2) { - var d = g.nodes[0]; - prepareDrag(mouse); - mousedown_group.dx = mousedown_group.x - mouse[0]; - mousedown_group.dy = mousedown_group.y - mouse[1]; - } - } - - updateSelection(); - redraw(); - d3.event.stopPropagation(); - } - - function selectGroup(g, includeNodes, addToMovingSet) { - if (!g.selected) { - g.selected = true; - g.dirty = true; - } - if (addToMovingSet !== false) { - movingSet.add(g); - } - if (includeNodes) { - var currentSet = new Set(movingSet.nodes()); - var allNodes = RED.group.getNodes(g,true); - allNodes.forEach(function(n) { - if (!currentSet.has(n)) { - movingSet.add(n) - // n.selected = true; - } - n.dirty = true; - }) - } - } - function enterActiveGroup(group) { - if (activeGroup) { - exitActiveGroup(); - } - group.active = true; - group.dirty = true; - activeGroup = group; - movingSet.remove(group); - } - function exitActiveGroup() { - if (activeGroup) { - activeGroup.active = false; - activeGroup.dirty = true; - deselectGroup(activeGroup); - selectGroup(activeGroup,true); - activeGroup = null; - } - } - function deselectGroup(g) { - if (g.selected) { - g.selected = false; - g.dirty = true; - } - var nodeSet = new Set(g.nodes); - nodeSet.add(g); - for (var i = movingSet.length()-1; i >= 0; i -= 1) { - var msn = movingSet.get(i); - if (nodeSet.has(msn.n) || msn.n === g) { - msn.n.selected = false; - msn.n.dirty = true; - movingSet.remove(msn.n,i) - } - } - } - function getGroupAt(x,y) { - // x,y expected to be in node-co-ordinate space - var candidateGroups = {}; - for (var i=0;i= g.x && x <= g.x + g.w && y >= g.y && y <= g.y + g.h) { - candidateGroups[g.id] = g; - } - } - var ids = Object.keys(candidateGroups); - if (ids.length > 1) { - ids.forEach(function(id) { - if (candidateGroups[id] && candidateGroups[id].g) { - delete candidateGroups[candidateGroups[id].g] - } - }) - ids = Object.keys(candidateGroups); - } - if (ids.length === 0) { - return null; - } else { - return candidateGroups[ids[ids.length-1]] - } - } - - function isButtonEnabled(d) { - var buttonEnabled = true; - var ws = RED.nodes.workspace(RED.workspaces.active()); - if (ws && !ws.disabled && !d.d) { - if (d._def.button.hasOwnProperty('enabled')) { - if (typeof d._def.button.enabled === "function") { - buttonEnabled = d._def.button.enabled.call(d); - } else { - buttonEnabled = d._def.button.enabled; - } - } - } else { - buttonEnabled = false; - } - return buttonEnabled; - } - - function nodeButtonClicked(d) { - if (mouse_mode === RED.state.SELECTING_NODE) { - if (d3.event) { - d3.event.stopPropagation(); - } - return; - } - var activeWorkspace = RED.workspaces.active(); - var ws = RED.nodes.workspace(activeWorkspace); - if (ws && !ws.disabled && !d.d) { - if (d._def.button.toggle) { - d[d._def.button.toggle] = !d[d._def.button.toggle]; - d.dirty = true; - } - if (d._def.button.onclick) { - try { - d._def.button.onclick.call(d); - } catch(err) { - console.log("Definition error: "+d.type+".onclick",err); - } - } - if (d.dirty) { - redraw(); - } - } else { - if (activeSubflow) { - RED.notify(RED._("notification.warning", {message:RED._("notification.warnings.nodeActionDisabledSubflow")}),"warning"); - } else { - RED.notify(RED._("notification.warning", {message:RED._("notification.warnings.nodeActionDisabled")}),"warning"); - } - } - if (d3.event) { - d3.event.preventDefault(); - } - } - - function showTouchMenu(obj,pos) { - var mdn = mousedown_node; - var options = []; - options.push({name:"delete",disabled:(movingSet.length()===0 && selectedLinks.length() === 0),onselect:function() {deleteSelection();}}); - options.push({name:"cut",disabled:(movingSet.length()===0),onselect:function() {copySelection();deleteSelection();}}); - options.push({name:"copy",disabled:(movingSet.length()===0),onselect:function() {copySelection();}}); - options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard, {generateIds: true, touchImport: true});}}); - options.push({name:"edit",disabled:(movingSet.length() != 1),onselect:function() { RED.editor.edit(mdn);}}); - options.push({name:"select",onselect:function() {selectAll();}}); - options.push({name:"undo",disabled:(RED.history.depth() === 0),onselect:function() {RED.history.pop();}}); - options.push({name:"add",onselect:function() { - chartPos = chart.offset(); - showQuickAddDialog({ - position:[pos[0]-chartPos.left+chart.scrollLeft(),pos[1]-chartPos.top+chart.scrollTop()], - touchTrigger:true - }) - }}); - - RED.touch.radialMenu.show(obj,pos,options); - resetMouseVars(); - } - - function createIconAttributes(iconUrl, icon_group, d) { - var fontAwesomeUnicode = null; - if (iconUrl.indexOf("font-awesome/") === 0) { - var iconName = iconUrl.substr(13); - var fontAwesomeUnicode = RED.nodes.fontAwesome.getIconUnicode(iconName); - if (!fontAwesomeUnicode) { - var iconPath = RED.utils.getDefaultNodeIcon(d._def, d); - iconUrl = RED.settings.apiRootUrl+"icons/"+iconPath.module+"/"+iconPath.file; - } - } - if (fontAwesomeUnicode) { - // Since Node-RED workspace uses SVG, i tag cannot be used for font-awesome icon. - // On SVG, use text tag as an alternative. - icon_group.append("text") - .attr("xlink:href",iconUrl) - .attr("class","fa-lg") - .attr("x",15) - .text(fontAwesomeUnicode); - } else { - var icon = icon_group.append("image") - .style("display","none") - .attr("xlink:href",iconUrl) - .attr("class","red-ui-flow-node-icon") - .attr("x",0) - .attr("width","30") - .attr("height","30"); - - var img = new Image(); - img.src = iconUrl; - img.onload = function() { - if (!iconUrl.match(/\.svg$/)) { - var largestEdge = Math.max(img.width,img.height); - var scaleFactor = 1; - if (largestEdge > 30) { - scaleFactor = 30/largestEdge; - } - var width = img.width * scaleFactor; - var height = img.height * scaleFactor; - icon.attr("width",width); - icon.attr("height",height); - icon.attr("x",15-width/2); - } - icon.attr("xlink:href",iconUrl); - icon.style("display",null); - //if ("right" == d._def.align) { - // icon.attr("x",function(d){return d.w-img.width-1-(d.outputs>0?5:0);}); - // icon_shade.attr("x",function(d){return d.w-30}); - // icon_shade_border.attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2);}); - //} - } - } - } - - function redrawStatus(d,nodeEl) { - if (d.z !== RED.workspaces.active()) { - return; - } - if (!nodeEl) { - nodeEl = document.getElementById(d.id); - } - if (nodeEl) { - // Do not show node status if: - // - global flag set - // - node has no status - // - node is disabled - if (!showStatus || !d.status || d.d === true) { - nodeEl.__statusGroup__.style.display = "none"; - } else { - nodeEl.__statusGroup__.style.display = "inline"; - var fill = status_colours[d.status.fill]; // Only allow our colours for now - if (d.status.shape == null && fill == null) { - nodeEl.__statusShape__.style.display = "none"; - nodeEl.__statusGroup__.setAttribute("transform","translate(-14,"+(d.h+3)+")"); - } else { - nodeEl.__statusGroup__.setAttribute("transform","translate(3,"+(d.h+3)+")"); - var statusClass = "red-ui-flow-node-status-"+(d.status.shape||"dot")+"-"+d.status.fill; - nodeEl.__statusShape__.style.display = "inline"; - nodeEl.__statusShape__.setAttribute("class","red-ui-flow-node-status "+statusClass); - } - if (d.status.hasOwnProperty('text')) { - nodeEl.__statusLabel__.textContent = d.status.text; - } else { - nodeEl.__statusLabel__.textContent = ""; - } - } - delete d.dirtyStatus; - } - } - - var pendingRedraw; - - function redraw() { - if (RED.view.DEBUG_SYNC_REDRAW) { - _redraw(); - } else { - if (pendingRedraw) { - cancelAnimationFrame(pendingRedraw); - } - pendingRedraw = requestAnimationFrame(_redraw); - } - } - - function _redraw() { - eventLayer.attr("transform","scale("+scaleFactor+")"); - outer.attr("width", space_width*scaleFactor).attr("height", space_height*scaleFactor); - - // Don't bother redrawing nodes if we're drawing links - if (showAllLinkPorts !== -1 || mouse_mode != RED.state.JOINING) { - - var dirtyNodes = {}; - - if (activeSubflow) { - var subflowOutputs = nodeLayer.selectAll(".red-ui-flow-subflow-port-output").data(activeSubflow.out,function(d,i){ return d.id;}); - subflowOutputs.exit().remove(); - var outGroup = subflowOutputs.enter().insert("svg:g").attr("class","red-ui-flow-node red-ui-flow-subflow-port-output") - outGroup.each(function(d,i) { - var node = d3.select(this); - var nodeContents = document.createDocumentFragment(); - - d.h = 40; - d.resize = true; - d.dirty = true; - - var mainRect = document.createElementNS("http://www.w3.org/2000/svg","rect"); - mainRect.__data__ = d; - mainRect.setAttribute("class", "red-ui-flow-subflow-port"); - mainRect.setAttribute("rx", 8); - mainRect.setAttribute("ry", 8); - mainRect.setAttribute("width", 40); - mainRect.setAttribute("height", 40); - node[0][0].__mainRect__ = mainRect; - d3.select(mainRect) - .on("mouseup",nodeMouseUp) - .on("mousedown",nodeMouseDown) - .on("touchstart",nodeTouchStart) - .on("touchend",nodeTouchEnd) - nodeContents.appendChild(mainRect); - - var output_groupEl = document.createElementNS("http://www.w3.org/2000/svg","g"); - output_groupEl.setAttribute("x",0); - output_groupEl.setAttribute("y",0); - node[0][0].__outputLabelGroup__ = output_groupEl; - - var output_output = document.createElementNS("http://www.w3.org/2000/svg","text"); - output_output.setAttribute("class","red-ui-flow-port-label"); - output_output.style["font-size"] = "10px"; - output_output.textContent = "output"; - output_groupEl.appendChild(output_output); - node[0][0].__outputOutput__ = output_output; - - var output_number = document.createElementNS("http://www.w3.org/2000/svg","text"); - output_number.setAttribute("class","red-ui-flow-port-label red-ui-flow-port-index"); - output_number.setAttribute("x",0); - output_number.setAttribute("y",0); - output_number.textContent = d.i+1; - output_groupEl.appendChild(output_number); - node[0][0].__outputNumber__ = output_number; - - var output_border = document.createElementNS("http://www.w3.org/2000/svg","path"); - output_border.setAttribute("d","M 40 1 l 0 38") - output_border.setAttribute("class", "red-ui-flow-node-icon-shade-border") - output_groupEl.appendChild(output_border); - node[0][0].__outputBorder__ = output_border; - - nodeContents.appendChild(output_groupEl); - - var text = document.createElementNS("http://www.w3.org/2000/svg","g"); - text.setAttribute("class","red-ui-flow-port-label"); - text.setAttribute("transform","translate(38,0)"); - text.setAttribute('style', 'fill : #888'); // hard coded here! - node[0][0].__textGroup__ = text; - nodeContents.append(text); - - var portEl = document.createElementNS("http://www.w3.org/2000/svg","g"); - portEl.setAttribute('transform','translate(-5,15)') - - var port = document.createElementNS("http://www.w3.org/2000/svg","rect"); - port.setAttribute("class","red-ui-flow-port"); - port.setAttribute("rx",3); - port.setAttribute("ry",3); - port.setAttribute("width",10); - port.setAttribute("height",10); - portEl.appendChild(port); - port.__data__ = d; - - d3.select(port) - .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);} ) - .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} ) - .on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);}) - .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} ) - .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_INPUT,0);}) - .on("mouseout",function(d){portMouseOut(d3.select(this),d,PORT_TYPE_INPUT,0);}); - - node[0][0].__port__ = portEl - nodeContents.appendChild(portEl); - node[0][0].appendChild(nodeContents); - }); - - var subflowInputs = nodeLayer.selectAll(".red-ui-flow-subflow-port-input").data(activeSubflow.in,function(d,i){ return d.id;}); - subflowInputs.exit().remove(); - var inGroup = subflowInputs.enter().insert("svg:g").attr("class","red-ui-flow-node red-ui-flow-subflow-port-input").attr("transform",function(d) { return "translate("+(d.x-20)+","+(d.y-20)+")"}); - inGroup.each(function(d,i) { - d.w=40; - d.h=40; - }); - inGroup.append("rect").attr("class","red-ui-flow-subflow-port").attr("rx",8).attr("ry",8).attr("width",40).attr("height",40) - // TODO: This is exactly the same set of handlers used for regular nodes - DRY - .on("mouseup",nodeMouseUp) - .on("mousedown",nodeMouseDown) - .on("touchstart",nodeTouchStart) - .on("touchend", nodeTouchEnd); - - inGroup.append("g").attr('transform','translate(35,15)').append("rect").attr("class","red-ui-flow-port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10) - .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_OUTPUT,i);} ) - .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_OUTPUT,i);d3.event.preventDefault();} ) - .on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE_OUTPUT,i);}) - .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_OUTPUT,i);d3.event.preventDefault();} ) - .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_OUTPUT,0);}) - .on("mouseout",function(d) {portMouseOut(d3.select(this),d,PORT_TYPE_OUTPUT,0);}); - - inGroup.append("svg:text").attr("class","red-ui-flow-port-label").attr("x",18).attr("y",20).style("font-size","10px").text("input"); - - var subflowStatus = nodeLayer.selectAll(".red-ui-flow-subflow-port-status").data(activeSubflow.status?[activeSubflow.status]:[],function(d,i){ return d.id;}); - subflowStatus.exit().remove(); - - var statusGroup = subflowStatus.enter().insert("svg:g").attr("class","red-ui-flow-node red-ui-flow-subflow-port-status").attr("transform",function(d) { return "translate("+(d.x-20)+","+(d.y-20)+")"}); - statusGroup.each(function(d,i) { - d.w=40; - d.h=40; - }); - statusGroup.append("rect").attr("class","red-ui-flow-subflow-port").attr("rx",8).attr("ry",8).attr("width",40).attr("height",40) - // TODO: This is exactly the same set of handlers used for regular nodes - DRY - .on("mouseup",nodeMouseUp) - .on("mousedown",nodeMouseDown) - .on("touchstart",nodeTouchStart) - .on("touchend", nodeTouchEnd); - - statusGroup.append("g").attr('transform','translate(-5,15)').append("rect").attr("class","red-ui-flow-port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10) - .on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);} ) - .on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} ) - .on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);}) - .on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} ) - .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_INPUT,0);}) - .on("mouseout",function(d){portMouseOut(d3.select(this),d,PORT_TYPE_INPUT,0);}); - - statusGroup.append("svg:text").attr("class","red-ui-flow-port-label").attr("x",22).attr("y",20).style("font-size","10px").text("status"); - - subflowOutputs.each(function(d,i) { - if (d.dirty) { - - var port_height = 40; - - var self = this; - var thisNode = d3.select(this); - - dirtyNodes[d.id] = d; - - var label = getPortLabel(activeSubflow, PORT_TYPE_OUTPUT, d.i) || ""; - var hideLabel = (label.length < 1) - - var labelParts; - if (d.resize || this.__hideLabel__ !== hideLabel || this.__label__ !== label) { - labelParts = getLabelParts(label, "red-ui-flow-node-label"); - if (labelParts.lines.length !== this.__labelLineCount__ || this.__label__ !== label) { - d.resize = true; - } - this.__label__ = label; - this.__labelLineCount__ = labelParts.lines.length; - - if (hideLabel) { - d.h = Math.max(port_height,(d.outputs || 0) * 15); - } else { - d.h = Math.max(6+24*labelParts.lines.length,(d.outputs || 0) * 15, port_height); - } - this.__hideLabel__ = hideLabel; - } - - if (d.resize) { - var ow = d.w; - if (hideLabel) { - d.w = port_height; - } else { - d.w = Math.max(port_height,20*(Math.ceil((labelParts.width+50+7)/20)) ); - } - if (ow !== undefined) { - d.x += (d.w-ow)/2; - } - d.resize = false; - } - - this.setAttribute("transform", "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"); - // This might be the first redraw after a node has been click-dragged to start a move. - // So its selected state might have changed since the last redraw. - this.classList.toggle("red-ui-flow-node-selected", !!d.selected ) - if (mouse_mode != RED.state.MOVING_ACTIVE) { - this.classList.toggle("red-ui-flow-node-disabled", d.d === true); - this.__mainRect__.setAttribute("width", d.w) - this.__mainRect__.setAttribute("height", d.h) - this.__mainRect__.classList.toggle("red-ui-flow-node-highlighted",!!d.highlighted ); - - if (labelParts) { - // The label has changed - var sa = labelParts.lines; - var sn = labelParts.lines.length; - var textLines = this.__textGroup__.childNodes; - while(textLines.length > sn) { - textLines[textLines.length-1].remove(); - } - for (var i=0; i0?7:0))/20)) ); - } - if (ow !== undefined) { - d.x += (d.w-ow)/2; - } - d.resize = false; - } - if (d._colorChanged) { - var newColor = RED.utils.getNodeColor(d.type,d._def); - this.__mainRect__.setAttribute("fill",newColor); - if (this.__buttonGroupButton__) { - this.__buttonGroupButton__.settAttribute("fill",newColor); - } - delete d._colorChanged; - } - //thisNode.selectAll(".centerDot").attr({"cx":function(d) { return d.w/2;},"cy":function(d){return d.h/2}}); - this.setAttribute("transform", "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"); - // This might be the first redraw after a node has been click-dragged to start a move. - // So its selected state might have changed since the last redraw. - this.classList.toggle("red-ui-flow-node-selected", !!d.selected ) - if (mouse_mode != RED.state.MOVING_ACTIVE) { - this.classList.toggle("red-ui-flow-node-disabled", d.d === true); - this.__mainRect__.setAttribute("width", d.w) - this.__mainRect__.setAttribute("height", d.h) - this.__mainRect__.classList.toggle("red-ui-flow-node-highlighted",!!d.highlighted ); - - if (labelParts) { - // The label has changed - var sa = labelParts.lines; - var sn = labelParts.lines.length; - var textLines = this.__textGroup__.childNodes; - while(textLines.length > sn) { - textLines[textLines.length-1].remove(); - } - for (var i=0; i numOutputs) { - var port = this.__outputs__.pop(); - RED.hooks.trigger("viewRemovePort",{ - node:d, - el:this, - port:port, - portType: "output", - portIndex: this.__outputs__.length - }) - port.remove(); - } - for(var portIndex = 0; portIndex < numOutputs; portIndex++ ) { - var portGroup; - if (portIndex === this.__outputs__.length) { - portGroup = document.createElementNS("http://www.w3.org/2000/svg","g"); - portGroup.setAttribute("class","red-ui-flow-port-output"); - var portPort; - if (d.type === "link out") { - portPort = document.createElementNS("http://www.w3.org/2000/svg","circle"); - portPort.setAttribute("cx",11); - portPort.setAttribute("cy",5); - portPort.setAttribute("r",5); - portPort.setAttribute("class","red-ui-flow-port red-ui-flow-link-port"); - } else { - portPort = document.createElementNS("http://www.w3.org/2000/svg","rect"); - portPort.setAttribute("rx",3); - portPort.setAttribute("ry",3); - portPort.setAttribute("width",10); - portPort.setAttribute("height",10); - portPort.setAttribute("class","red-ui-flow-port"); - } - portGroup.appendChild(portPort); - portGroup.__port__ = portPort; - portPort.__data__ = this.__data__; - portPort.__portType__ = PORT_TYPE_OUTPUT; - portPort.__portIndex__ = portIndex; - portPort.addEventListener("mousedown", portMouseDownProxy); - portPort.addEventListener("touchstart", portTouchStartProxy); - portPort.addEventListener("mouseup", portMouseUpProxy); - portPort.addEventListener("touchend", portTouchEndProxy); - portPort.addEventListener("mouseover", portMouseOverProxy); - portPort.addEventListener("mouseout", portMouseOutProxy); - - this.appendChild(portGroup); - this.__outputs__.push(portGroup); - RED.hooks.trigger("viewAddPort",{node:d,el: this, port: portGroup, portType: "output", portIndex: portIndex}) - } else { - portGroup = this.__outputs__[portIndex]; - } - var x = d.w - 5; - var y = (d.h/2)-((numOutputs-1)/2)*13; - portGroup.setAttribute("transform","translate("+x+","+((y+13*portIndex)-5)+")") - } - if (d._def.icon) { - var icon = thisNode.select(".red-ui-flow-node-icon"); - var faIcon = thisNode.select(".fa-lg"); - var current_url; - if (!icon.empty()) { - current_url = icon.attr("xlink:href"); - } else { - current_url = faIcon.attr("xlink:href"); - } - var new_url = RED.utils.getNodeIcon(d._def,d); - if (new_url !== current_url) { - if (!icon.empty()) { - icon.remove(); - } else { - faIcon.remove(); - } - var iconGroup = thisNode.select(".red-ui-flow-node-icon-group"); - createIconAttributes(new_url, iconGroup, d); - icon = thisNode.select(".red-ui-flow-node-icon"); - faIcon = thisNode.select(".fa-lg"); - } - - icon.attr("y",function(){return (d.h-d3.select(this).attr("height"))/2;}); - this.__iconShade__.setAttribute("height", d.h ); - this.__iconShadeBorder__.setAttribute("d", - "M " + (((!d._def.align && d.inputs !== 0 && d.outputs === 0) || "right" === d._def.align) ? 0 : 30) + " 1 l 0 " + (d.h - 2) - ); - faIcon.attr("y",(d.h+13)/2); - } - // this.__changeBadge__.setAttribute("transform", "translate("+(d.w-10)+", -2)"); - // this.__changeBadge__.classList.toggle("hide", !(d.changed||d.moved)); - // this.__errorBadge__.setAttribute("transform", "translate("+(d.w-10-((d.changed||d.moved)?14:0))+", -2)"); - // this.__errorBadge__.classList.toggle("hide", d.valid); - - thisNode.selectAll(".red-ui-flow-port-input").each(function(d,i) { - var port = d3.select(this); - port.attr("transform",function(d){return "translate(-5,"+((d.h/2)-5)+")";}) - }); - - if (d._def.button) { - var buttonEnabled = isButtonEnabled(d); - this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-disabled", !buttonEnabled); - if(RED.runtime && Object.hasOwn(RED.runtime,'started')) { - this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-stopped", !RED.runtime.started); - } - - var x = d._def.align == "right"?d.w-6:-25; - if (d._def.button.toggle && !d[d._def.button.toggle]) { - x = x - (d._def.align == "right"?8:-8); - } - this.__buttonGroup__.setAttribute("transform", "translate("+x+",2)"); - - if (d._def.button.toggle) { - this.__buttonGroupButton__.setAttribute("fill-opacity",d[d._def.button.toggle]?1:0.2) - this.__buttonGroupBackground__.setAttribute("fill-opacity",d[d._def.button.toggle]?1:0.2) - } - - if (typeof d._def.button.visible === "function") { // is defined and a function... - if (d._def.button.visible.call(d) === false) { - this.__buttonGroup__.style.display = "none"; - } - else { - this.__buttonGroup__.style.display = "inherit"; - } - } - } - // thisNode.selectAll(".node_badge_group").attr("transform",function(d){return "translate("+(d.w-40)+","+(d.h+3)+")";}); - // thisNode.selectAll("text.node_badge_label").text(function(d,i) { - // if (d._def.badge) { - // if (typeof d._def.badge == "function") { - // try { - // return d._def.badge.call(d); - // } catch(err) { - // console.log("Definition error: "+d.type+".badge",err); - // return ""; - // } - // } else { - // return d._def.badge; - // } - // } - // return ""; - // }); - } - - if (d.dirtyStatus) { - redrawStatus(d,this); - } - d.dirty = false; - if (d.g) { - if (!dirtyGroups[d.g]) { - var gg = d.g; - while (gg && !dirtyGroups[gg]) { - dirtyGroups[gg] = RED.nodes.group(gg); - gg = dirtyGroups[gg].g; - } - } - } - } - - RED.hooks.trigger("viewRedrawNode",{node:d,el:this}) - }); - - if (nodesReordered) { - node.sort(function(a,b) { - return a._index - b._index; - }) - } - - var junction = junctionLayer.selectAll(".red-ui-flow-junction").data( - activeJunctions, - d => d.id - ) - var junctionEnter = junction.enter().insert("svg:g").attr("class","red-ui-flow-junction") - junctionEnter.each(function(d,i) { - var junction = d3.select(this); - var contents = document.createDocumentFragment(); - // d.added = true; - var junctionBack = document.createElementNS("http://www.w3.org/2000/svg","rect"); - junctionBack.setAttribute("class","red-ui-flow-junction-background"); - junctionBack.setAttribute("x",-5); - junctionBack.setAttribute("y",-5); - junctionBack.setAttribute("width",10); - junctionBack.setAttribute("height",10); - junctionBack.setAttribute("rx",3); - junctionBack.setAttribute("ry",3); - junctionBack.__data__ = d; - this.__junctionBack__ = junctionBack; - contents.appendChild(junctionBack); - - var junctionInput = document.createElementNS("http://www.w3.org/2000/svg","rect"); - junctionInput.setAttribute("class","red-ui-flow-junction-port red-ui-flow-junction-port-input"); - junctionInput.setAttribute("x",-5); - junctionInput.setAttribute("y",-5); - junctionInput.setAttribute("width",10); - junctionInput.setAttribute("height",10); - junctionInput.setAttribute("rx",3); - junctionInput.setAttribute("ry",3); - junctionInput.__data__ = d; - junctionInput.__portType__ = PORT_TYPE_INPUT; - junctionInput.__portIndex__ = 0; - this.__junctionInput__ = junctionOutput; - contents.appendChild(junctionInput); - junctionInput.addEventListener("mouseup", portMouseUpProxy); - junctionInput.addEventListener("mousedown", portMouseDownProxy); - - - this.__junctionInput__ = junctionInput; - contents.appendChild(junctionInput); - var junctionOutput = document.createElementNS("http://www.w3.org/2000/svg","rect"); - junctionOutput.setAttribute("class","red-ui-flow-junction-port red-ui-flow-junction-port-output"); - junctionOutput.setAttribute("x",-5); - junctionOutput.setAttribute("y",-5); - junctionOutput.setAttribute("width",10); - junctionOutput.setAttribute("height",10); - junctionOutput.setAttribute("rx",3); - junctionOutput.setAttribute("ry",3); - junctionOutput.__data__ = d; - junctionOutput.__portType__ = PORT_TYPE_OUTPUT; - junctionOutput.__portIndex__ = 0; - this.__junctionOutput__ = junctionOutput; - contents.appendChild(junctionOutput); - junctionOutput.addEventListener("mouseup", portMouseUpProxy); - junctionOutput.addEventListener("mousedown", portMouseDownProxy); - - junctionOutput.addEventListener("mouseover", junctionMouseOverProxy); - junctionOutput.addEventListener("mouseout", junctionMouseOutProxy); - junctionInput.addEventListener("mouseover", junctionMouseOverProxy); - junctionInput.addEventListener("mouseout", junctionMouseOutProxy); - junctionBack.addEventListener("mouseover", junctionMouseOverProxy); - junctionBack.addEventListener("mouseout", junctionMouseOutProxy); - - // These handlers expect to be registered as d3 events - d3.select(junctionBack).on("mousedown", nodeMouseDown).on("mouseup", nodeMouseUp); - - junction[0][0].appendChild(contents); - }) - junction.exit().remove(); - junction.each(function(d) { - var junction = d3.select(this); - this.setAttribute("transform", "translate(" + (d.x) + "," + (d.y) + ")"); - if (d.dirty) { - junction.classed("red-ui-flow-junction-dragging", mouse_mode === RED.state.MOVING_ACTIVE && movingSet.has(d)) - junction.classed("selected", !!d.selected) - dirtyNodes[d.id] = d; - - if (d.g) { - if (!dirtyGroups[d.g]) { - var gg = d.g; - while (gg && !dirtyGroups[gg]) { - dirtyGroups[gg] = RED.nodes.group(gg); - gg = dirtyGroups[gg].g; - } - } - } - - } - - }) - - var link = linkLayer.selectAll(".red-ui-flow-link").data( - activeLinks, - function(d) { - return d.source.id+":"+d.sourcePort+":"+d.target.id+":"+d.target.i; - } - ); - var linkEnter = link.enter().insert("g",".red-ui-flow-node").attr("class","red-ui-flow-link"); - - linkEnter.each(function(d,i) { - var l = d3.select(this); - var pathContents = document.createDocumentFragment(); - - d.added = true; - var pathBack = document.createElementNS("http://www.w3.org/2000/svg","path"); - pathBack.__data__ = d; - pathBack.setAttribute("class","red-ui-flow-link-background red-ui-flow-link-path"+(d.link?" red-ui-flow-link-link":"")); - this.__pathBack__ = pathBack; - pathContents.appendChild(pathBack); - d3.select(pathBack) - .on("mousedown",linkMouseDown) - .on("touchstart",linkTouchStart) - .on("mousemove", function(d) { - if (mouse_mode === RED.state.SLICING) { - - selectedLinks.add(d) - l.classed("red-ui-flow-link-splice",true) - redraw() - } else if (mouse_mode === RED.state.SLICING_JUNCTION && !d.link) { - if (!l.classed("red-ui-flow-link-splice")) { - // Find intersection point - var lineLength = pathLine.getTotalLength(); - var pos; - var delta = Infinity; - for (var i = 0; i < lineLength; i++) { - var linePos = pathLine.getPointAtLength(i); - var posDeltaX = Math.abs(linePos.x-d3.event.offsetX) - var posDeltaY = Math.abs(linePos.y-d3.event.offsetY) - var posDelta = posDeltaX*posDeltaX + posDeltaY*posDeltaY - if (posDelta < delta) { - pos = linePos - delta = posDelta - } - } - d._sliceLocation = pos - selectedLinks.add(d) - l.classed("red-ui-flow-link-splice",true) - redraw() - } - } - }) - - var pathOutline = document.createElementNS("http://www.w3.org/2000/svg","path"); - pathOutline.__data__ = d; - pathOutline.setAttribute("class","red-ui-flow-link-outline red-ui-flow-link-path"); - this.__pathOutline__ = pathOutline; - pathContents.appendChild(pathOutline); - - var pathLine = document.createElementNS("http://www.w3.org/2000/svg","path"); - pathLine.__data__ = d; - pathLine.setAttribute("class","red-ui-flow-link-line red-ui-flow-link-path"+ - (d.link?" red-ui-flow-link-link":(activeSubflow?" red-ui-flow-subflow-link":""))); - this.__pathLine__ = pathLine; - pathContents.appendChild(pathLine); - - l[0][0].appendChild(pathContents); - }); - - link.exit().remove(); - link.each(function(d) { - var link = d3.select(this); - if (d.added || d.selected || dirtyNodes[d.source.id] || dirtyNodes[d.target.id]) { - var numOutputs = d.source.outputs || 1; - var sourcePort = d.sourcePort || 0; - var y = -((numOutputs-1)/2)*13 +13*sourcePort; - d.x1 = d.source.x+(d.source.w/2||0); - d.y1 = d.source.y+y; - d.x2 = d.target.x-(d.target.w/2||0); - d.y2 = d.target.y; - - // return "M "+d.x1+" "+d.y1+ - // " C "+(d.x1+scale*node_width)+" "+(d.y1+scaleY*node_height)+" "+ - // (d.x2-scale*node_width)+" "+(d.y2-scaleY*node_height)+" "+ - // d.x2+" "+d.y2; - var path = generateLinkPath(d.x1,d.y1,d.x2,d.y2,1); - if (/NaN/.test(path)) { - path = "" - } - this.__pathBack__.setAttribute("d",path); - this.__pathOutline__.setAttribute("d",path); - this.__pathLine__.setAttribute("d",path); - this.__pathLine__.classList.toggle("red-ui-flow-node-disabled",!!(d.source.d || d.target.d)); - this.__pathLine__.classList.toggle("red-ui-flow-subflow-link", !d.link && activeSubflow); - } - - this.classList.toggle("red-ui-flow-link-selected", !!d.selected); - - var connectedToUnknown = !!(d.target.type == "unknown" || d.source.type == "unknown"); - this.classList.toggle("red-ui-flow-link-unknown",!!(d.target.type == "unknown" || d.source.type == "unknown")) - delete d.added; - }) - var offLinks = linkLayer.selectAll(".red-ui-flow-link-off-flow").data( - activeFlowLinks, - function(d) { - return d.node.id+":"+d.refresh - } - ); - - var offLinksEnter = offLinks.enter().insert("g",".red-ui-flow-node").attr("class","red-ui-flow-link-off-flow"); - offLinksEnter.each(function(d,i) { - var g = d3.select(this); - var s = 1; - var labelAnchor = "start"; - if (d.node.type === "link in") { - s = -1; - labelAnchor = "end"; - } - var stemLength = s*30; - var branchLength = s*20; - var l = g.append("svg:path").attr("class","red-ui-flow-link-link").attr("d","M 0 0 h "+stemLength); - var links = d.links; - var flows = Object.keys(links); - var tabOrder = RED.nodes.getWorkspaceOrder(); - flows.sort(function(A,B) { - return tabOrder.indexOf(A) - tabOrder.indexOf(B); - }); - var linkWidth = 10; - var h = node_height; - var y = -(flows.length-1)*h/2; - var linkGroups = g.selectAll(".red-ui-flow-link-group").data(flows); - var enterLinkGroups = linkGroups.enter().append("g").attr("class","red-ui-flow-link-group") - .on('mouseover', function() { if (mouse_mode !== 0) { return } d3.select(this).classed('red-ui-flow-link-group-active',true)}) - .on('mouseout', function() {if (mouse_mode !== 0) { return } d3.select(this).classed('red-ui-flow-link-group-active',false)}) - .on('mousedown', function() { d3.event.preventDefault(); d3.event.stopPropagation(); }) - .on('mouseup', function(f) { - if (mouse_mode !== 0) { - return - } - d3.event.stopPropagation(); - var targets = d.links[f]; - RED.workspaces.show(f); - targets.forEach(function(n) { - n.selected = true; - n.dirty = true; - movingSet.add(n); - if (targets.length === 1) { - RED.view.reveal(n.id); - } - }); - updateSelection(); - redraw(); - }); - enterLinkGroups.each(function(f) { - var linkG = d3.select(this); - linkG.append("svg:path") - .attr("class","red-ui-flow-link-link") - .attr("d", - "M "+stemLength+" 0 "+ - "C "+(stemLength+(1.7*branchLength))+" "+0+ - " "+(stemLength+(0.1*branchLength))+" "+y+" "+ - (stemLength+branchLength*1.5)+" "+y+" " - ); - linkG.append("svg:path") - .attr("class","red-ui-flow-link-port") - .attr("d", - "M "+(stemLength+branchLength*1.5+s*(linkWidth+7))+" "+(y-12)+" "+ - "h "+(-s*linkWidth)+" "+ - "a 3 3 45 0 "+(s===1?"0":"1")+" "+(s*-3)+" 3 "+ - "v 18 "+ - "a 3 3 45 0 "+(s===1?"0":"1")+" "+(s*3)+" 3 "+ - "h "+(s*linkWidth) - ); - linkG.append("svg:path") - .attr("class","red-ui-flow-link-port") - .attr("d", - "M "+(stemLength+branchLength*1.5+s*(linkWidth+10))+" "+(y-12)+" "+ - "h "+(s*(linkWidth*3))+" "+ - "M "+(stemLength+branchLength*1.5+s*(linkWidth+10))+" "+(y+12)+" "+ - "h "+(s*(linkWidth*3)) - ).style("stroke-dasharray","12 3 8 4 3"); - linkG.append("rect").attr("class","red-ui-flow-port red-ui-flow-link-port") - .attr("x",stemLength+branchLength*1.5-4+(s*4)) - .attr("y",y-4) - .attr("rx",2) - .attr("ry",2) - .attr("width",8) - .attr("height",8); - linkG.append("rect") - .attr("x",stemLength+branchLength*1.5-(s===-1?node_width:0)) - .attr("y",y-12) - .attr("width",node_width) - .attr("height",24) - .style("stroke","none") - .style("fill","transparent") - var tab = RED.nodes.workspace(f); - var label; - if (tab) { - label = tab.label || tab.id; - } - linkG.append("svg:text") - .attr("class","red-ui-flow-port-label") - .attr("x",stemLength+branchLength*1.5+(s*15)) - .attr("y",y+1) - .style("font-size","10px") - .style("text-anchor",labelAnchor) - .text(label); - - y += h; - }); - linkGroups.exit().remove(); - }); - offLinks.exit().remove(); - offLinks = linkLayer.selectAll(".red-ui-flow-link-off-flow"); - offLinks.each(function(d) { - var s = 1; - if (d.node.type === "link in") { - s = -1; - } - var link = d3.select(this); - link.attr("transform", function(d) { return "translate(" + (d.node.x+(s*d.node.w/2)) + "," + (d.node.y) + ")"; }); - - }) - - var group = groupLayer.selectAll(".red-ui-flow-group").data(activeGroups,function(d) { return d.id }); - group.exit().each(function(d,i) { - document.getElementById("group_select_"+d.id).remove() - }).remove(); - var groupEnter = group.enter().insert("svg:g").attr("class", "red-ui-flow-group") - var addedGroups = false; - groupEnter.each(function(d,i) { - addedGroups = true; - var g = d3.select(this); - g.attr("id",d.id); - - var groupBorderRadius = 4; - - var selectGroup = groupSelectLayer.append('g').attr("class", "red-ui-flow-group").attr("id","group_select_"+d.id); - selectGroup.append('rect').classed("red-ui-flow-group-outline-select",true) - .classed("red-ui-flow-group-outline-select-background",true) - .attr('rx',groupBorderRadius).attr('ry',groupBorderRadius) - .attr("x",-4) - .attr("y",-4); - - - selectGroup.append('rect').classed("red-ui-flow-group-outline-select",true) - .attr('rx',groupBorderRadius).attr('ry',groupBorderRadius) - .attr("x",-4) - .attr("y",-4) - selectGroup.on("mousedown", function() {groupMouseDown.call(g[0][0],d)}); - selectGroup.on("mouseup", function() {groupMouseUp.call(g[0][0],d)}); - selectGroup.on("touchstart", function() {groupMouseDown.call(g[0][0],d); d3.event.preventDefault();}); - selectGroup.on("touchend", function() {groupMouseUp.call(g[0][0],d); d3.event.preventDefault();}); - - g.append('rect').classed("red-ui-flow-group-outline",true).attr('rx',0.5).attr('ry',0.5); - - g.append('rect').classed("red-ui-flow-group-body",true) - .attr('rx',groupBorderRadius).attr('ry',groupBorderRadius).style({ - "fill":d.fill||"none", - "stroke": d.stroke||"none", - }) - g.on("mousedown",groupMouseDown).on("mouseup",groupMouseUp) - g.on("touchstart", function() {groupMouseDown.call(g[0][0],d); d3.event.preventDefault();}); - g.on("touchend", function() {groupMouseUp.call(g[0][0],d); d3.event.preventDefault();}); - - g.append('svg:text').attr("class","red-ui-flow-group-label"); - d.dirty = true; - }); - if (addedGroups) { - group.sort(function(a,b) { - if (a._root === b._root) { - return a._depth - b._depth; - } else { - return a._index - b._index; - } - }) - } - group[0].reverse(); - var groupOpCount=0; - group.each(function(d,i) { - groupOpCount++ - if (d.resize) { - d.minWidth = 0; - delete d.resize; - } - if (d.dirty || dirtyGroups[d.id]) { - var g = d3.select(this); - var recalculateLabelOffsets = false; - if (d.nodes.length > 0) { - // If the group was just moved, all of its contents was - // also moved - so no need to recalculate its bounding box - if (!d.groupMoved) { - var minX = Number.POSITIVE_INFINITY; - var minY = Number.POSITIVE_INFINITY; - var maxX = 0; - var maxY = 0; - var margin = 26; - d.nodes.forEach(function(n) { - groupOpCount++ - if (n.type !== "group") { - minX = Math.min(minX,n.x-n.w/2-margin-((n._def.button && n._def.align!=="right")?20:0)); - minY = Math.min(minY,n.y-n.h/2-margin); - maxX = Math.max(maxX,n.x+n.w/2+margin+((n._def.button && n._def.align=="right")?20:0)); - maxY = Math.max(maxY,n.y+n.h/2+margin); - } else { - minX = Math.min(minX,n.x-margin) - minY = Math.min(minY,n.y-margin) - maxX = Math.max(maxX,n.x+n.w+margin) - maxY = Math.max(maxY,n.y+n.h+margin) - } - }); - - d.x = minX; - d.y = minY; - d.w = maxX - minX; - d.h = maxY - minY; - recalculateLabelOffsets = true; - // if set explicitly to false, this group has just been - // imported so needed this initial resize calculation. - // Now that's done, delete the flag so the normal - // logic kicks in. - if (d.groupMoved === false) { - delete d.groupMoved; - } - } else { - delete d.groupMoved; - } - } else { - d.w = 40; - d.h = 40; - recalculateLabelOffsets = true; - } - if (recalculateLabelOffsets) { - if (!d.minWidth) { - if (d.style.label && d.name) { - var labelParts = getLabelParts(d.name||"","red-ui-flow-group-label"); - d.minWidth = labelParts.width + 8; - d.labels = labelParts.lines; - } else { - d.minWidth = 40; - d.labels = []; - } - } - d.w = Math.max(d.minWidth,d.w); - if (d.style.label && d.labels.length > 0) { - var labelPos = d.style["label-position"] || "nw"; - var h = (d.labels.length-1) * 16; - if (labelPos[0] === "s") { - h += 8; - } - d.h += h; - if (labelPos[0] === "n") { - if (d.nodes.length > 0) { - d.y -= h; - } - } - } - } - - g.attr("transform","translate("+d.x+","+d.y+")") - g.selectAll(".red-ui-flow-group-outline") - .attr("width",d.w) - .attr("height",d.h) - - - var selectGroup = document.getElementById("group_select_"+d.id); - selectGroup.setAttribute("transform","translate("+d.x+","+d.y+")"); - if (d.hovered) { - selectGroup.classList.add("red-ui-flow-group-hovered") - } else { - selectGroup.classList.remove("red-ui-flow-group-hovered") - } - var selectGroupRect = selectGroup.children[0]; - selectGroupRect.setAttribute("width",d.w+8) - selectGroupRect.setAttribute("height",d.h+8) - selectGroupRect.style.strokeOpacity = (d.active || d.selected || d.highlighted)?0.8:0; - selectGroupRect.style.strokeDasharray = (d.active)?"10 4":""; - selectGroupRect = selectGroup.children[1]; - selectGroupRect.setAttribute("width",d.w+8) - selectGroupRect.setAttribute("height",d.h+8) - selectGroupRect.style.strokeOpacity = (d.active || d.selected || d.highlighted)?0.8:0; - selectGroupRect.style.strokeDasharray = (d.active)?"10 4":""; - - if (d.highlighted) { - selectGroup.classList.add("red-ui-flow-node-highlighted"); - } else { - selectGroup.classList.remove("red-ui-flow-node-highlighted"); - } - - - g.selectAll(".red-ui-flow-group-body") - .attr("width",d.w) - .attr("height",d.h) - .style("stroke", d.style.stroke || "") - .style("stroke-opacity", d.style.hasOwnProperty('stroke-opacity') ? d.style['stroke-opacity'] : "") - .style("fill", d.style.fill || "") - .style("fill-opacity", d.style.hasOwnProperty('fill-opacity') ? d.style['fill-opacity'] : "") - - var label = g.selectAll(".red-ui-flow-group-label"); - label.classed("hide",!!!d.style.label) - if (d.style.label) { - var labelPos = d.style["label-position"] || "nw"; - var labelX = 0; - var labelY = 0; - - if (labelPos[0] === 'n') { - labelY = 0+15; // Allow for font-height - } else { - labelY = d.h - 5 -(d.labels.length -1) * 16; - } - if (labelPos[1] === 'w') { - labelX = 5; - labelAnchor = "start" - } else if (labelPos[1] === 'e') { - labelX = d.w-5; - labelAnchor = "end" - } else { - labelX = d.w/2; - labelAnchor = "middle" - } - if (d.style.hasOwnProperty('color')) { - label.style("fill",d.style.color) - } else { - label.style("fill",null) - } - label.attr("transform","translate("+labelX+","+labelY+")") - .attr("text-anchor",labelAnchor); - if (d.labels) { - var ypos = 0; - g.selectAll(".red-ui-flow-group-label-text").remove(); - d.labels.forEach(function (name) { - label.append("tspan") - .classed("red-ui-flow-group-label-text", true) - .text(name) - .attr("x", 0) - .attr("y", ypos); - ypos += 16; - }); - } else { - g.selectAll(".red-ui-flow-group-label-text").remove(); - } - } - - delete dirtyGroups[d.id]; - delete d.dirty; - } - }) - } else { - // JOINING - unselect any selected links - linkLayer.selectAll(".red-ui-flow-link-selected").data( - activeLinks, - function(d) { - return d.source.id+":"+d.sourcePort+":"+d.target.id+":"+d.target.i; - } - ).classed("red-ui-flow-link-selected", false); - } - RED.view.navigator.refresh(); - if (d3.event) { - d3.event.preventDefault(); - } - } - - function focusView() { - try { - // Workaround for browser unexpectedly scrolling iframe into full - // view - record the parent scroll position and restore it after - // setting the focus - var scrollX = window.parent.window.scrollX; - var scrollY = window.parent.window.scrollY; - chart.trigger("focus"); - window.parent.window.scrollTo(scrollX,scrollY); - } catch(err) { - // In case we're iframed into a page of a different origin, just focus - // the view following the inevitable DOMException - chart.trigger("focus"); - } - } - - - /** - * Imports a new collection of nodes from a JSON String. - * - * - all get new IDs assigned - * - all "selected" - * - attached to mouse for placing - "IMPORT_DRAGGING" - * @param {String/Array} newNodesObj nodes to import - * @param {Object} options options object - * - * Options: - * - addFlow - whether to import nodes to a new tab - * - touchImport - whether this is a touch import. If not, imported nodes are - * attachedto mouse for placing - "IMPORT_DRAGGING" state - * - generateIds - whether to automatically generate new ids for all imported nodes - * - generateDefaultNames - whether to automatically update any nodes with clashing - * default names - */ - function importNodes(newNodesObj,options) { - options = options || { - addFlow: false, - touchImport: false, - generateIds: false, - generateDefaultNames: false - } - var addNewFlow = options.addFlow - var touchImport = options.touchImport; - - if (mouse_mode === RED.state.SELECTING_NODE) { - return; - } - - var nodesToImport; - if (typeof newNodesObj === "string") { - if (newNodesObj === "") { - return; - } - try { - nodesToImport = JSON.parse(newNodesObj); - } catch(err) { - var e = new Error(RED._("clipboard.invalidFlow",{message:err.message})); - e.code = "NODE_RED"; - throw e; - } - } else { - nodesToImport = newNodesObj; - } - - if (!$.isArray(nodesToImport)) { - nodesToImport = [nodesToImport]; - } - if (options.generateDefaultNames) { - RED.actions.invoke("core:generate-node-names", nodesToImport, { - renameBlank: false, - renameClash: true, - generateHistory: false - }) - } - - try { - var activeSubflowChanged; - if (activeSubflow) { - activeSubflowChanged = activeSubflow.changed; - } - var result = RED.nodes.import(nodesToImport,{generateIds:options.generateIds, addFlow: addNewFlow, importMap: options.importMap}); - 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 (addNewFlow && new_default_workspace) { - RED.workspaces.show(new_default_workspace.id); - } - var new_ms = new_nodes.filter(function(n) { return n.hasOwnProperty("x") && n.hasOwnProperty("y") && n.z == RED.workspaces.active() }); - new_ms = new_ms.concat(new_groups.filter(function(g) { return g.z === RED.workspaces.active()})) - new_ms = new_ms.concat(new_junctions.filter(function(j) { return j.z === RED.workspaces.active()})) - var new_node_ids = new_nodes.map(function(n){ n.changed = true; return n.id; }); - - clearSelection(); - movingSet.clear(); - movingSet.add(new_ms); - - - // TODO: pick a more sensible root node - if (movingSet.length() > 0) { - if (mouse_position == null) { - mouse_position = [0,0]; - } - - var dx = mouse_position[0]; - var dy = mouse_position[1]; - if (movingSet.length() > 0) { - var root_node = movingSet.get(0).n; - dx = root_node.x; - dy = root_node.y; - } - - var minX = 0; - var minY = 0; - var i; - var node,group; - var l =movingSet.length(); - for (i=0;i 0) || (!node.n.hasOwnProperty("inputs") && node.n._def.inputs > 0)) && - ((node.n.hasOwnProperty("outputs") && node.n.outputs > 0) || (!node.n.hasOwnProperty("outputs") && node.n._def.outputs > 0)) - - - } - } - - } - - var historyEvent = { - t:"add", - nodes:new_node_ids, - links:new_links, - groups:new_groups, - junctions: new_junctions, - workspaces:new_workspaces, - subflows:new_subflows, - dirty:RED.nodes.dirty() - }; - if (movingSet.length() === 0) { - RED.nodes.dirty(true); - } - if (activeSubflow) { - var subflowRefresh = RED.subflow.refresh(true); - if (subflowRefresh) { - historyEvent.subflow = { - id:activeSubflow.id, - changed: activeSubflowChanged, - instances: subflowRefresh.instances - } - } - } - if (removedNodes) { - var replaceEvent = { - t: "replace", - config: removedNodes - } - historyEvent = { - t:"multi", - events: [ - replaceEvent, - historyEvent - ] - } - } - - RED.history.push(historyEvent); - - updateActiveNodes(); - redraw(); - - var counts = []; - var newNodeCount = 0; - var newConfigNodeCount = 0; - new_nodes.forEach(function(n) { - if (n.hasOwnProperty("x") && n.hasOwnProperty("y")) { - newNodeCount++; - } else { - newConfigNodeCount++; - } - }) - var newGroupCount = new_groups.length; - var newJunctionCount = new_junctions.length; - if (new_workspaces.length > 0) { - counts.push(RED._("clipboard.flow",{count:new_workspaces.length})); - } - if (newNodeCount > 0) { - counts.push(RED._("clipboard.node",{count:newNodeCount})); - } - if (newGroupCount > 0) { - counts.push(RED._("clipboard.group",{count:newGroupCount})); - } - if (newConfigNodeCount > 0) { - counts.push(RED._("clipboard.configNode",{count:newConfigNodeCount})); - } - if (new_subflows.length > 0) { - counts.push(RED._("clipboard.subflow",{count:new_subflows.length})); - } - if (removedNodes && removedNodes.length > 0) { - counts.push(RED._("clipboard.replacedNodes",{count:removedNodes.length})); - } - if (counts.length > 0) { - var countList = "
    • "+counts.join("
    • ")+"
    "; - RED.notify("

    "+RED._("clipboard.nodesImported")+"

    "+countList,{id:"clipboard"}); - } - - } - } catch(error) { - if (error.code === "import_conflict") { - // Pass this up for the called to resolve - throw error; - } else if (error.code != "NODE_RED") { - console.log(error.stack); - RED.notify(RED._("notification.error",{message:error.toString()}),"error"); - } else { - RED.notify(RED._("notification.error",{message:error.message}),"error"); - } - } - } - - function toggleShowGrid(state) { - if (state) { - gridLayer.style("visibility","visible"); - } else { - gridLayer.style("visibility","hidden"); - } - } - function toggleSnapGrid(state) { - snapGrid = state; - redraw(); - } - function toggleStatus(s) { - showStatus = s; - RED.nodes.eachNode(function(n) { n.dirtyStatus = true; n.dirty = true;}); - //TODO: subscribe/unsubscribe here - redraw(); - } - function setSelectedNodeState(isDisabled) { - if (mouse_mode === RED.state.SELECTING_NODE) { - return; - } - var workspaceSelection = RED.workspaces.selection(); - var changed = false; - if (workspaceSelection.length > 0) { - // TODO: toggle workspace state - } else if (movingSet.length() > 0) { - var historyEvents = []; - for (var i=0;i 0) { - RED.history.push({ - t:"multi", - events: historyEvents, - dirty:RED.nodes.dirty() - }) - RED.nodes.dirty(true) - } - } - RED.view.redraw(); - - } - function getSelection() { - var selection = {}; - - var allNodes = new Set(); - - if (movingSet.length() > 0) { - movingSet.forEach(function(n) { - if (n.n.type !== 'group') { - allNodes.add(n.n); - } - }); - } - var selectedGroups = activeGroups.filter(function(g) { return g.selected && !g.active }); - if (selectedGroups.length > 0) { - if (selectedGroups.length === 1 && selectedGroups[0].active) { - // Let nodes be nodes - } else { - selectedGroups.forEach(function(g) { - var groupNodes = RED.group.getNodes(g,true); - groupNodes.forEach(function(n) { - allNodes.delete(n); - }); - allNodes.add(g); - }); - } - } - if (allNodes.size > 0) { - selection.nodes = Array.from(allNodes); - } - if (selectedLinks.length() > 0) { - selection.links = selectedLinks.toArray(); - selection.link = selection.links[0]; - } - return selection; - } - - /** - * Create a node from a type string. - * **NOTE:** Can throw on error - use `try` `catch` block when calling - * @param {string} type The node type to create - * @param {number} [x] (optional) The horizontal position on the workspace - * @param {number} [y] (optional)The vertical on the workspace - * @param {string} [z] (optional) The flow tab this node will belong to. Defaults to active workspace. - * @returns An object containing the `node` and a `historyEvent` - * @private - */ - function createNode(type, x, y, z) { - var m = /^subflow:(.+)$/.exec(type); - var activeSubflow = z ? RED.nodes.subflow(z) : null; - if (activeSubflow && m) { - var subflowId = m[1]; - if (subflowId === activeSubflow.id) { - throw new Error(RED._("notification.error", { message: RED._("notification.errors.cannotAddSubflowToItself") })) - } - if (RED.nodes.subflowContains(m[1], activeSubflow.id)) { - throw new Error(RED._("notification.error", { message: RED._("notification.errors.cannotAddCircularReference") })) - } - } - - var nn = { id: RED.nodes.id(), z: z || RED.workspaces.active() }; - - nn.type = type; - nn._def = RED.nodes.getType(nn.type); - - if (!m) { - nn.inputs = nn._def.inputs || 0; - nn.outputs = nn._def.outputs; - - for (var d in nn._def.defaults) { - if (nn._def.defaults.hasOwnProperty(d)) { - if (nn._def.defaults[d].value !== undefined) { - nn[d] = JSON.parse(JSON.stringify(nn._def.defaults[d].value)); - } - } - } - - if (nn._def.onadd) { - try { - nn._def.onadd.call(nn); - } catch (err) { - console.log("Definition error: " + nn.type + ".onadd:", err); - } - } - } else { - var subflow = RED.nodes.subflow(m[1]); - nn.name = ""; - nn.inputs = subflow.in.length; - nn.outputs = subflow.out.length; - } - - nn.changed = true; - nn.moved = true; - - nn.w = RED.view.node_width; - nn.h = Math.max(RED.view.node_height, (nn.outputs || 0) * 15); - nn.resize = true; - if (x != null && typeof x == "number" && x >= 0) { - nn.x = x; - } - if (y != null && typeof y == "number" && y >= 0) { - nn.y = y; - } - var historyEvent = { - t: "add", - nodes: [nn.id], - dirty: RED.nodes.dirty() - } - if (activeSubflow) { - var subflowRefresh = RED.subflow.refresh(true); - if (subflowRefresh) { - historyEvent.subflow = { - id: activeSubflow.id, - changed: activeSubflow.changed, - instances: subflowRefresh.instances - } - } - } - return { - node: nn, - historyEvent: historyEvent - } - } - - function calculateNodeDimensions(node) { - var result = [node_width,node_height]; - try { - var isLink = (node.type === "link in" || node.type === "link out") - var hideLabel = node.hasOwnProperty('l')?!node.l : isLink; - var label = RED.utils.getNodeLabel(node, node.type); - var labelParts = getLabelParts(label, "red-ui-flow-node-label"); - if (hideLabel) { - result[1] = Math.max(node_height,(node.outputs || 0) * 15); - } else { - result[1] = Math.max(6+24*labelParts.lines.length,(node.outputs || 0) * 15, 30); - } - if (hideLabel) { - result[0] = node_height; - } else { - result[0] = Math.max(node_width,20*(Math.ceil((labelParts.width+50+(node._def.inputs>0?7:0))/20)) ); - } - }catch(err) { - console.log("Error",node); - } - return result; - } - - - function flashNode(n) { - let node = n; - if(typeof node === "string") { node = RED.nodes.node(n); } - if(!node) { return; } - - const flashingNode = flashingNodeId && RED.nodes.node(flashingNodeId); - if(flashingNode) { - //cancel current flashing node before flashing new node - clearInterval(flashingNode.__flashTimer); - delete flashingNode.__flashTimer; - flashingNode.dirty = true; - flashingNode.highlighted = false; - } - node.__flashTimer = setInterval(function(flashEndTime, n) { - n.dirty = true; - if (flashEndTime >= Date.now()) { - n.highlighted = !n.highlighted; - } else { - clearInterval(n.__flashTimer); - delete n.__flashTimer; - flashingNodeId = null; - n.highlighted = false; - } - RED.view.redraw(); - }, 100, Date.now() + 2200, node) - flashingNodeId = node.id; - node.highlighted = true; - RED.view.redraw(); - } - return { - init: init, - state:function(state) { - if (state == null) { - return mouse_mode - } else { - mouse_mode = state; - } - }, - - updateActive: updateActiveNodes, - redraw: function(updateActive, syncRedraw) { - if (updateActive) { - updateActiveNodes(); - updateSelection(); - } - if (syncRedraw) { - _redraw(); - } else { - redraw(); - } - }, - focus: focusView, - importNodes: importNodes, - calculateTextWidth: calculateTextWidth, - select: function(selection) { - if (typeof selection !== "undefined") { - clearSelection(); - if (typeof selection == "string") { - var selectedNode = RED.nodes.node(selection); - if (selectedNode) { - selectedNode.selected = true; - selectedNode.dirty = true; - movingSet.clear(); - movingSet.add(selectedNode); - } - } else if (selection) { - if (selection.nodes) { - updateActiveNodes(); - movingSet.clear(); - // TODO: this selection group span groups - // - if all in one group -> activate the group - // - if in multiple groups (or group/no-group) - // -> select the first 'set' of things in the same group/no-group - selection.nodes.forEach(function(n) { - if (n.type !== "group") { - n.selected = true; - n.dirty = true; - movingSet.add(n); - } else { - selectGroup(n,true); - } - }) - } - } - } - updateSelection(); - redraw(true); - }, - selection: getSelection, - clearSelection: clearSelection, - createNode: createNode, - /** default node width */ - get node_width() { - return node_width; - }, - /** default node height */ - get node_height() { - return node_height; - }, - /** snap to grid option state */ - get snapGrid() { - return snapGrid; - }, - /** gets the current scale factor */ - scale: function() { - return scaleFactor; - }, - getLinksAtPoint: function(x,y) { - // x,y must be in SVG co-ordinate space - // if they come from a node.x/y, they will need to be scaled using - // scaleFactor first. - var result = []; - var links = outer.selectAll(".red-ui-flow-link-background")[0]; - for (var i=0;i= bb.x && y >= bb.y && x <= bb.x+bb.width && y <= bb.y+bb.height) { - result.push(links[i]) - } - } - return result; - }, - getGroupAtPoint: getGroupAt, - getActiveGroup: function() { return activeGroup }, - reveal: function(id,triggerHighlight) { - if (RED.nodes.workspace(id) || RED.nodes.subflow(id)) { - RED.workspaces.show(id, null, null, true); - } else { - var node = RED.nodes.node(id) || RED.nodes.group(id); - if (node) { - if (node.z && (node.type === "group" || node._def.category !== 'config')) { - node.dirty = true; - RED.workspaces.show(node.z); - - var screenSize = [chart[0].clientWidth/scaleFactor,chart[0].clientHeight/scaleFactor]; - var scrollPos = [chart.scrollLeft()/scaleFactor,chart.scrollTop()/scaleFactor]; - var cx = node.x; - var cy = node.y; - if (node.type === "group") { - cx += node.w/2; - cy += node.h/2; - } - if (cx < scrollPos[0] || cy < scrollPos[1] || cx > screenSize[0]+scrollPos[0] || cy > screenSize[1]+scrollPos[1]) { - var deltaX = '-='+(((scrollPos[0] - cx) + screenSize[0]/2)*scaleFactor); - var deltaY = '-='+(((scrollPos[1] - cy) + screenSize[1]/2)*scaleFactor); - chart.animate({ - scrollLeft: deltaX, - scrollTop: deltaY - },200); - } - if (triggerHighlight !== false) { - flashNode(node); - } - } else if (node._def.category === 'config') { - RED.sidebar.config.show(id); - } - } - } - }, - gridSize: function(v) { - if (v === undefined) { - return gridSize; - } else { - gridSize = Math.max(5,v); - updateGrid(); - } - }, - getActiveNodes: function() { - return activeNodes; - }, - getSubflowPorts: function() { - var result = []; - if (activeSubflow) { - var subflowOutputs = nodeLayer.selectAll(".red-ui-flow-subflow-port-output").data(activeSubflow.out,function(d,i){ return d.id;}); - subflowOutputs.each(function(d,i) { result.push(d) }) - var subflowInputs = nodeLayer.selectAll(".red-ui-flow-subflow-port-input").data(activeSubflow.in,function(d,i){ return d.id;}); - subflowInputs.each(function(d,i) { result.push(d) }) - var subflowStatus = nodeLayer.selectAll(".red-ui-flow-subflow-port-status").data(activeSubflow.status?[activeSubflow.status]:[],function(d,i){ return d.id;}); - subflowStatus.each(function(d,i) { result.push(d) }) - } - return result; - }, - selectNodes: function(options) { - $("#red-ui-workspace-tabs-shade").show(); - $("#red-ui-palette-shade").show(); - $("#red-ui-sidebar-shade").show(); - $("#red-ui-header-shade").show(); - $("#red-ui-workspace").addClass("red-ui-workspace-select-mode"); - - mouse_mode = RED.state.SELECTING_NODE; - clearSelection(); - if (options.selected) { - options.selected.forEach(function(id) { - var n = RED.nodes.node(id); - if (n) { - n.selected = true; - n.dirty = true; - movingSet.add(n); - } - }) - } - redraw(); - selectNodesOptions = options||{}; - var closeNotification = function() { - clearSelection(); - $("#red-ui-workspace-tabs-shade").hide(); - $("#red-ui-palette-shade").hide(); - $("#red-ui-sidebar-shade").hide(); - $("#red-ui-header-shade").hide(); - $("#red-ui-workspace").removeClass("red-ui-workspace-select-mode"); - resetMouseVars(); - notification.close(); - } - selectNodesOptions.done = function(selection) { - closeNotification(); - if (selectNodesOptions.onselect) { - selectNodesOptions.onselect(selection); - } - } - var buttons = [{ - text: RED._("common.label.cancel"), - click: function(e) { - closeNotification(); - if (selectNodesOptions.oncancel) { - selectNodesOptions.oncancel(); - } - } - }]; - if (!selectNodesOptions.single) { - buttons.push({ - text: RED._("common.label.done"), - class: "primary", - click: function(e) { - var selection = movingSet.nodes() - selectNodesOptions.done(selection); - } - }); - } - var notification = RED.notify(selectNodesOptions.prompt || RED._("workspace.selectNodes"),{ - modal: false, - fixed: true, - type: "compact", - buttons: buttons - }) - }, - scroll: function(x,y) { - chart.scrollLeft(chart.scrollLeft()+x); - chart.scrollTop(chart.scrollTop()+y) - }, - clickNodeButton: function(n) { - if (n._def.button) { - nodeButtonClicked(n); - } - }, - clipboard: function() { - return clipboard - }, - redrawStatus: redrawStatus, - showQuickAddDialog:showQuickAddDialog, - calculateNodeDimensions: calculateNodeDimensions, - getElementPosition:getElementPosition, - showTooltip:showTooltip - }; -})(); From 7af3acde9ee0edf836422857aeea627c4f6073e0 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 27 Jun 2022 20:30:14 +0100 Subject: [PATCH 046/237] Ensure 'hidden flow' count doesn't include subflows Fixes #3707 --- .../editor-client/src/js/ui/workspaces.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js b/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js index 673607229..1f5cdf0f1 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js @@ -284,9 +284,22 @@ RED.workspaces = (function() { onselect: "core:show-last-hidden-flow" } ] - if (hideStack.length > 0) { + let hiddenFlows = new Set() + for (let i = 0; i < hideStack.length; i++) { + let ids = hideStack[i] + if (!Array.isArray(ids)) { + ids = [ids] + } + ids.forEach(id => { + if (RED.nodes.workspace(id)) { + hiddenFlows.add(id) + } + }) + } + const flowCount = hiddenFlows.size; + if (flowCount > 0) { menuItems.unshift({ - label: RED._("workspace.hiddenFlows",{count: hideStack.length}), + label: RED._("workspace.hiddenFlows",{count: flowCount}), onselect: "core:list-hidden-flows" }) } From b9649aed328275186b93b15245f592fc1fae6b63 Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Mon, 27 Jun 2022 16:59:56 -0400 Subject: [PATCH 047/237] Fix typo --- .../@node-red/editor-client/src/sass/tab-config.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss index c8e44e26e..a8e2e4971 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss @@ -44,7 +44,7 @@ ul.red-ui-sidebar-node-config-list { } &.highlighted { border-color: transparent; - outline: dashed var(--red-ui-node-selected-color 4px); + outline: dashed var(--red-ui-node-selected-color) 4px; } } .red-ui-palette-label { From f7e6d7d143228b17dafd73b61d17a595a07cd320 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Tue, 28 Jun 2022 09:16:30 +0900 Subject: [PATCH 048/237] fix type selector of subflow environment variable --- .../editor-client/src/js/ui/editors/envVarList.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js index 41a528e21..1094cc464 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js @@ -41,8 +41,14 @@ RED.editor.envVarList = (function() { style: "width:100%", class: "node-input-env-value", type: "text", - }).attr("autocomplete","disable").appendTo(envRow) - valueField.typedInput({default:'str',types:isTemplateNode?DEFAULT_ENV_TYPE_LIST:DEFAULT_ENV_TYPE_LIST_INC_CRED}); + }).attr("autocomplete","disable").appendTo(envRow); + var types = (opt.ui && opt.ui.opts && opt.ui.opts.types); + if (!types) { + types = isTemplateNode + ? DEFAULT_ENV_TYPE_LIST + : DEFAULT_ENV_TYPE_LIST_INC_CRED; + } + valueField.typedInput({default:'str',types:types}); valueField.typedInput('type', opt.type); if (opt.type === "cred") { if (opt.value) { From 53017986544b0f4ffe83ce7cb83d40df755d72e8 Mon Sep 17 00:00:00 2001 From: Dennis Neufeld Date: Tue, 28 Jun 2022 08:06:16 +0200 Subject: [PATCH 049/237] Update german translation based on PR review --- .../editor-client/locales/de/editor.json | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/de/editor.json b/packages/node_modules/@node-red/editor-client/locales/de/editor.json index f47ebf5cf..41fe1459a 100755 --- a/packages/node_modules/@node-red/editor-client/locales/de/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/de/editor.json @@ -96,7 +96,7 @@ }, "edit": "Bearbeiten", "settings": "Einstellungen", - "userSettings": "Benutzereinstellungen", + "userSettings": "Einstellungen", "nodes": "Nodes", "displayStatus": "Node-Status anzeigen", "displayConfig": "Konfigurations-Nodes", @@ -105,7 +105,7 @@ "search": "Flows durchsuchen", "searchInput": "Flows durchsuchen", "subflows": "Subflow", - "createSubflow": "Subflow erstellen", + "createSubflow": "Subflow", "selectionToSubflow": "Auswahl in Subflow umwandeln", "flows": "Flow", "add": "Hinzufügen", @@ -122,7 +122,7 @@ "projects": "Projekte", "projects-new": "Neu", "projects-open": "Öffnen", - "projects-settings": "Projekteinstellungen", + "projects-settings": "Einstellungen", "showNodeLabelDefault": "Zeige Namen von neu hinzugefügten Nodes", "codeEditor": "Code-Editor", "groups": "Gruppen", @@ -192,7 +192,7 @@ "lostConnectionTry": "Jetzt versuchen", "cannotAddSubflowToItself": "Subflow kann nicht zu sich selbst hinzugefügt werden", "cannotAddCircularReference": "Subflow kann nicht hinzugefügt werden, da ein zirkulärer Bezug erkannt wurde", - "unsupportedVersion": "

    Nicht unterstützte Version von Node.js erkannt.

    Es sollte ein Upgrade auf das neueste LTS-Release von Node.js durchgeführt werden.

    ", + "unsupportedVersion": "

    Nicht unterstützte Version von Node.js erkannt.

    Es muss ein Upgrade auf das neueste LTS-Release von Node.js durchgeführt werden.

    ", "failedToAppendNode": "

    Fehler beim Laden von '__module__'.

    __error__

    " }, "project": { @@ -237,12 +237,12 @@ "replacedNodes_plural": "__count__ Nodes ersetzt", "pasteNodes": "Flow-JSON einfügen oder", "selectFile": "Datei für Import auswählen", - "importNodes": "Importiere Nodes", - "exportNodes": "Exportiere Nodes", + "importNodes": "Import", + "exportNodes": "Export", "download": "Download", - "importUnrecognised": "Nicht erkannter Typ importiert:", + "importUnrecognised": "Nicht erkannten Typ importiert:", "importUnrecognised_plural": "Nicht erkannte Typen importiert:", - "importDuplicate": "Doppelte Node importiert:", + "importDuplicate": "Doppelten Node importiert:", "importDuplicate_plural": "Doppelte Nodes importiert:", "nodesExported": "Nodes in die Zwischenablage exportiert", "nodesImported": "Importiert:", @@ -543,8 +543,8 @@ "advanced": "Fortgeschritten" }, "actions": { - "collapse-all": "Alle Kategorien einklappen", - "expand-all": "Alle Kategorien ausklappen" + "collapse-all": "Kategorien einklappen", + "expand-all": "Kategorien ausklappen" }, "event": { "nodeAdded": "Node zur Palette hinzugefügt:", From b60fd36c6ef67e279beb60a3fa48781dfaf88084 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Tue, 28 Jun 2022 10:14:12 +0100 Subject: [PATCH 050/237] Fix CSV node to handle \n when outputting text fields and add tests --- .../@node-red/nodes/core/parsers/70-CSV.js | 7 ++++-- test/nodes/core/parsers/70-CSV_spec.js | 24 +++++++++---------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js index 184f40bdd..9c55fa2b6 100644 --- a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js +++ b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js @@ -89,6 +89,9 @@ module.exports = function(RED) { else if (msg.payload[s][t].toString().indexOf(node.sep) !== -1) { // add quotes if any "commas" msg.payload[s][t] = node.quo + msg.payload[s][t].toString() + node.quo; } + else if (msg.payload[s][t].toString().indexOf("\n") !== -1) { // add quotes if any "\n" + msg.payload[s][t] = node.quo + msg.payload[s][t].toString() + node.quo; + } } ou += msg.payload[s].join(node.sep) + node.ret; } @@ -112,7 +115,7 @@ module.exports = function(RED) { q = q.replace(/"/g, '""'); ou += node.quo + q + node.quo + node.sep; } - else if (q.indexOf(node.sep) !== -1) { // add quotes if any "commas" + else if (q.indexOf(node.sep) !== -1 || p.indexOf("\n") !== -1) { // add quotes if any "commas" or "\n" ou += node.quo + q + node.quo + node.sep; } else { ou += q + node.sep; } // otherwise just add @@ -134,7 +137,7 @@ module.exports = function(RED) { p = p.replace(/"/g, '""'); ou += node.quo + p + node.quo + node.sep; } - else if (p.indexOf(node.sep) !== -1) { // add quotes if any "commas" + else if (p.indexOf(node.sep) !== -1 || p.indexOf("\n") !== -1) { // add quotes if any "commas" or "\n" ou += node.quo + p + node.quo + node.sep; } else { ou += p + node.sep; } // otherwise just add diff --git a/test/nodes/core/parsers/70-CSV_spec.js b/test/nodes/core/parsers/70-CSV_spec.js index cb8d7ca09..93d59a171 100644 --- a/test/nodes/core/parsers/70-CSV_spec.js +++ b/test/nodes/core/parsers/70-CSV_spec.js @@ -693,19 +693,19 @@ describe('CSV node', function() { describe('json object to csv', function() { it('should convert a simple object back to a csv', function(done) { - var flow = [ { id:"n1", type:"csv", temp:"a,b,c,,e", wires:[["n2"]] }, + var flow = [ { id:"n1", type:"csv", temp:"a,b,c,,e,f", wires:[["n2"]] }, {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n2.on("input", function(msg) { try { - msg.should.have.property('payload', '4,foo,true,,0\n'); + msg.should.have.property('payload', '4,foo,true,,0,"Hello\nWorld"\n'); done(); } catch(e) { done(e); } }); - var testJson = { e:0, d:1, b:"foo", c:true, a:4 }; + var testJson = { e:0, d:1, b:"foo", c:true, a:4, f:"Hello\nWorld" }; n1.emit("input", {payload:testJson}); }); }); @@ -777,7 +777,7 @@ describe('CSV node', function() { } catch(e) { done(e); } }); - var testJson = [{ d: 1, b: 3, c: 2, a: 4 },{d:4,a:1,c:3,b:2}]; + var testJson = [{ d:1, b:3, c:2, a:4 },{d:4, a:1, c:3, b:2}]; n1.emit("input", {payload:testJson}); }); }); @@ -790,12 +790,12 @@ describe('CSV node', function() { var n2 = helper.getNode("n2"); n2.on("input", function(msg) { try { - msg.should.have.property('payload', 'a,b,c,d\n4,3,2,1\n1,2,3,4\n'); + msg.should.have.property('payload', 'a,b,c,d\n4,3,2,1\n1,2,3,"a\nb"\n'); done(); } catch(e) { done(e); } }); - var testJson = [{ d: 1, b: 3, c: 2, a: 4 },{d:4,a:1,c:3,b:2}]; + var testJson = [{ d:1, b:3, c:2, a:4 },{d:"a\nb", a:1, c:3, b:2}]; n1.emit("input", {payload:testJson}); }); }); @@ -826,12 +826,12 @@ describe('CSV node', function() { var n2 = helper.getNode("n2"); n2.on("input", function(msg) { try { - msg.should.have.property('payload', 'd,b,c,a\n1,3,2,4\n4,2,3,1\n'); + msg.should.have.property('payload', 'd,b,c,a\n1,3,2,4\n4,"f\ng",3,1\n'); done(); } catch(e) { done(e); } }); - var testJson = [{ d: 1, b: 3, c: 2, a: 4 },{d:4,a:1,c:3,b:2}]; + var testJson = [{ d: 1, b: 3, c: 2, a: 4 },{d:4,a:1,c:3,b:"f\ng"}]; n1.emit("input", {payload:testJson}); }); }); @@ -844,12 +844,12 @@ describe('CSV node', function() { var n2 = helper.getNode("n2"); n2.on("input", function(msg) { try { - msg.should.have.property('payload', ',0,1,foo,"ba""r","di,ng"\n'); + msg.should.have.property('payload', ',0,1,foo,"ba""r","di,ng","fa\nba"\n'); done(); } catch(e) { done(e); } }); - var testJson = ["",0,1,"foo",'ba"r','di,ng']; + var testJson = ["",0,1,"foo",'ba"r','di,ng',"fa\nba"]; n1.emit("input", {payload:testJson}); }); }); @@ -898,12 +898,12 @@ describe('CSV node', function() { var n2 = helper.getNode("n2"); n2.on("input", function(msg) { try { - msg.should.have.property('payload', 'col1,col2,col3,col4\r\nH1,H2,H3,H4\r\nA,B,,\r\nA,,C,\r\nA,,,D\r\n'); + msg.should.have.property('payload', 'col1,col2,col3,col4\r\nH1,H2,H3,H4\r\nA,B,,\r\nA,,C,\r\nA,,,"D\nE"\r\n'); done(); } catch(e) { done(e); } }); - var testJson = [{"col1":"H1","col2":"H2","col3":"H3","col4":"H4"},{"col1":"A","col2":"B"},{"col1":"A","col3":"C"},{"col1":"A","col4":"D"}]; + var testJson = [{"col1":"H1","col2":"H2","col3":"H3","col4":"H4"},{"col1":"A","col2":"B"},{"col1":"A","col3":"C"},{"col1":"A","col4":"D\nE"}]; n1.emit("input", {payload:testJson}); }); }); From a7e3548f22d2c64229363b3203d980606173b300 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 28 Jun 2022 11:54:51 +0100 Subject: [PATCH 051/237] List welcome tours in help sidebar under node-red section --- .../editor-client/locales/en-US/editor.json | 5 ++-- .../editor-client/src/js/ui/tab-help.js | 28 ++++++++++--------- .../editor-client/src/js/ui/tour/tourGuide.js | 9 ++---- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index e6fe4a4af..7ecd6f640 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -685,7 +685,8 @@ "showHelp": "Show help", "showInOutline": "Show in outline", "showTopics": "Show topics", - "noHelp": "No help topic selected" + "noHelp": "No help topic selected", + "changeLog": "Change Log" }, "config": { "name": "Configuration nodes", @@ -1159,7 +1160,7 @@ "takeATour": "Take a tour", "start": "Start", "next": "Next", - "tours": "Tours" + "welcomeTours": "Welcome Tours" }, "diagnostics": { "title": "System Info" diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js index 09a67e338..e5199b5bc 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js @@ -20,10 +20,8 @@ RED.sidebar.help = (function() { var helpSection; var panels; var panelRatio; - var helpTopics = []; var treeList; var tocPanel; - var helpIndex = {}; function resizeStack() { var h = $(content).parent().height() - toolbar.outerHeight(); @@ -192,7 +190,6 @@ RED.sidebar.help = (function() { } function refreshHelpIndex() { - helpTopics = []; var modules = RED.nodes.registry.getModuleList(); var moduleNames = Object.keys(modules); moduleNames.sort(); @@ -204,23 +201,28 @@ RED.sidebar.help = (function() { }; var tours = RED.tourGuide.list().map(function (item) { return { - icon: "fa fa-repeat", + icon: "fa fa-play-circle-o", label: item.label, tour: item.path, }; }); var helpData = [ { - id: 'changelog', - label: "Node-RED v"+RED.settings.version, - content: getChangelog - }, - nodeHelp, - { - id: "tours", - label: RED._("tourGuide.tours"), - children: tours + label: "Node-RED", + children: [ + { + id: 'changelog', + label: RED._("sidebar.help.changeLog"), + content: getChangelog + }, + { + label: RED._("tourGuide.welcomeTours"), + children: tours + } + + ] }, + nodeHelp ]; var subflows = RED.nodes.registry.getNodeTypes().filter(function(t) {return /subflow/.test(t)}); if (subflows.length > 0) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js index 49b960402..adf7f4212 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js @@ -437,7 +437,7 @@ RED.tourGuide = (function() { return [ { id: "2_3", - label: "3.0.0-beta.3 (latest)", + label: "3.0.0-beta.3", path: "./tours/welcome.js" }, { @@ -449,12 +449,7 @@ RED.tourGuide = (function() { id: "2_1", label: "2.1.0", path: "./tours/2.1/welcome.js" - }, - { - id: "first_flow", - label: "First flow", - path: "./tours/first-flow.js" - }, + } ]; } From 3578f1b254d38ff1a225757ccfbc809a9ead8e7f Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Tue, 28 Jun 2022 20:35:39 +0900 Subject: [PATCH 052/237] remove line break to prevent jshint error --- .../@node-red/editor-client/src/js/ui/editors/envVarList.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js index 1094cc464..209e953e0 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/envVarList.js @@ -44,9 +44,7 @@ RED.editor.envVarList = (function() { }).attr("autocomplete","disable").appendTo(envRow); var types = (opt.ui && opt.ui.opts && opt.ui.opts.types); if (!types) { - types = isTemplateNode - ? DEFAULT_ENV_TYPE_LIST - : DEFAULT_ENV_TYPE_LIST_INC_CRED; + types = isTemplateNode ? DEFAULT_ENV_TYPE_LIST : DEFAULT_ENV_TYPE_LIST_INC_CRED; } valueField.typedInput({default:'str',types:types}); valueField.typedInput('type', opt.type); From c120dffbf07fbc49b642d6d0cdf9ac10d2503945 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Tue, 28 Jun 2022 16:22:24 +0100 Subject: [PATCH 053/237] Add option flag `reimport` to `importNodes` fixes #3699 --- .../@node-red/editor-client/src/js/nodes.js | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 052fad558..45804c29e 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -1654,21 +1654,19 @@ RED.nodes = (function() { * Options: * - generateIds - whether to replace all node ids * - addFlow - whether to import nodes to a new tab - * - importToCurrent + * - reimport - if node has a .z property, dont overwrite it + * Only applicible when `generateIds` is false * - importMap - how to resolve any conflicts. * - id:import - import as-is * - id:copy - import with new id * - id:replace - import over the top of existing */ function importNodes(newNodesObj,options) { // createNewIds,createMissingWorkspace) { - options = options || { - generateIds: false, - addFlow: false, - } - options.importMap = options.importMap || {}; - - var createNewIds = options.generateIds; - var createMissingWorkspace = options.addFlow; + const defOpts = { generateIds: false, addFlow: false, reimport: false, importMap: {} } + options = Object.assign({}, defOpts, options) + const createNewIds = options.generateIds; + const reimport = (!createNewIds && !!options.reimport) + const createMissingWorkspace = options.addFlow; var i; var n; var newNodes; @@ -1969,7 +1967,8 @@ RED.nodes = (function() { } } } else { - if (n.z && !workspace_map[n.z] && !subflow_map[n.z]) { + const keepNodesCurrentZ = reimport && n.z && RED.workspaces.contains(n.z) + if (!keepNodesCurrentZ && n.z && !workspace_map[n.z] && !subflow_map[n.z]) { n.z = activeWorkspace; } } @@ -2070,7 +2069,8 @@ RED.nodes = (function() { node.id = getID(); } else { node.id = n.id; - if (node.z == null || (!workspace_map[node.z] && !subflow_map[node.z])) { + const keepNodesCurrentZ = reimport && node.z && RED.workspaces.contains(node.z) + if (!keepNodesCurrentZ && (node.z == null || (!workspace_map[node.z] && !subflow_map[node.z]))) { if (createMissingWorkspace) { if (missingWorkspace === null) { missingWorkspace = RED.workspaces.add(null,true); @@ -2769,7 +2769,7 @@ RED.nodes = (function() { // Force the redraw to be synchronous so the view updates // *now* and removes the unknown node RED.view.redraw(true, true); - var result = importNodes(reimportList,{generateIds:false}); + var result = importNodes(reimportList,{generateIds:false, reimport: true}); var newNodeMap = {}; result.nodes.forEach(function(n) { newNodeMap[n.id] = n; From 7b06d4273aaa4c4338e1a369ec4a4b93cb4cd7ba Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Tue, 28 Jun 2022 16:22:31 -0400 Subject: [PATCH 054/237] Use the correct variable for alpha values --- .../@node-red/editor-client/src/sass/colors.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/colors.scss b/packages/node_modules/@node-red/editor-client/src/sass/colors.scss index 66b3bd48e..b311b06b5 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/colors.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/colors.scss @@ -112,13 +112,13 @@ $tab-text-color-disabled-inactive: $secondary-text-color-disabled-inactive; $tab-badge-color: $tertiary-text-color; $tab-background: $secondary-background; $tab-background-active: $secondary-background; -$tab-background-active-alpha: rgba($secondary-background, 0.001); +$tab-background-active-alpha: rgba($tab-background-active, 0.001); $tab-background-selected: $secondary-background-selected; -$tab-background-selected-alpha: rgba($secondary-background-selected, 0.001); +$tab-background-selected-alpha: rgba($tab-background-selected, 0.001); $tab-background-inactive: $secondary-background-inactive; -$tab-background-inactive-alpha: rgba($secondary-background-inactive, 0.001); +$tab-background-inactive-alpha: rgba($tab-background-inactive, 0.001); $tab-background-hover: $secondary-background-hover; -$tab-background-hover-alpha: rgba($secondary-background-hover, 0.001); +$tab-background-hover-alpha: rgba($tab-background-hover, 0.001); $palette-header-background: $primary-background; $palette-header-color: $header-text-color; From f33848e16b6fa0ebaf0432e1dbd7fa3d1cded965 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 29 Jun 2022 10:27:44 +0100 Subject: [PATCH 055/237] Rework start/stop api to use runtime-event notification message --- .../@node-red/editor-api/lib/admin/flows.js | 2 +- .../editor-client/locales/en-US/editor.json | 10 +- .../@node-red/editor-client/src/js/nodes.js | 2 +- .../@node-red/editor-client/src/js/red.js | 7 +- .../@node-red/editor-client/src/js/runtime.js | 67 +++++-------- .../editor-client/src/js/ui/deploy.js | 23 ++--- .../@node-red/editor-client/src/js/ui/view.js | 2 +- .../@node-red/runtime/lib/api/flows.js | 31 +++--- .../@node-red/runtime/lib/flows/index.js | 96 ++++++++++--------- .../@node-red/runtime/lib/index.js | 6 +- .../runtime/locales/en-US/runtime.json | 1 + 11 files changed, 107 insertions(+), 140 deletions(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/flows.js b/packages/node_modules/@node-red/editor-api/lib/admin/flows.js index 2ad233f8f..4d8679aac 100644 --- a/packages/node_modules/@node-red/editor-api/lib/admin/flows.js +++ b/packages/node_modules/@node-red/editor-api/lib/admin/flows.js @@ -83,7 +83,7 @@ module.exports = { postState: function(req,res) { const opts = { user: req.user, - requestedState: req.body.state||"", + state: req.body.state || "", req: apiUtils.getRequestLogObject(req) } runtimeAPI.flows.setState(opts).then(function(result) { diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index b25885e21..ab2fba982 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -169,6 +169,10 @@ } }, "notification": { + "state": { + "flowsStopped": "Flows stopped", + "flowsStarted": "Flows started" + }, "warning": "Warning: __message__", "warnings": { "undeployedChanges": "node has undeployed changes", @@ -291,12 +295,6 @@ "stopstart":{ "status": { "state_changed": "Flows runtime has been changed to '__state__' state" - }, - "errors": { - "notAllowed": "Method not allowed", - "notAuthorized": "Not authorized", - "notFound": "Not found", - "noResponse": "No response from server" } }, "deploy": { diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 052fad558..b390dab53 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -14,7 +14,7 @@ * limitations under the License. **/ -/** +/** * An Interface to nodes and utility functions for creating/adding/deleting nodes and links * @namespace RED.nodes */ diff --git a/packages/node_modules/@node-red/editor-client/src/js/red.js b/packages/node_modules/@node-red/editor-client/src/js/red.js index 939c32f2e..55446418b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/red.js +++ b/packages/node_modules/@node-red/editor-client/src/js/red.js @@ -336,7 +336,6 @@ var RED = (function() { id: notificationId } if (notificationId === "runtime-state") { - RED.events.emit("runtime-state",msg); if (msg.error === "safe-mode") { options.buttons = [ { @@ -477,9 +476,9 @@ var RED = (function() { } else if (persistentNotifications.hasOwnProperty(notificationId)) { persistentNotifications[notificationId].close(); delete persistentNotifications[notificationId]; - if (notificationId === 'runtime-state') { - RED.events.emit("runtime-state",msg); - } + } + if (notificationId === 'runtime-state') { + RED.events.emit("runtime-state",msg); } }); RED.comms.subscribe("status/#",function(topic,msg) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/runtime.js b/packages/node_modules/@node-red/editor-client/src/js/runtime.js index 49960e382..eac985467 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/runtime.js +++ b/packages/node_modules/@node-red/editor-client/src/js/runtime.js @@ -1,53 +1,36 @@ RED.runtime = (function() { let state = "" - let settings = {ui: false, enabled: false}; - const STOPPED = "stopped" - const STARTED = "started" + let settings = { ui: false, enabled: false }; + const STOPPED = "stop" + const STARTED = "start" + const SAFE = "safe" + return { init: function() { // refresh the current runtime status from server settings = Object.assign({}, settings, RED.settings.runtimeState); - RED.runtime.requestState() - - // {id:"flows-run-state", started: false, state: "stopped", retain:true} - RED.comms.subscribe("notification/flows-run-state",function(topic,msg) { - RED.events.emit("flows-run-state",msg); - RED.runtime.updateState(msg.state); - }); - }, - get state() { - return state - }, - get started() { - return state === STARTED - }, - get states() { - return { STOPPED, STARTED } - }, - updateState: function(newState) { - state = newState; - // disable pointer events on node buttons (e.g. inject/debug nodes) - $(".red-ui-flow-node-button").toggleClass("red-ui-flow-node-button-stopped", state === STOPPED) - // show/hide Start/Stop based on current state - if(settings.enabled === true && settings.ui === true) { - RED.menu.setVisible("deploymenu-item-runtime-stop", state === STARTED) - RED.menu.setVisible("deploymenu-item-runtime-start", state === STOPPED) - } - }, - requestState: function(callback) { - $.ajax({ - headers: { - "Accept":"application/json" - }, - cache: false, - url: 'flows/state', - success: function(data) { - RED.runtime.updateState(data.state) - if(callback) { - callback(data.state) + RED.events.on("runtime-state", function(msg) { + if (msg.state) { + const currentState = state + state = msg.state + $(".red-ui-flow-node-button").toggleClass("red-ui-flow-node-button-stopped", state !== STARTED) + if(settings.enabled === true && settings.ui === true) { + RED.menu.setVisible("deploymenu-item-runtime-stop", state === STARTED) + RED.menu.setVisible("deploymenu-item-runtime-start", state !== STARTED) + } + // Do not notify the user about this event if: + // - This is the very first event we've received after loading the editor (currentState = '') + // - The state matches what we already thought was the case (state === currentState) + // - The event was triggered by a deploy (msg.deploy === true) + // - The event is a safe mode event - that gets notified separately + if (currentState !== '' && state !== currentState && !msg.deploy && state !== SAFE) { + RED.notify(RED._("notification.state.flows"+(state === STOPPED?'Stopped':'Started'), msg), "success") } } }); + }, + get started() { + return state === STARTED } } -})() \ No newline at end of file +})() diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js index 46408cb6c..809202c99 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js @@ -69,7 +69,7 @@ RED.deploy = (function() { {id:"deploymenu-item-node",toggle:"deploy-type",icon:"red/images/deploy-nodes.svg",label:RED._("deploy.modifiedNodes"),sublabel:RED._("deploy.modifiedNodesDesc"),onselect:function(s) { if(s){changeDeploymentType("nodes")}}}, null ] - if(RED.settings.runtimeState && RED.settings.runtimeState.ui === true) { + if (RED.settings.runtimeState && RED.settings.runtimeState.ui === true) { mainMenuItems.push({id:"deploymenu-item-runtime-start", icon:"red/images/start.svg",label:"Start"/*RED._("deploy.startFlows")*/,sublabel:"Start Flows" /*RED._("deploy.startFlowsDesc")*/,onselect:"core:start-flows", visible:false}) mainMenuItems.push({id:"deploymenu-item-runtime-stop", icon:"red/images/stop.svg",label:"Stop"/*RED._("deploy.startFlows")*/,sublabel:"Stop Flows" /*RED._("deploy.startFlowsDesc")*/,onselect:"core:stop-flows", visible:false}) } @@ -302,7 +302,6 @@ RED.deploy = (function() { deployInflight = true deployButtonSetBusy() shadeShow() - RED.runtime.updateState(state) $.ajax({ url:"flows/state", type: "POST", @@ -311,30 +310,23 @@ RED.deploy = (function() { if (deployWasEnabled) { $("#red-ui-header-button-deploy").removeClass("disabled") } - RED.runtime.updateState((data && data.state) || "unknown") - RED.notify(RED._("stopstart.status.state_changed", data), "success") }).fail(function(xhr,textStatus,err) { if (deployWasEnabled) { $("#red-ui-header-button-deploy").removeClass("disabled") } if (xhr.status === 401) { - RED.notify(RED._("notification.error", { message: RED._("stopstart.errors.notAuthorized") }), "error") - } else if (xhr.status === 404) { - RED.notify(RED._("notification.error", { message: RED._("stopstart.errors.notFound") }), "error") - } else if (xhr.status === 405) { - RED.notify(RED._("notification.error", { message: RED._("stopstart.errors.notAllowed") }), "error") + RED.notify(RED._("notification.error", { message: RED._("user.notAuthorized") }), "error") } else if (xhr.responseText) { const errorDetail = { message: err ? (err + "") : "" } try { errorDetail.message = JSON.parse(xhr.responseText).message - } finally { + } finally { errorDetail.message = errorDetail.message || xhr.responseText } RED.notify(RED._("notification.error", errorDetail), "error") } else { - RED.notify(RED._("notification.error", { message: RED._("stopstart.errors.noResponse") }), "error") + RED.notify(RED._("notification.error", { message: RED._("deploy.errors.noResponse") }), "error") } - RED.runtime.requestState() }).always(function() { const delta = Math.max(0, 300 - (Date.now() - startTime)) setTimeout(function () { @@ -514,13 +506,10 @@ RED.deploy = (function() { deployButtonSetBusy(); const data = { flows: nns }; - data.runtimeState = RED.runtime.state; - if (data.runtimeState === RED.runtime.states.STOPPED || force) { - data._rev = RED.nodes.version(); - } else { + if (!force) { data.rev = RED.nodes.version(); } - + deployInflight = true; shadeShow(); $.ajax({ diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index eca5c01ef..02b8df5d8 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -4877,7 +4877,7 @@ RED.view = (function() { if (d._def.button) { var buttonEnabled = isButtonEnabled(d); this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-disabled", !buttonEnabled); - if(RED.runtime && Object.hasOwn(RED.runtime,'started')) { + if (RED.runtime && Object.hasOwn(RED.runtime,'started')) { this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-stopped", !RED.runtime.started); } diff --git a/packages/node_modules/@node-red/runtime/lib/api/flows.js b/packages/node_modules/@node-red/runtime/lib/api/flows.js index 2e71cbdca..b91635201 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/flows.js +++ b/packages/node_modules/@node-red/runtime/lib/api/flows.js @@ -73,10 +73,6 @@ var api = module.exports = { if (deploymentType === 'reload') { apiPromise = runtime.flows.loadFlows(true); } else { - //ensure the runtime running/stopped state matches the deploying editor. If not, then copy the _rev number to flows.rev - if(flows.hasOwnProperty('_rev') && !flows.hasOwnProperty('rev') && (flows.runtimeState !== "stopped" || runtime.flows.started)) { - flows.rev = flows._rev - } if (flows.hasOwnProperty('rev')) { var currentVersion = runtime.flows.getFlows().rev; if (currentVersion !== flows.rev) { @@ -271,9 +267,7 @@ var api = module.exports = { getState: async function(opts) { runtime.log.audit({event: "flows.getState"}, opts.req); const result = { - state: runtime.flows.started ? "started" : "stopped", - started: !!runtime.flows.started, - rev: runtime.flows.getFlows().rev + state: runtime.flows.state() } return result; }, @@ -282,7 +276,7 @@ var api = module.exports = { * @param {Object} opts * @param {Object} opts.req - the request to log (optional) * @param {User} opts.user - the user calling the api - * @param {string} opts.requestedState - the requested state. Valid values are "start" and "stop". + * @param {string} opts.state - the requested state. Valid values are "start" and "stop". * @return {Promise} - the active flow configuration * @memberof @node-red/runtime_flows */ @@ -295,7 +289,7 @@ var api = module.exports = { err.code = err.code || errcode || "unexpected_error" runtime.log.audit({ event: "flows.setState", - state: opts.requestedState || "", + state: opts.state || "", error: errcode || "unexpected_error", message: err.code }, opts.req); @@ -304,21 +298,22 @@ var api = module.exports = { const getState = () => { return { - state: runtime.flows.started ? "started" : "stopped", - started: !!runtime.flows.started, - rev: runtime.flows.getFlows().rev, + state: runtime.flows.state() } } if(!runtime.settings.runtimeState || runtime.settings.runtimeState.enabled !== true) { throw (makeError("Method Not Allowed", "not_allowed", 405)) } - switch (opts.requestedState) { + switch (opts.state) { case "start": try { try { - runtime.settings.set('flowsRunStateRequested', opts.requestedState); - } catch(err) { } + runtime.settings.set('runtimeFlowState', opts.state); + } catch(err) {} + if (runtime.settings.safeMode) { + delete runtime.settings.safeMode + } await runtime.flows.startFlows("full") return getState() } catch (err) { @@ -327,15 +322,15 @@ var api = module.exports = { case "stop": try { try { - runtime.settings.set('flowsRunStateRequested', opts.requestedState); - } catch(err) { } + runtime.settings.set('runtimeFlowState', opts.state); + } catch(err) {} await runtime.flows.stopFlows("full") return getState() } catch (err) { throw (makeError(err, err.code, 500)) } default: - throw (makeError(`Cannot change flows runtime state to '${opts.requestedState}'}`, "invalid_run_state", 400)) + throw (makeError(`Cannot change flows runtime state to '${opts.state}'}`, "invalid_run_state", 400)) } }, } diff --git a/packages/node_modules/@node-red/runtime/lib/flows/index.js b/packages/node_modules/@node-red/runtime/lib/flows/index.js index b707b4aaf..b2de8ea73 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/index.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/index.js @@ -36,6 +36,8 @@ var activeFlowConfig = null; var activeFlows = {}; var started = false; +var state = 'stop' + var credentialsPendingReset = false; var activeNodesToFlow = {}; @@ -50,6 +52,7 @@ function init(runtime) { storage = runtime.storage; log = runtime.log; started = false; + state = 'stop'; if (!typeEventRegistered) { events.on('type-registered',function(type) { if (activeFlowConfig && activeFlowConfig.missingTypes.length > 0) { @@ -214,19 +217,26 @@ function setFlows(_config,_credentials,type,muteLog,forceStart,user) { // Flows are running (or should be) // Stop the active flows (according to deploy type and the diff) - return stop(type,diff,muteLog).then(() => { + return stop(type,diff,muteLog,true).then(() => { // Once stopped, allow context to remove anything no longer needed return context.clean(activeFlowConfig) }).then(() => { + if (!isLoad) { + log.info(log._("nodes.flows.updated-flows")); + } // Start the active flows - start(type,diff,muteLog).then(() => { + start(type,diff,muteLog,true).then(() => { events.emit("runtime-event",{id:"runtime-deploy",payload:{revision:flowRevision},retain: true}); }); // Return the new revision asynchronously to the actual start return flowRevision; }).catch(function(err) { }) } else { + if (!isLoad) { + log.info(log._("nodes.flows.updated-flows")); + } events.emit("runtime-event",{id:"runtime-deploy",payload:{revision:flowRevision},retain: true}); + return flowRevision; } }); } @@ -259,10 +269,10 @@ function getFlows() { return activeConfig; } -async function start(type,diff,muteLog) { - type = type||"full"; - let reallyStarted = started +async function start(type,diff,muteLog,isDeploy) { + type = type || "full"; started = true; + state = 'start' var i; // If there are missing types, report them, emit the necessary runtime event and return if (activeFlowConfig.missingTypes.length > 0) { @@ -284,7 +294,7 @@ async function start(type,diff,muteLog) { log.info(log._("nodes.flows.missing-type-install-2")); log.info(" "+settings.userDir); } - events.emit("runtime-event",{id:"runtime-state",payload:{error:"missing-types", type:"warning",text:"notification.warnings.missing-types",types:activeFlowConfig.missingTypes},retain:true}); + events.emit("runtime-event",{id:"runtime-state",payload:{state: 'stop', error:"missing-types", type:"warning",text:"notification.warnings.missing-types",types:activeFlowConfig.missingTypes},retain:true}); return; } @@ -298,7 +308,7 @@ async function start(type,diff,muteLog) { missingModules.push({module:err[i].module.module, error: err[i].error.code || err[i].error.toString()}) log.info(` - ${err[i].module.spec} [${err[i].error.code || "unknown_error"}]`); } - events.emit("runtime-event",{id:"runtime-state",payload:{error:"missing-modules", type:"warning",text:"notification.warnings.missing-modules",modules:missingModules},retain:true}); + events.emit("runtime-event",{id:"runtime-state",payload:{state: 'stop', error:"missing-modules", type:"warning",text:"notification.warnings.missing-modules",modules:missingModules},retain:true}); return; } @@ -307,10 +317,20 @@ async function start(type,diff,muteLog) { log.info("*****************************************************************") log.info(log._("nodes.flows.safe-mode")); log.info("*****************************************************************") - events.emit("runtime-event",{id:"runtime-state",payload:{error:"safe-mode", type:"warning",text:"notification.warnings.safe-mode"},retain:true}); + state = 'safe' + events.emit("runtime-event",{id:"runtime-state",payload:{state: 'safe', error:"safe-mode", type:"warning",text:"notification.warnings.safe-mode"},retain:true}); return; } + const runtimeState = settings.get('runtimeFlowState') || 'start' + if (runtimeState === 'stop') { + log.info(log._("nodes.flows.stopped-flows")); + events.emit("runtime-event",{id:"runtime-state",payload:{ state: 'stop', deploy:isDeploy },retain:true}); + state = 'stop' + started = false + return + } + if (!muteLog) { if (type !== "full") { log.info(log._("nodes.flows.starting-modified-"+type)); @@ -365,51 +385,31 @@ async function start(type,diff,muteLog) { } } } - // Having created or updated all flows, now start them. - let startFlows = true - try { - startFlows = settings.get('flowsRunStateRequested'); - } catch(err) { - } - startFlows = (startFlows !== "stop"); - - if (startFlows) { - for (id in activeFlows) { - if (activeFlows.hasOwnProperty(id)) { - try { - activeFlows[id].start(diff); - // Create a map of node id to flow id and also a subflowInstance lookup map - var activeNodes = activeFlows[id].getActiveNodes(); - Object.keys(activeNodes).forEach(function(nid) { - activeNodesToFlow[nid] = id; - }); - } catch(err) { - console.log(err.stack); - } + for (id in activeFlows) { + if (activeFlows.hasOwnProperty(id)) { + try { + activeFlows[id].start(diff); + // Create a map of node id to flow id and also a subflowInstance lookup map + var activeNodes = activeFlows[id].getActiveNodes(); + Object.keys(activeNodes).forEach(function(nid) { + activeNodesToFlow[nid] = id; + }); + } catch(err) { + console.log(err.stack); } } - reallyStarted = true; - events.emit("flows:started", {config: activeConfig, type: type, diff: diff}); - // Deprecated event - events.emit("nodes-started"); - } else { - started = false; } - - const state = { - started: reallyStarted, - state: reallyStarted ? "started" : "stopped", - } - events.emit("runtime-event",{id:"flows-run-state", payload: state, retain:true}); - + events.emit("flows:started", {config: activeConfig, type: type, diff: diff}); + // Deprecated event + events.emit("nodes-started"); if (credentialsPendingReset === true) { credentialsPendingReset = false; } else { - events.emit("runtime-event",{id:"runtime-state",retain:true}); + events.emit("runtime-event",{id:"runtime-state", payload:{ state: 'start', deploy:isDeploy}, retain:true}); } - if (!muteLog && reallyStarted) { + if (!muteLog) { if (type !== "full") { log.info(log._("nodes.flows.started-modified-"+type)); } else { @@ -419,7 +419,7 @@ async function start(type,diff,muteLog) { return; } -function stop(type,diff,muteLog) { +function stop(type,diff,muteLog,isDeploy) { if (!started) { return Promise.resolve(); } @@ -439,6 +439,7 @@ function stop(type,diff,muteLog) { } } started = false; + state = 'stop' var promises = []; var stopList; var removedList = diff.removed; @@ -490,7 +491,8 @@ function stop(type,diff,muteLog) { } } events.emit("flows:stopped",{config: activeConfig, type: type, diff: diff}); - events.emit("runtime-event",{id:"flows-run-state", payload: {started: false, state: "stopped"}, retain:true}); + + events.emit("runtime-event",{ id:"runtime-state", payload:{ state: 'stop', deploy:isDeploy }, retain:true }); // Deprecated event events.emit("nodes-stopped"); }); @@ -810,7 +812,7 @@ module.exports = { stopFlows: stop, get started() { return started }, - + state: () => { return state }, // handleError: handleError, // handleStatus: handleStatus, diff --git a/packages/node_modules/@node-red/runtime/lib/index.js b/packages/node_modules/@node-red/runtime/lib/index.js index 8e1d2b487..88b3b8293 100644 --- a/packages/node_modules/@node-red/runtime/lib/index.js +++ b/packages/node_modules/@node-red/runtime/lib/index.js @@ -215,7 +215,7 @@ function start() { } } return redNodes.loadContextsPlugin().then(function () { - redNodes.loadFlows().then(redNodes.startFlows).catch(function(err) {}); + redNodes.loadFlows().then(() => { redNodes.startFlows() }).catch(function(err) {}); started = true; }); }); @@ -399,12 +399,12 @@ module.exports = { * @memberof @node-red/runtime */ version: externalAPI.version, - + /** * @memberof @node-red/diagnostics */ diagnostics:externalAPI.diagnostics, - + storage: storage, events: events, hooks: hooks, diff --git a/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json b/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json index 3b46d0c9f..ecd010abb 100644 --- a/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json +++ b/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json @@ -122,6 +122,7 @@ "stopped-flows": "Stopped flows", "stopped": "Stopped", "stopping-error": "Error stopping node: __message__", + "updated-flows": "Updated flows", "added-flow": "Adding flow: __label__", "updated-flow": "Updated flow: __label__", "removed-flow": "Removed flow: __label__", From 7580f7491a02aba9c9f60076fdb68a811af93dd5 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 29 Jun 2022 10:45:06 +0100 Subject: [PATCH 056/237] Update flow state tests to match changes in api --- .../@node-red/runtime/lib/api/flows_spec.js | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/test/unit/@node-red/runtime/lib/api/flows_spec.js b/test/unit/@node-red/runtime/lib/api/flows_spec.js index 0f560e3f8..deed6a9b6 100644 --- a/test/unit/@node-red/runtime/lib/api/flows_spec.js +++ b/test/unit/@node-red/runtime/lib/api/flows_spec.js @@ -431,7 +431,7 @@ describe("runtime-api/flows", function() { var startFlows, stopFlows, runtime; beforeEach(function() { let flowsStarted = true; - let flowsState = "started"; + let flowsState = "start"; startFlows = sinon.spy(function(type) { if (type !== "full") { var err = new Error(); @@ -442,7 +442,7 @@ describe("runtime-api/flows", function() { return p; } flowsStarted = true; - flowsState = "started"; + flowsState = "start"; return Promise.resolve(); }); stopFlows = sinon.spy(function(type) { @@ -455,7 +455,7 @@ describe("runtime-api/flows", function() { return p; } flowsStarted = false; - flowsState = "stopped"; + flowsState = "stop"; return Promise.resolve(); }); runtime = { @@ -473,6 +473,7 @@ describe("runtime-api/flows", function() { startFlows, stopFlows, getFlows: function() { return {rev:"currentRev",flows:[]} }, + state: function() { return flowsState} } } }) @@ -480,29 +481,25 @@ describe("runtime-api/flows", function() { it("gets flows run state", async function() { flows.init(runtime); const state = await flows.getState({}) - state.should.have.property("started", true) - state.should.have.property("state", "started") + state.should.have.property("state", "start") }); it("permits getting flows run state when setting disabled", async function() { runtime.settings.runtimeState.enabled = false; flows.init(runtime); const state = await flows.getState({}) - state.should.have.property("started", true) - state.should.have.property("state", "started") + state.should.have.property("state", "start") }); it("start flows", async function() { flows.init(runtime); - const state = await flows.setState({requestedState:"start"}) - state.should.have.property("started", true) - state.should.have.property("state", "started") + const state = await flows.setState({state:"start"}) + state.should.have.property("state", "start") stopFlows.called.should.not.be.true(); startFlows.called.should.be.true(); }); it("stop flows", async function() { flows.init(runtime); - const state = await flows.setState({requestedState:"stop"}) - state.should.have.property("started", false) - state.should.have.property("state", "stopped") + const state = await flows.setState({state:"stop"}) + state.should.have.property("state", "stop") stopFlows.called.should.be.true(); startFlows.called.should.not.be.true(); }); @@ -511,7 +508,7 @@ describe("runtime-api/flows", function() { runtime.settings.runtimeState.enabled = false; flows.init(runtime); try { - await flows.setState({requestedState:"start"}) + await flows.setState({state:"start"}) } catch (error) { err = error } @@ -525,7 +522,7 @@ describe("runtime-api/flows", function() { runtime.settings.runtimeState.enabled = false; flows.init(runtime); try { - await flows.setState({requestedState:"stop"}) + await flows.setState({state:"stop"}) } catch (error) { err = error } @@ -538,7 +535,7 @@ describe("runtime-api/flows", function() { let err; flows.init(runtime); try { - await flows.setState({requestedState:"bad-state"}) + await flows.setState({state:"bad-state"}) } catch (error) { err = error } From b59a3b15f361283d97ddd14bba27fac3841aece1 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 29 Jun 2022 11:41:19 +0100 Subject: [PATCH 057/237] Fix flow unit tests --- .../@node-red/runtime/lib/flows/index.js | 5 +- .../@node-red/runtime/lib/flows/index_spec.js | 66 ++----------------- 2 files changed, 11 insertions(+), 60 deletions(-) diff --git a/packages/node_modules/@node-red/runtime/lib/flows/index.js b/packages/node_modules/@node-red/runtime/lib/flows/index.js index b2de8ea73..1b5476a3f 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/index.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/index.js @@ -322,7 +322,10 @@ async function start(type,diff,muteLog,isDeploy) { return; } - const runtimeState = settings.get('runtimeFlowState') || 'start' + let runtimeState + try { + runtimeState = settings.get('runtimeFlowState') || 'start' + } catch (err) {} if (runtimeState === 'stop') { log.info(log._("nodes.flows.stopped-flows")); events.emit("runtime-event",{id:"runtime-state",payload:{ state: 'stop', deploy:isDeploy },retain:true}); diff --git a/test/unit/@node-red/runtime/lib/flows/index_spec.js b/test/unit/@node-red/runtime/lib/flows/index_spec.js index 5e8f8a46e..1a0f2a73c 100644 --- a/test/unit/@node-red/runtime/lib/flows/index_spec.js +++ b/test/unit/@node-red/runtime/lib/flows/index_spec.js @@ -321,59 +321,6 @@ describe('flows/index', function() { return flows.startFlows(); }); }); - it('emits runtime-event "flows-run-state" "started"', async function () { - var originalConfig = [ - { id: "t1-1", x: 10, y: 10, z: "t1", type: "test", wires: [] }, - { id: "t1", type: "tab" } - ]; - storage.getFlows = function () { - return Promise.resolve({ flows: originalConfig }); - } - let receivedEvent = null; - const handleEvent = (data) => { - if(data && data.id === 'flows-run-state') { - receivedEvent = data; - } - } - events.on('runtime-event', handleEvent); - flows.init({ log: mockLog, settings: {}, storage: storage }); - await flows.load() - await flows.startFlows() - events.removeListener("runtime-event", handleEvent); - - //{id:"flows-run-state", payload: {started: true, state: "started"} - should(receivedEvent).not.be.null() - receivedEvent.should.have.property("id", "flows-run-state") - receivedEvent.should.have.property("payload", { started: true, state: "started" }) - receivedEvent.should.have.property("retain", true) - }); - it('emits runtime-event "flows-run-state" "stopped"', async function () { - const originalConfig = [ - { id: "t1-1", x: 10, y: 10, z: "t1", type: "test", wires: [] }, - { id: "t1", type: "tab" } - ]; - storage.getFlows = function () { - return Promise.resolve({ flows: originalConfig }); - } - let receivedEvent = null; - const handleEvent = (data) => { - if(data && data.id === 'flows-run-state') { - receivedEvent = data; - } - } - events.on('runtime-event', handleEvent); - flows.init({ log: mockLog, settings: {}, storage: storage }); - await flows.load() - await flows.startFlows() - await flows.stopFlows() - events.removeListener("runtime-event", handleEvent); - - //{id:"flows-run-state", payload: {started: true, state: "started"} - should(receivedEvent).not.be.null() - receivedEvent.should.have.property("id", "flows-run-state") - receivedEvent.should.have.property("payload", { started: false, state: "stopped" }) - receivedEvent.should.have.property("retain", true) - }); it('does not start if nodes missing', function(done) { var originalConfig = [ {id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]}, @@ -449,12 +396,13 @@ describe('flows/index', function() { try { flowCreate.called.should.be.false(); receivedEvent.should.have.property('id','runtime-state'); - receivedEvent.should.have.property('payload', - { error: 'missing-modules', - type: 'warning', - text: 'notification.warnings.missing-modules', - modules: [] } - ); + receivedEvent.should.have.property('payload', { + state: 'stop', + error: 'missing-modules', + type: 'warning', + text: 'notification.warnings.missing-modules', + modules: [] + }); done(); }catch(err) { From b7bdcc4e57d8f8eca619a63d527578768dcb1b03 Mon Sep 17 00:00:00 2001 From: Stephen McLaughlin <44235289+Steve-Mcl@users.noreply.github.com> Date: Wed, 29 Jun 2022 18:37:53 +0100 Subject: [PATCH 058/237] Update packages/node_modules/@node-red/editor-client/locales/en-US/editor.json Remove unused i18n entries (these are safe to remove - were added in the original PR) --- .../@node-red/editor-client/locales/en-US/editor.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index ab2fba982..d09e6e977 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -292,11 +292,6 @@ "copyMessageValue": "Value copied", "copyMessageValue_truncated": "Truncated value copied" }, - "stopstart":{ - "status": { - "state_changed": "Flows runtime has been changed to '__state__' state" - } - }, "deploy": { "deploy": "Deploy", "full": "Full", From 1839c1972e7d335e59e8ea8e13eebecb46599be5 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 29 Jun 2022 20:21:27 +0100 Subject: [PATCH 059/237] Update packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js Co-authored-by: Stephen McLaughlin <44235289+Steve-Mcl@users.noreply.github.com> --- .../@node-red/editor-client/src/js/ui/deploy.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js index 809202c99..f0d4754f4 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js @@ -102,8 +102,10 @@ RED.deploy = (function() { RED.actions.add("core:deploy-flows",save); if (type === "default") { - RED.actions.add("core:stop-flows",function() { stopStartFlows("stop") }); - RED.actions.add("core:start-flows",function() { stopStartFlows("start") }); + if (RED.settings.runtimeState && RED.settings.runtimeState.ui === true) { + RED.actions.add("core:stop-flows",function() { stopStartFlows("stop") }); + RED.actions.add("core:start-flows",function() { stopStartFlows("start") }); + } RED.actions.add("core:restart-flows",restart); RED.actions.add("core:set-deploy-type-to-full",function() { RED.menu.setSelected("deploymenu-item-full",true);}); RED.actions.add("core:set-deploy-type-to-modified-flows",function() { RED.menu.setSelected("deploymenu-item-flow",true); }); From f438cbf63317d1913a441ed273ac978159adc448 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 29 Jun 2022 21:04:28 +0100 Subject: [PATCH 060/237] Update for 3 beta 4 --- CHANGELOG.md | 35 +++++++++++++++++++ package.json | 4 +-- .../@node-red/editor-api/package.json | 6 ++-- .../@node-red/editor-client/package.json | 2 +- .../editor-client/src/js/ui/tour/tourGuide.js | 4 +-- .../editor-client/src/tours/welcome.js | 6 ++-- .../node_modules/@node-red/nodes/package.json | 4 +-- .../@node-red/registry/package.json | 4 +-- .../@node-red/runtime/package.json | 6 ++-- .../node_modules/@node-red/util/package.json | 2 +- packages/node_modules/node-red/package.json | 10 +++--- 11 files changed, 59 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87fcc30bd..ba74e4d5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,38 @@ +#### 3.0.0-beta.4: Beta Release + +Editor + + - Fix clicking on node in workspace to hide context menu (#3696) @knolleary + - Fix credential type input item of subflow template (#3703) @HiroyasuNishiyama + - Add option flag `reimport` to `importNodes` (#3718) @Steve-Mcl + - Update german translation (#3691) @Dennis14e + - List welcome tours in help sidebar (#3717) @knolleary + - Ensure 'hidden flow' count doesn't include subflows (#3715) @knolleary + - Fix Chinese translate (#3706) @hotlong + - Fix use default button for node icon (#3714) @kazuhitoyokoi + - Fix select boxes vertical alignment (#3698) @bonanitech + - Ensure workspace clean after undoing dropped node (#3708) @Steve-Mcl + - Use solid colour as config node icon background to hide text overflow (#3710) @Steve-Mcl + - Increase quick-add height to reveal 2 most recent entries (#3711) @Steve-Mcl + - Set default editor to monaco in absence of user preference (#3702) @knolleary + - Add Japanese translations for v3.0-beta.3 (#3688) @kazuhitoyokoi + - Fix handling of spacebar inside JSON visual editor (#3687) @knolleary + - Fix menu padding to handle both icons and submenus (#3686) @knolleary + - Include scroll offset when positioning quick-add dialog (#3685) @knolleary + +Runtime + + - Import default export if node is a transpiled es module (#3669) @dschmidt + - Leave Monaco theme commented out by default (#3704) @bonanitech + +Nodes + + - CSV: Fix CSV node to handle when outputting text fields (#3716) @dceejay + - Delay: Fix delay rate limit last timing when empty (#3709) @dceejay + - Link: Ensure link-call cache is updated when link-in is modified (#3695) @Steve-Mcl + - Join: Join node in reduce mode doesn't keep existing msg properties (#3670) @dceejay + - Template: Add support for evalulating {{env.}} within a template node (#3690) @cow0w + #### 3.0.0-beta.3: Beta Release Editor diff --git a/package.json b/package.json index 715d09a5b..1f5759634 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "3.0.0-beta.3", + "version": "3.0.0-beta.4", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", @@ -45,7 +45,7 @@ "express-session": "1.17.3", "form-data": "4.0.0", "fs-extra": "10.1.0", - "got": "11.8.3", + "got": "11.8.5", "hash-sum": "2.0.0", "hpagent": "1.0.0", "https-proxy-agent": "5.0.1", diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index 874fc2cec..777e15cc3 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-api", - "version": "3.0.0-beta.3", + "version": "3.0.0-beta.4", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/util": "3.0.0-beta.3", - "@node-red/editor-client": "3.0.0-beta.3", + "@node-red/util": "3.0.0-beta.4", + "@node-red/editor-client": "3.0.0-beta.4", "bcryptjs": "2.4.3", "body-parser": "1.20.0", "clone": "2.1.2", diff --git a/packages/node_modules/@node-red/editor-client/package.json b/packages/node_modules/@node-red/editor-client/package.json index 2a32cbded..1041c9745 100644 --- a/packages/node_modules/@node-red/editor-client/package.json +++ b/packages/node_modules/@node-red/editor-client/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-client", - "version": "3.0.0-beta.3", + "version": "3.0.0-beta.4", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js index adf7f4212..913582c10 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js @@ -436,8 +436,8 @@ RED.tourGuide = (function() { function listTour() { return [ { - id: "2_3", - label: "3.0.0-beta.3", + id: "3_0", + label: "3.0.0-beta.4", path: "./tours/welcome.js" }, { diff --git a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js index 98d83c7e5..4f80f93ef 100644 --- a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js +++ b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js @@ -1,14 +1,14 @@ export default { - version: "3.0.0-beta.3", + version: "3.0.0-beta.4", steps: [ { titleIcon: "fa fa-map-o", title: { - "en-US": "Welcome to Node-RED 3.0 Beta 3!", + "en-US": "Welcome to Node-RED 3.0 Beta 4!", "ja": "Node-RED 3.0 ベータ3へようこそ!" }, description: { - "en-US": "

    This is the final beta release of Node-RED 3.0.

    Let's take a moment to discover the new features in this release.

    ", + "en-US": "

    This is another final beta release of Node-RED 3.0.

    Let's take a moment to discover the new features in this release.

    ", "ja": "

    これはNode-RED 3.0の最後のベータリリースです。

    本リリースの新機能を見つけてみましょう。

    " } }, diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index 5df2fd503..68c7099bf 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/nodes", - "version": "3.0.0-beta.3", + "version": "3.0.0-beta.4", "license": "Apache-2.0", "repository": { "type": "git", @@ -28,7 +28,7 @@ "denque": "2.0.1", "form-data": "4.0.0", "fs-extra": "10.1.0", - "got": "11.8.3", + "got": "11.8.5", "hash-sum": "2.0.0", "hpagent": "1.0.0", "https-proxy-agent": "5.0.1", diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index 92dbb0d95..f4808c8ad 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/registry", - "version": "3.0.0-beta.3", + "version": "3.0.0-beta.4", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,7 +16,7 @@ } ], "dependencies": { - "@node-red/util": "3.0.0-beta.3", + "@node-red/util": "3.0.0-beta.4", "clone": "2.1.2", "fs-extra": "10.1.0", "semver": "7.3.7", diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json index dcf7bf47c..c6dc2130e 100644 --- a/packages/node_modules/@node-red/runtime/package.json +++ b/packages/node_modules/@node-red/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/runtime", - "version": "3.0.0-beta.3", + "version": "3.0.0-beta.4", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/registry": "3.0.0-beta.3", - "@node-red/util": "3.0.0-beta.3", + "@node-red/registry": "3.0.0-beta.4", + "@node-red/util": "3.0.0-beta.4", "async-mutex": "0.3.2", "clone": "2.1.2", "express": "4.18.1", diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index fec9690ba..6723b9b6c 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/util", - "version": "3.0.0-beta.3", + "version": "3.0.0-beta.4", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/node-red/package.json b/packages/node_modules/node-red/package.json index 36207c17e..3457e03af 100644 --- a/packages/node_modules/node-red/package.json +++ b/packages/node_modules/node-red/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "3.0.0-beta.3", + "version": "3.0.0-beta.4", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "flow" ], "dependencies": { - "@node-red/editor-api": "3.0.0-beta.3", - "@node-red/runtime": "3.0.0-beta.3", - "@node-red/util": "3.0.0-beta.3", - "@node-red/nodes": "3.0.0-beta.3", + "@node-red/editor-api": "3.0.0-beta.4", + "@node-red/runtime": "3.0.0-beta.4", + "@node-red/util": "3.0.0-beta.4", + "@node-red/nodes": "3.0.0-beta.4", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "express": "4.18.1", From 1d130a7857e77f92d7008b06dcf23acf022b4810 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 30 Jun 2022 10:14:51 +0100 Subject: [PATCH 061/237] Add missing entries from beta.4 changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba74e4d5f..fb99d8e0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Editor + - Move all colours to CSS variables (#3692) @bonanitech - Fix clicking on node in workspace to hide context menu (#3696) @knolleary - Fix credential type input item of subflow template (#3703) @HiroyasuNishiyama - Add option flag `reimport` to `importNodes` (#3718) @Steve-Mcl @@ -22,6 +23,7 @@ Editor Runtime + - Allow flows to be stopped and started manually (#3719) @knolleary - Import default export if node is a transpiled es module (#3669) @dschmidt - Leave Monaco theme commented out by default (#3704) @bonanitech From 1b6cbe5c233cc8404c1f63fa2c3dc53df0cd4cb3 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Thu, 30 Jun 2022 15:17:49 +0100 Subject: [PATCH 062/237] Ensure importMap is not null when using import UI fixes #3722 --- packages/node_modules/@node-red/editor-client/src/js/nodes.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index a898834b0..9da5aad05 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -1664,6 +1664,7 @@ RED.nodes = (function() { function importNodes(newNodesObj,options) { // createNewIds,createMissingWorkspace) { const defOpts = { generateIds: false, addFlow: false, reimport: false, importMap: {} } options = Object.assign({}, defOpts, options) + options.importMap = options.importMap || {} const createNewIds = options.generateIds; const reimport = (!createNewIds && !!options.reimport) const createMissingWorkspace = options.addFlow; From d4fdf6be670bf9a0f4ec98b251aaf080cac02e51 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Fri, 1 Jul 2022 01:09:06 +0900 Subject: [PATCH 063/237] Add Japanese translations for v3.0-beta.4 --- .../editor-client/locales/en-US/editor.json | 4 ++++ .../editor-client/locales/ja/editor.json | 20 +++++++++++++++---- .../editor-client/src/js/ui/deploy.js | 4 ++-- .../editor-client/src/tours/welcome.js | 4 ++-- .../@node-red/runtime/locales/ja/runtime.json | 1 + 5 files changed, 25 insertions(+), 8 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index 87725bd14..c8abada3e 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -300,6 +300,10 @@ "modifiedFlowsDesc": "Only deploys flows that contain changed nodes", "modifiedNodes": "Modified Nodes", "modifiedNodesDesc": "Only deploys nodes that have changed", + "startFlows": "Start", + "startFlowsDesc": "Start Flows", + "stopFlows": "Stop", + "stopFlowsDesc": "Stop Flows", "restartFlows": "Restart Flows", "restartFlowsDesc": "Restarts the current deployed flows", "successfulDeploy": "Successfully deployed", diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index 752bfc906..fb3458eed 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -169,6 +169,10 @@ } }, "notification": { + "state": { + "flowsStopped": "フローを停止しました", + "flowsStarted": "フローを開始しました" + }, "warning": "警告: __message__", "warnings": { "undeployedChanges": "ノードの変更をデプロイしていません", @@ -296,6 +300,10 @@ "modifiedFlowsDesc": "変更したノードを含むフローのみデプロイ", "modifiedNodes": "変更したノード", "modifiedNodesDesc": "変更したノードのみデプロイ", + "startFlows": "開始", + "startFlowsDesc": "フローを開始", + "stopFlows": "停止", + "stopFlowsDesc": "フローを停止", "restartFlows": "フローを再起動", "restartFlowsDesc": "デプロイされた現在のフローを再起動", "successfulDeploy": "デプロイが成功しました", @@ -685,7 +693,8 @@ "showHelp": "ヘルプを表示", "showInOutline": "アウトラインに表示", "showTopics": "トピックを表示", - "noHelp": "ヘルプのトピックが未選択" + "noHelp": "ヘルプのトピックが未選択", + "changeLog": "更新履歴" }, "config": { "name": "設定ノードを表示", @@ -845,7 +854,7 @@ "pushFailed": "リモートに新しいコミットがあるため、プッシュに失敗しました。プルしてマージしてから、再度プッシュしてください。", "push": "プッシュ", "pull": "プル", - "unablePull": "

    リモートの変更のプル失敗:ステージングされていないローカルの変更を上書きされてしまいます。

    変更をコミットしてから再度実行してください。

    ", + "unablePull": "

    リモートの変更のプル失敗:ステージングされていないローカルの変更が上書きされてしまいます。

    変更をコミットしてから再度実行してください。

    ", "showUnstagedChanges": "ステージングされていない変更を表示", "connectionFailed": "リモートリポジトリに接続できません: ", "pullUnrelatedHistory": "

    リモートに関連のないコミット履歴があります。

    本当に変更をプルしてローカルリポジトリに反映しますか?

    ", @@ -1159,7 +1168,8 @@ "takeATour": "ツアーを開始", "start": "開始", "next": "次へ", - "tours": "ツアー" + "welcomeTours": "ウェルカムツアー", + "tours": "ツアー" }, "diagnostics": { "title": "システム情報" @@ -1336,6 +1346,8 @@ "new-project": "新しいプロジェクト", "open-project": "プロジェクトを開く", "show-project-settings": "プロジェクト設定を表示", - "show-version-control-tab": "バージョンコントロールタブを表示" + "show-version-control-tab": "バージョンコントロールタブを表示", + "start-flows": "フローを開始", + "stop-flows": "フローを停止" } } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js index f0d4754f4..8a8df6837 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js @@ -70,8 +70,8 @@ RED.deploy = (function() { null ] if (RED.settings.runtimeState && RED.settings.runtimeState.ui === true) { - mainMenuItems.push({id:"deploymenu-item-runtime-start", icon:"red/images/start.svg",label:"Start"/*RED._("deploy.startFlows")*/,sublabel:"Start Flows" /*RED._("deploy.startFlowsDesc")*/,onselect:"core:start-flows", visible:false}) - mainMenuItems.push({id:"deploymenu-item-runtime-stop", icon:"red/images/stop.svg",label:"Stop"/*RED._("deploy.startFlows")*/,sublabel:"Stop Flows" /*RED._("deploy.startFlowsDesc")*/,onselect:"core:stop-flows", visible:false}) + mainMenuItems.push({id:"deploymenu-item-runtime-start", icon:"red/images/start.svg",label:RED._("deploy.startFlows"),sublabel:RED._("deploy.startFlowsDesc"),onselect:"core:start-flows", visible:false}) + mainMenuItems.push({id:"deploymenu-item-runtime-stop", icon:"red/images/stop.svg",label:RED._("deploy.stopFlows"),sublabel:RED._("deploy.stopFlowsDesc"),onselect:"core:stop-flows", visible:false}) } mainMenuItems.push({id:"deploymenu-item-reload", icon:"red/images/deploy-reload.svg",label:RED._("deploy.restartFlows"),sublabel:RED._("deploy.restartFlowsDesc"),onselect:"core:restart-flows"}) RED.menu.init({id:"red-ui-header-button-deploy-options", options: mainMenuItems }); diff --git a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js index 4f80f93ef..35a276606 100644 --- a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js +++ b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js @@ -5,11 +5,11 @@ export default { titleIcon: "fa fa-map-o", title: { "en-US": "Welcome to Node-RED 3.0 Beta 4!", - "ja": "Node-RED 3.0 ベータ3へようこそ!" + "ja": "Node-RED 3.0 ベータ4へようこそ!" }, description: { "en-US": "

    This is another final beta release of Node-RED 3.0.

    Let's take a moment to discover the new features in this release.

    ", - "ja": "

    これはNode-RED 3.0の最後のベータリリースです。

    本リリースの新機能を見つけてみましょう。

    " + "ja": "

    これはNode-RED 3.0のもう一つの最後のベータリリースです。

    本リリースの新機能を見つけてみましょう。

    " } }, { diff --git a/packages/node_modules/@node-red/runtime/locales/ja/runtime.json b/packages/node_modules/@node-red/runtime/locales/ja/runtime.json index df5f9fa08..bb5b0badc 100644 --- a/packages/node_modules/@node-red/runtime/locales/ja/runtime.json +++ b/packages/node_modules/@node-red/runtime/locales/ja/runtime.json @@ -122,6 +122,7 @@ "stopped-flows": "フローを停止しました", "stopped": "停止しました", "stopping-error": "ノードの停止に失敗しました: __message__", + "updated-flows": "フローを更新しました", "added-flow": "フローを追加します: __label__", "updated-flow": "フローを更新しました: __label__", "removed-flow": "フローを削除しました: __label__", From 6090a5b2740ab271a23d01568c304647de8bc7d3 Mon Sep 17 00:00:00 2001 From: ralphwetzel Date: Thu, 30 Jun 2022 21:51:51 +0200 Subject: [PATCH 064/237] Revert disable/enable logic proposed before --- .../editor-client/src/js/ui/editor.js | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js index 0ea00621e..fb4c200f5 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js @@ -1033,8 +1033,6 @@ RED.editor = (function() { }) }, open: function(tray, done) { - RED.keyboard.disable(); - if (editing_node.hasOwnProperty('outputs')) { editing_node.__outputs = editing_node.outputs; } @@ -1077,8 +1075,6 @@ RED.editor = (function() { }); }, close: function() { - RED.keyboard.enable(); - if (RED.view.state() != RED.state.IMPORT_DRAGGING) { RED.view.state(RED.state.DEFAULT); } @@ -1182,8 +1178,6 @@ RED.editor = (function() { }) }, open: function(tray, done) { - RED.keyboard.disable(); - var trayHeader = tray.find(".red-ui-tray-header"); var trayBody = tray.find('.red-ui-tray-body'); var trayFooter = tray.find(".red-ui-tray-footer"); @@ -1264,8 +1258,6 @@ RED.editor = (function() { }); }, close: function() { - RED.keyboard.enable(); - RED.workspaces.refresh(); activeEditPanes.forEach(function(pane) { @@ -1643,8 +1635,6 @@ RED.editor = (function() { }) }, open: function(tray, done) { - RED.keyboard.disable(); - var trayFooter = tray.find(".red-ui-tray-footer"); var trayFooterLeft = $("
    ", { class: "red-ui-tray-footer-left" @@ -1676,8 +1666,6 @@ RED.editor = (function() { }); }, close: function() { - RED.keyboard.enable(); - if (RED.view.state() != RED.state.IMPORT_DRAGGING) { RED.view.state(RED.state.DEFAULT); } @@ -1767,8 +1755,6 @@ RED.editor = (function() { }) }, open: function(tray, done) { - RED.keyboard.disable(); - var trayFooter = tray.find(".red-ui-tray-footer"); var trayFooterLeft = $("
    ", { class: "red-ui-tray-footer-left" @@ -1790,8 +1776,6 @@ RED.editor = (function() { }, close: function() { - RED.keyboard.enable(); - if (RED.view.state() != RED.state.IMPORT_DRAGGING) { RED.view.state(RED.state.DEFAULT); } @@ -1904,8 +1888,6 @@ RED.editor = (function() { }) }, open: function(tray, done) { - RED.keyboard.disable(); - var trayFooter = tray.find(".red-ui-tray-footer"); var trayBody = tray.find('.red-ui-tray-body'); trayBody.parent().css('overflow','hidden'); @@ -1933,8 +1915,6 @@ RED.editor = (function() { }); }, close: function() { - RED.keyboard.enable(); - if (RED.view.state() != RED.state.IMPORT_DRAGGING) { RED.view.state(RED.state.DEFAULT); } From 3726d6fe3b81f8cc11d68833630968501b37d109 Mon Sep 17 00:00:00 2001 From: ralphwetzel Date: Thu, 30 Jun 2022 21:52:16 +0200 Subject: [PATCH 065/237] Finetune keyboard shortcut management --- .../editor-client/src/js/ui/keyboard.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js b/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js index d9511269d..ae104637c 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js @@ -265,13 +265,18 @@ RED.keyboard = (function() { if (partialState) { partialState = null; return resolveKeyEvent(evt); - } else if (Object.keys(handler).length > 0) { - partialState = handler; - evt.preventDefault(); - return null; - } else { - return null; } + if (Object.keys(handler).length > 0) { + // check if there's a potential combined handler initiated by this keyCode + for (h in handler) { + if (matchHandlerToEvent(evt,handler[h]) > -1) { + partialState = handler; + evt.preventDefault(); + break; + } + } + } + return null; } else { var depth = Infinity; var matchedHandler; From d8a781632c0dc9874b09efa672dfcccccc097bd6 Mon Sep 17 00:00:00 2001 From: ralphwetzel Date: Thu, 30 Jun 2022 21:52:52 +0200 Subject: [PATCH 066/237] Re-order shortcut keymap --- .../editor-client/src/js/keymap.json | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/keymap.json b/packages/node_modules/@node-red/editor-client/src/js/keymap.json index 62a1f4414..1bedab6e8 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/keymap.json +++ b/packages/node_modules/@node-red/editor-client/src/js/keymap.json @@ -3,16 +3,12 @@ "alt-shift-p":"core:manage-palette", "ctrl-f": "core:search", "ctrl-shift-f": "core:list-flows", - "ctrl-+": "core:zoom-in", - "ctrl--": "core:zoom-out", - "ctrl-0": "core:zoom-reset", - "ctrl-enter": "core:confirm-edit-tray", - "ctrl-escape": "core:cancel-edit-tray", "ctrl-d": "core:deploy-flows", - "ctrl-g i": "core:show-info-tab", - "ctrl-g h": "core:show-help-tab", - "ctrl-g d": "core:show-debug-tab", "ctrl-g c": "core:show-config-tab", + "ctrl-g d": "core:show-debug-tab", + "ctrl-g h": "core:show-help-tab", + "ctrl-g i": "core:show-info-tab", + "ctrl-g v": "core:show-version-control-tab", "ctrl-g x": "core:show-context-tab", "ctrl-e": "core:show-export-dialog", "ctrl-i": "core:show-import-dialog", @@ -23,11 +19,8 @@ "ctrl-alt-r": "core:show-remote-diff", "ctrl-alt-n": "core:new-project", "ctrl-alt-o": "core:open-project", - "ctrl-g v": "core:show-version-control-tab", "ctrl-shift-l": "core:show-event-log", - "ctrl-shift-p":"core:show-action-list", - "alt-w": "core:hide-flow", - "alt-shift-w": "core:show-last-hidden-flow" + "ctrl-shift-p":"core:show-action-list" }, "red-ui-sidebar-node-config": { "backspace": "core:delete-config-selection", @@ -93,7 +86,16 @@ "alt-a v": "core:distribute-selection-vertically", "shift-f": "core:search-previous", "f": "core:search-next", - "alt-l l": "core:split-wire-with-link-nodes" + "alt-l l": "core:split-wire-with-link-nodes", + "alt-w": "core:hide-flow", + "alt-shift-w": "core:show-last-hidden-flow", + "ctrl-+": "core:zoom-in", + "ctrl--": "core:zoom-out", + "ctrl-0": "core:zoom-reset" + }, + "red-ui-editor-stack": { + "ctrl-enter": "core:confirm-edit-tray", + "ctrl-escape": "core:cancel-edit-tray" } } From 4d42f8ec589114d1ce3fd7881e3251faa3985245 Mon Sep 17 00:00:00 2001 From: ralphwetzel Date: Thu, 30 Jun 2022 22:08:58 +0200 Subject: [PATCH 067/237] Global variable definition in for loop: Fixed --- .../node_modules/@node-red/editor-client/src/js/ui/keyboard.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js b/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js index ae104637c..40e11aa72 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js @@ -268,7 +268,7 @@ RED.keyboard = (function() { } if (Object.keys(handler).length > 0) { // check if there's a potential combined handler initiated by this keyCode - for (h in handler) { + for (let h in handler) { if (matchHandlerToEvent(evt,handler[h]) > -1) { partialState = handler; evt.preventDefault(); From 0a5d7c21003f325ff88e252b9e32b34090640659 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 1 Jul 2022 10:31:50 +0100 Subject: [PATCH 068/237] Update add-junction menu to work in more cases --- .../editor-client/src/js/ui/contextMenu.js | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js index 0b388aff3..a8b787ba5 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js @@ -43,7 +43,16 @@ RED.contextMenu = (function() { const isGroup = hasSelection && selection.nodes.length === 1 && selection.nodes[0].type === 'group' const canRemoveFromGroup = hasSelection && !!selection.nodes[0].g + const offset = $("#red-ui-workspace-chart").offset() + let addX = options.x - offset.left + $("#red-ui-workspace-chart").scrollLeft() + let addY = options.y - offset.top + $("#red-ui-workspace-chart").scrollTop() + + if (RED.view.snapGrid) { + const gridSize = RED.view.gridSize() + addX = gridSize*Math.floor(addX/gridSize) + addY = gridSize*Math.floor(addY/gridSize) + } const menuItems = [ { onselect: 'core:show-action-list', onpostselect: function() {} }, @@ -54,17 +63,42 @@ RED.contextMenu = (function() { label: RED._("contextMenu.node"), onselect: function() { RED.view.showQuickAddDialog({ - position: [ options.x - offset.left, options.y - offset.top ], + position: [ addX, addY ], touchTrigger: true, splice: isSingleLink?selection.links[0]:undefined, // spliceMultiple: isMultipleLinks }) } }, - { + ( hasSelection || hasLinks ) ? { label: RED._("contextMenu.junction"), onselect: 'core:split-wires-with-junctions', - disabled: hasSelection || !hasLinks + disabled: !hasLinks + } : { + label: RED._("contextMenu.junction"), + onselect: function() { + const nn = { + _def: {defaults:{}}, + type: 'junction', + z: RED.workspaces.active(), + id: RED.nodes.id(), + x: addX, + y: addY, + w: 0, h: 0, + outputs: 1, + inputs: 1, + dirty: true + } + const historyEvent = { + dirty: RED.nodes.dirty(), + t:'add', + junctions:[nn] + } + RED.nodes.addJunction(nn); + RED.history.push(historyEvent); + RED.nodes.dirty(true); + RED.view.redraw(true) + } }, { label: RED._("contextMenu.linkNodes"), @@ -118,7 +152,6 @@ RED.contextMenu = (function() { } } - const offset = $("#red-ui-workspace-chart").offset() menu = RED.menu.init({ direction: 'right', onpreselect: function() { From 9ac83cf62e66db3cb4c66aae2370f62d4d0095f5 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 1 Jul 2022 17:34:16 +0100 Subject: [PATCH 069/237] Do not remove unknown credentials of Subflow Modules Fixes #3641 --- .../@node-red/runtime/lib/flows/Subflow.js | 1 - .../@node-red/runtime/lib/nodes/credentials.js | 13 ++++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js b/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js index 2e2beed74..824b88a28 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js @@ -390,7 +390,6 @@ class Subflow extends Flow { } name = newName; } - var parent = this.parent; if (parent) { diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js b/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js index 0432e01fb..5a72ab7fc 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js @@ -373,11 +373,14 @@ var api = module.exports = { } } - for (cred in savedCredentials) { - if (savedCredentials.hasOwnProperty(cred)) { - if (!newCreds.hasOwnProperty(cred)) { - delete savedCredentials[cred]; - dirty = true; + if (/^subflow(:|$)/.test(nodeType)) { + for (cred in savedCredentials) { + if (savedCredentials.hasOwnProperty(cred)) { + if (!newCreds.hasOwnProperty(cred)) { + console.log(` + ${cred} deleting for some reason`) + delete savedCredentials[cred]; + dirty = true; + } } } } From 829ccc3466ba6da0a927fbd8f64ad593a3c49a29 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 1 Jul 2022 17:48:14 +0100 Subject: [PATCH 070/237] Do not generate new node-ids when pasting a cut flow Fixes #3629 --- .../@node-red/editor-client/src/js/ui/view.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 02b8df5d8..d24445deb 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -95,6 +95,7 @@ RED.view = (function() { let flashingNodeId; var clipboard = ""; + let clipboardSource // Note: these are the permitted status colour aliases. The actual RGB values // are set in the CSS - flow.scss/colors.scss @@ -628,8 +629,8 @@ RED.view = (function() { }); RED.actions.add("core:copy-selection-to-internal-clipboard",copySelection); - RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection();deleteSelection();}); - RED.actions.add("core:paste-from-internal-clipboard",function(){importNodes(clipboard,{generateIds: true, generateDefaultNames: true});}); + RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection(true);deleteSelection();}); + RED.actions.add("core:paste-from-internal-clipboard",function(){importNodes(clipboard,{generateIds: clipboardSource === 'copy', generateDefaultNames: clipboardSource === 'copy'});}); RED.actions.add("core:detach-selected-nodes", function() { detachSelectedNodes() }) @@ -2703,7 +2704,7 @@ RED.view = (function() { } } - function copySelection() { + function copySelection(isCut) { if (mouse_mode === RED.state.SELECTING_NODE) { return; } @@ -2767,6 +2768,7 @@ RED.view = (function() { } } clipboard = JSON.stringify(nns); + clipboardSource = isCut ? 'cut' : 'copy' RED.menu.setDisabled("menu-item-edit-paste", false); if (nodeCount > 0) { RED.notify(RED._("clipboard.nodeCopied",{count:nodeCount}),{id:"clipboard"}); @@ -4086,7 +4088,7 @@ RED.view = (function() { var mdn = mousedown_node; var options = []; options.push({name:"delete",disabled:(movingSet.length()===0 && selectedLinks.length() === 0),onselect:function() {deleteSelection();}}); - options.push({name:"cut",disabled:(movingSet.length()===0),onselect:function() {copySelection();deleteSelection();}}); + options.push({name:"cut",disabled:(movingSet.length()===0),onselect:function() {copySelection(true);deleteSelection();}}); options.push({name:"copy",disabled:(movingSet.length()===0),onselect:function() {copySelection();}}); options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard, {generateIds: true, touchImport: true});}}); options.push({name:"edit",disabled:(movingSet.length() != 1),onselect:function() { RED.editor.edit(mdn);}}); From 0fdcbb46115d283289a8a6041c43ce211a887351 Mon Sep 17 00:00:00 2001 From: ralphwetzel Date: Sat, 2 Jul 2022 09:52:14 +0200 Subject: [PATCH 071/237] Fix label overflow @ config-node palette --- .../@node-red/editor-client/src/sass/tab-config.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss index 3223420f9..d6bde7db5 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss @@ -54,6 +54,9 @@ ul.red-ui-sidebar-node-config-list { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + &:not(:last-child) { + width: calc(100% - 38px); + } } .red-ui-palette-icon-container { font-size: 12px; From 02308f9e2fce251ab8a40adc8a68566cc035e349 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Sun, 3 Jul 2022 20:39:54 +0900 Subject: [PATCH 072/237] prevennt node from moving out of workspace --- .../@node-red/editor-client/src/js/ui/view-tools.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js index a781c44fc..44da78fec 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js @@ -105,6 +105,8 @@ RED.view.tools = (function() { $(document).one('keyup',endKeyboardMove); endMoveSet = true; } + var space_width = 5000; + var space_height = 5000; var minX = 0; var minY = 0; var node; @@ -120,6 +122,12 @@ RED.view.tools = (function() { node.n.dirty = true; node.n.x += dx; node.n.y += dy; + if ((node.n.x +node.n.w/2) >= space_width) { + node.n.x = space_width -node.n.w/2; + } + if ((node.n.y +node.n.h/2) >= space_height) { + node.n.y = space_height -node.n.h/2; + } node.n.dirty = true; if (node.n.type === "group") { RED.group.markDirty(node.n); @@ -130,6 +138,7 @@ RED.view.tools = (function() { minY = Math.min(node.n.y-node.n.h/2-5,minY); } } + console.log("; mS:", dx, dy, minX, minY); if (minX !== 0 || minY !== 0) { for (var n = 0; n Date: Sun, 3 Jul 2022 20:22:19 +0100 Subject: [PATCH 073/237] Fix defaulting to monaco if settings does not contain codeEditor --- .../node_modules/@node-red/editor-api/lib/editor/theme.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/theme.js b/packages/node_modules/@node-red/editor-api/lib/editor/theme.js index 5a8dacde4..88b3eeb62 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/theme.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/theme.js @@ -101,7 +101,10 @@ module.exports = { } themeSettings = null; theme = settings.editorTheme || {}; - themeContext.asset.vendorMonaco = ((theme.codeEditor || {}).lib === "monaco") ? "vendor/monaco/monaco-bootstrap.js" : ""; + themeContext.asset.vendorMonaco = "vendor/monaco/monaco-bootstrap.js" + if (theme.codeEditor && theme.codeEditor.lib === 'ace') { + themeContext.asset.vendorMonaco = '' + } activeTheme = theme.theme; }, From ca20f41d0ef5cd14974837d8b7fff40a6daf66bb Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Sun, 3 Jul 2022 20:37:55 +0100 Subject: [PATCH 074/237] Update theme tests to ensure monaco is loaded --- test/unit/@node-red/editor-api/lib/editor/theme_spec.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/unit/@node-red/editor-api/lib/editor/theme_spec.js b/test/unit/@node-red/editor-api/lib/editor/theme_spec.js index 900be126f..fd1b42d61 100644 --- a/test/unit/@node-red/editor-api/lib/editor/theme_spec.js +++ b/test/unit/@node-red/editor-api/lib/editor/theme_spec.js @@ -51,7 +51,7 @@ describe("api/editor/theme", function () { context.should.have.a.property("asset"); context.asset.should.have.a.property("red", "red/red.min.js"); context.asset.should.have.a.property("main", "red/main.min.js"); - context.asset.should.have.a.property("vendorMonaco", ""); + context.asset.should.have.a.property("vendorMonaco", "vendor/monaco/monaco-bootstrap.js"); should.not.exist(theme.settings()); }); @@ -69,16 +69,16 @@ describe("api/editor/theme", function () { } }); - it("Adds monaco bootstrap when enabled", async function () { + it("Does not add monaco bootstrap when ace selected", async function () { theme.init({ editorTheme: { codeEditor: { - lib: 'monaco' + lib: 'ace' } } }); var context = await theme.context(); - context.asset.should.have.a.property("vendorMonaco", "vendor/monaco/monaco-bootstrap.js"); + context.asset.should.have.a.property("vendorMonaco", ""); }); it("picks up custom theme", async function () { From 67f45532139a72e64f9ab8ba1b7a0eae8c776235 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Mon, 4 Jul 2022 10:45:20 +0900 Subject: [PATCH 075/237] fix handling of global debug message --- .../node_modules/@node-red/nodes/core/common/21-debug.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/node_modules/@node-red/nodes/core/common/21-debug.html b/packages/node_modules/@node-red/nodes/core/common/21-debug.html index f861f518b..a85a4892b 100644 --- a/packages/node_modules/@node-red/nodes/core/common/21-debug.html +++ b/packages/node_modules/@node-red/nodes/core/common/21-debug.html @@ -252,6 +252,9 @@ if (pathParts.length === 1) { // The source node is on a flow - so can use its id to find sourceNode = RED.nodes.node(o.id); + if (pathParts[0] === "global") { + pathParts = []; + } } else if (pathParts.length > 1) { // Highlight the subflow instance node. sourceNode = RED.nodes.node(pathParts[1]); From 277cc19ec372bd3e38bd4fa054ca6d8df9d23594 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Mon, 4 Jul 2022 11:23:47 +0900 Subject: [PATCH 076/237] call done after close handler --- .../node_modules/@node-red/nodes/core/network/22-websocket.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/node_modules/@node-red/nodes/core/network/22-websocket.js b/packages/node_modules/@node-red/nodes/core/network/22-websocket.js index 5a46f04ff..d2a862a46 100644 --- a/packages/node_modules/@node-red/nodes/core/network/22-websocket.js +++ b/packages/node_modules/@node-red/nodes/core/network/22-websocket.js @@ -215,6 +215,7 @@ module.exports = function(RED) { delete listenerNodes[node.fullPath]; node.server.close(); node._inputNodes = []; + done(); } else { node.closing = true; From d95314c754f2dc2d2436b9f416719dc369b200bc Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 4 Jul 2022 20:21:25 +0100 Subject: [PATCH 077/237] Update packages/node_modules/@node-red/runtime/lib/nodes/credentials.js --- packages/node_modules/@node-red/runtime/lib/nodes/credentials.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js b/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js index 5a72ab7fc..305594c85 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js @@ -377,7 +377,6 @@ var api = module.exports = { for (cred in savedCredentials) { if (savedCredentials.hasOwnProperty(cred)) { if (!newCreds.hasOwnProperty(cred)) { - console.log(` + ${cred} deleting for some reason`) delete savedCredentials[cred]; dirty = true; } From 78ed53f4fba241217062fdca0c30862e0dc42ff4 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 4 Jul 2022 20:46:21 +0100 Subject: [PATCH 078/237] Ensure a second paste of cut nodes is treated as a copy-paste --- .../@node-red/editor-client/src/js/ui/view.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index d24445deb..224f0a6f6 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -630,7 +630,9 @@ RED.view = (function() { RED.actions.add("core:copy-selection-to-internal-clipboard",copySelection); RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection(true);deleteSelection();}); - RED.actions.add("core:paste-from-internal-clipboard",function(){importNodes(clipboard,{generateIds: clipboardSource === 'copy', generateDefaultNames: clipboardSource === 'copy'});}); + RED.actions.add("core:paste-from-internal-clipboard",function(){ + importNodes(clipboard,{generateIds: clipboardSource === 'copy', generateDefaultNames: clipboardSource === 'copy'}); + }); RED.actions.add("core:detach-selected-nodes", function() { detachSelectedNodes() }) @@ -2150,6 +2152,9 @@ RED.view = (function() { } } if (mouse_mode == RED.state.IMPORT_DRAGGING) { + if (clipboardSource === 'cut') { + clipboardSource = 'copy' + } updateActiveNodes(); RED.nodes.dirty(true); } @@ -3479,6 +3484,9 @@ RED.view = (function() { updateSelection(); RED.nodes.dirty(true); redraw(); + if (clipboardSource === 'cut') { + clipboardSource = 'copy' + } resetMouseVars(); d3.event.stopPropagation(); return; From daa9cb65e5443517e18b364a36f59ce79c466d96 Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Mon, 4 Jul 2022 17:17:40 -0400 Subject: [PATCH 079/237] Change disabled config node background color variable --- .../@node-red/editor-client/src/sass/tab-config.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss index d6bde7db5..876a21662 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss @@ -101,7 +101,7 @@ ul.red-ui-sidebar-node-config-list li.red-ui-palette-node-config-type { } .red-ui-palette-node-config-unused,.red-ui-palette-node-config-disabled { border-color: var(--red-ui-primary-border-color); - background: var(--red-ui-secondary-background-inactive); + background: var(--red-ui-node-config-background); border-style: dashed; color: var(--red-ui-tertiary-text-color); } From 7d0267c924e98f262a8a560b7122cc051d83129b Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Mon, 4 Jul 2022 18:38:45 -0400 Subject: [PATCH 080/237] Move colors left behind in #3692 to CSS variables --- .../@node-red/editor-client/src/sass/dropdownMenu.scss | 2 +- .../@node-red/editor-client/src/sass/projects.scss | 4 +++- .../node_modules/@node-red/editor-client/src/sass/search.scss | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss b/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss index 4323e9342..5cb5f725c 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss @@ -220,7 +220,7 @@ margin-right: -15px; /* Caret Arrow */ border-color: transparent; - border-left-color: $menuCaret; + border-left-color: var(--red-ui-menuCaret); border-style: solid; border-width: 5px 0 5px 5px; content: " "; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/projects.scss b/packages/node_modules/@node-red/editor-client/src/sass/projects.scss index 0019ba516..ee43c7a87 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/projects.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/projects.scss @@ -711,7 +711,9 @@ transform: rotate(90deg); } } -.red-ui-projects-dialog-file-list-entry-file-type-git { color: $tertiary-text-color } +.red-ui-projects-dialog-file-list-entry-file-type-git { + color: var(--red-ui-tertiary-text-color); +} .red-ui-projects-dialog-remote-list { .red-ui-editableList-container { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/search.scss b/packages/node_modules/@node-red/editor-client/src/sass/search.scss index cf37fcb6e..f5502715b 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/search.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/search.scss @@ -84,7 +84,7 @@ .red-ui-search-result-node-port { position: absolute; border-radius: 2px; - border: 1px solid $node-border; + border: 1px solid var(--red-ui-node-border); width: 6px; height: 7px; top:4px; From 6c1f63bfbbc56308546bd69a22f53bbeb61dcc73 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Tue, 5 Jul 2022 09:02:21 +0900 Subject: [PATCH 081/237] introduce RED.view.dimensions for workspace dimensions --- .../@node-red/editor-client/src/js/ui/view-tools.js | 6 +++--- .../@node-red/editor-client/src/js/ui/view.js | 8 +++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js index 44da78fec..2bec5669c 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js @@ -105,8 +105,9 @@ RED.view.tools = (function() { $(document).one('keyup',endKeyboardMove); endMoveSet = true; } - var space_width = 5000; - var space_height = 5000; + var dim = RED.view.dimensions(); + var space_width = dim.width; + var space_height = dim.height; var minX = 0; var minY = 0; var node; @@ -138,7 +139,6 @@ RED.view.tools = (function() { minY = Math.min(node.n.y-node.n.h/2-5,minY); } } - console.log("; mS:", dx, dy, minX, minY); if (minX !== 0 || minY !== 0) { for (var n = 0; n Date: Tue, 5 Jul 2022 07:33:31 -0400 Subject: [PATCH 082/237] Don't let themes change config node label colors --- .../node_modules/@node-red/editor-client/src/sass/colors.scss | 1 + .../@node-red/editor-client/src/sass/tab-config.scss | 4 ++-- .../@node-red/editor-client/src/sass/variables.scss | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/colors.scss b/packages/node_modules/@node-red/editor-client/src/sass/colors.scss index b311b06b5..ce71bcdba 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/colors.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/colors.scss @@ -217,6 +217,7 @@ $node-icon-border-color: #000; $node-icon-border-color-opacity: 0.1; $node-config-background: #f3f3f3; +$node-config-icon-container-disabled: #aaa; $node-link-port-background: #eee; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss index 876a21662..c1f151ca6 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-config.scss @@ -103,13 +103,13 @@ ul.red-ui-sidebar-node-config-list li.red-ui-palette-node-config-type { border-color: var(--red-ui-primary-border-color); background: var(--red-ui-node-config-background); border-style: dashed; - color: var(--red-ui-tertiary-text-color); + color: var(--red-ui-node-config-icon-container-disabled); } .red-ui-palette-node-config-disabled { opacity: 0.6; font-style: italic; i { - color: var(--red-ui-secondary-text-color); + color: var(--red-ui-node-port-label-color); margin-right: 5px; } } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/variables.scss b/packages/node_modules/@node-red/editor-client/src/sass/variables.scss index abd31629c..50e1c9310 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/variables.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/variables.scss @@ -203,6 +203,7 @@ --red-ui-node-icon-border-color-opacity: #{$node-icon-border-color-opacity}; --red-ui-node-config-background: #{$node-config-background}; + --red-ui-node-config-icon-container-disabled: #{$node-config-icon-container-disabled}; --red-ui-node-link-port-background: #{$node-link-port-background}; From eaa85ae8d505d259d8b346ba0e9cd705c521ba34 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Wed, 6 Jul 2022 12:54:22 +0100 Subject: [PATCH 083/237] Fix insert=>link nodes enable/disable state --- .../@node-red/editor-client/src/js/ui/contextMenu.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js index a8b787ba5..44f11c87d 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js @@ -103,7 +103,7 @@ RED.contextMenu = (function() { { label: RED._("contextMenu.linkNodes"), onselect: 'core:split-wire-with-link-nodes', - disabled: hasSelection || !hasLinks + disabled: !hasLinks } ] From 03f758720cbcacabb8aa13ee95d9b814b26e878c Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Wed, 6 Jul 2022 13:05:18 +0100 Subject: [PATCH 084/237] Allow escape key to close context menu --- .../@node-red/editor-client/src/js/ui/contextMenu.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js index 44f11c87d..bebd178a4 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js @@ -192,16 +192,12 @@ RED.contextMenu = (function() { disposeMenu() }); menu.show(); - - // menu.show({ - // target: $('#red-ui-main-container'), - // x: options.x, - // y: options.y - // }) - + // set focus to first item so that pressing escape key closes the menu + $("#red-ui-workspace-context-menu :first(ul) > a").trigger("focus") } - + // Allow escape key hook and other editor events to close context menu + RED.keyboard.add("red-ui-workspace-context-menu", "escape", function () { RED.contextMenu.hide() }) return { show: show, hide: disposeMenu From 21d3261eecea2889699ef08410f4b805796d2df4 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Wed, 6 Jul 2022 13:05:57 +0100 Subject: [PATCH 085/237] Allow editor events to close context menu --- .../@node-red/editor-client/src/js/ui/contextMenu.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js index bebd178a4..ac88ebe76 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js @@ -198,6 +198,11 @@ RED.contextMenu = (function() { } // Allow escape key hook and other editor events to close context menu RED.keyboard.add("red-ui-workspace-context-menu", "escape", function () { RED.contextMenu.hide() }) + RED.events.on("editor:open",function() { RED.contextMenu.hide() }); + RED.events.on("search:open",function() { RED.contextMenu.hide() }); + RED.events.on("type-search:open",function() { RED.contextMenu.hide() }); + RED.events.on("actionList:open",function() { RED.contextMenu.hide() }); + RED.events.on("view:selection-changed",function() { RED.contextMenu.hide() }); return { show: show, hide: disposeMenu From 7216c6d62a2f8420d1524772a9e5907398bebe6c Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Wed, 6 Jul 2022 13:08:56 +0100 Subject: [PATCH 086/237] code linting and remove excessive new lines --- .../editor-client/src/js/ui/contextMenu.js | 81 +++++++------------ 1 file changed, 31 insertions(+), 50 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js index ac88ebe76..063654d4a 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js @@ -1,4 +1,4 @@ -RED.contextMenu = (function() { +RED.contextMenu = (function () { let menu; function createMenu() { @@ -15,10 +15,6 @@ RED.contextMenu = (function() { // ], // width: 200, // }) - - - - } function disposeMenu() { @@ -50,35 +46,35 @@ RED.contextMenu = (function() { if (RED.view.snapGrid) { const gridSize = RED.view.gridSize() - addX = gridSize*Math.floor(addX/gridSize) - addY = gridSize*Math.floor(addY/gridSize) + addX = gridSize * Math.floor(addX / gridSize) + addY = gridSize * Math.floor(addY / gridSize) } const menuItems = [ - { onselect: 'core:show-action-list', onpostselect: function() {} }, + { onselect: 'core:show-action-list', onpostselect: function () { } }, { label: RED._("contextMenu.insert"), options: [ { label: RED._("contextMenu.node"), - onselect: function() { + onselect: function () { RED.view.showQuickAddDialog({ - position: [ addX, addY ], + position: [addX, addY], touchTrigger: true, - splice: isSingleLink?selection.links[0]:undefined, + splice: isSingleLink ? selection.links[0] : undefined, // spliceMultiple: isMultipleLinks }) } }, - ( hasSelection || hasLinks ) ? { + (hasSelection || hasLinks) ? { label: RED._("contextMenu.junction"), onselect: 'core:split-wires-with-junctions', disabled: !hasLinks } : { label: RED._("contextMenu.junction"), - onselect: function() { + onselect: function () { const nn = { - _def: {defaults:{}}, + _def: { defaults: {} }, type: 'junction', z: RED.workspaces.active(), id: RED.nodes.id(), @@ -91,8 +87,8 @@ RED.contextMenu = (function() { } const historyEvent = { dirty: RED.nodes.dirty(), - t:'add', - junctions:[nn] + t: 'add', + junctions: [nn] } RED.nodes.addJunction(nn); RED.history.push(historyEvent); @@ -111,28 +107,13 @@ RED.contextMenu = (function() { } ] - // menuItems.push( - // { - // label: (isSingleLink || isMultipleLinks)?'Insert into wire...':'Add node...', - // onselect: function() { - // RED.view.showQuickAddDialog({ - // position: [ options.x - offset.left, options.y - offset.top ], - // touchTrigger: true, - // splice: isSingleLink?selection.links[0]:undefined, - // spliceMultiple: isMultipleLinks - // }) - // } - // }, - // ) - // if (hasLinks && !hasSelection) { - // menuItems.push({ onselect: 'core:split-wires-with-junctions', label: 'Insert junction'}) - // } + menuItems.push( null, { onselect: 'core:undo', disabled: RED.history.list().length === 0 }, { onselect: 'core:redo', disabled: RED.history.listRedo().length === 0 }, null, - { onselect: 'core:cut-selection-to-internal-clipboard', label: RED._("keyboard.cutNode"), disabled: !hasSelection}, + { onselect: 'core:cut-selection-to-internal-clipboard', label: RED._("keyboard.cutNode"), disabled: !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: !RED.view.clipboard() }, { onselect: 'core:delete-selection', disabled: !canDelete }, @@ -144,8 +125,8 @@ RED.contextMenu = (function() { menuItems.push( null, isGroup ? - { onselect: 'core:ungroup-selection', disabled: !isGroup } - : { onselect: 'core:group-selection', disabled: !hasSelection } + { onselect: 'core:ungroup-selection', disabled: !isGroup } + : { onselect: 'core:group-selection', disabled: !hasSelection } ) if (canRemoveFromGroup) { menuItems.push({ onselect: 'core:remove-selection-from-group', label: RED._("menu.label.groupRemoveSelection") }) @@ -154,16 +135,16 @@ RED.contextMenu = (function() { } menu = RED.menu.init({ direction: 'right', - onpreselect: function() { + onpreselect: function () { disposeMenu() }, - onpostselect: function() { + onpostselect: function () { RED.view.focus() }, options: menuItems }); - menu.attr("id","red-ui-workspace-context-menu"); + menu.attr("id", "red-ui-workspace-context-menu"); menu.css({ position: "absolute" }) @@ -174,18 +155,18 @@ RED.contextMenu = (function() { var top = options.y var left = options.x - if (top+menu.height()-$(document).scrollTop() > $(window).height()) { - top -= (top+menu.height())-$(window).height() + 22; + if (top + menu.height() - $(document).scrollTop() > $(window).height()) { + top -= (top + menu.height()) - $(window).height() + 22; } - if (left+menu.width()-$(document).scrollLeft() > $(window).width()) { - left -= (left+menu.width())-$(window).width() + 18; + if (left + menu.width() - $(document).scrollLeft() > $(window).width()) { + left -= (left + menu.width()) - $(window).width() + 18; } menu.css({ - top: top+"px", - left: left+"px" + top: top + "px", + left: left + "px" }) $(".red-ui-menu.red-ui-menu-dropdown").hide(); - $(document).on("mousedown.red-ui-workspace-context-menu", function(evt) { + $(document).on("mousedown.red-ui-workspace-context-menu", function (evt) { if (menu && menu[0].contains(evt.target)) { return } @@ -198,11 +179,11 @@ RED.contextMenu = (function() { } // Allow escape key hook and other editor events to close context menu RED.keyboard.add("red-ui-workspace-context-menu", "escape", function () { RED.contextMenu.hide() }) - RED.events.on("editor:open",function() { RED.contextMenu.hide() }); - RED.events.on("search:open",function() { RED.contextMenu.hide() }); - RED.events.on("type-search:open",function() { RED.contextMenu.hide() }); - RED.events.on("actionList:open",function() { RED.contextMenu.hide() }); - RED.events.on("view:selection-changed",function() { RED.contextMenu.hide() }); + RED.events.on("editor:open", function () { RED.contextMenu.hide() }); + RED.events.on("search:open", function () { RED.contextMenu.hide() }); + RED.events.on("type-search:open", function () { RED.contextMenu.hide() }); + RED.events.on("actionList:open", function () { RED.contextMenu.hide() }); + RED.events.on("view:selection-changed", function () { RED.contextMenu.hide() }); return { show: show, hide: disposeMenu From 962672564eba014b299a15193fcfa244ec036008 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Wed, 6 Jul 2022 15:17:58 +0100 Subject: [PATCH 087/237] prevent exception generating tooltip for deleted nodes --- .../@node-red/editor-client/src/js/ui/view.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 70583d741..5a9df8ed7 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -3293,11 +3293,17 @@ RED.view = (function() { if (active && ((portType === PORT_TYPE_INPUT && ((d._def && d._def.inputLabels)||d.inputLabels)) || (portType === PORT_TYPE_OUTPUT && ((d._def && d._def.outputLabels)||d.outputLabels)))) { portLabelHoverTimeout = setTimeout(function() { + const n = port && port.node() + const nId = n && n.__data__ && n.__data__.id + //check see if node has been deleted since timeout started + if(!n || !n.parentNode || !RED.nodes.node(n.__data__.id)) { + return; //node is gone! + } var tooltip = getPortLabel(d,portType,portIndex); if (!tooltip) { return; } - var pos = getElementPosition(port.node()); + var pos = getElementPosition(n); portLabelHoverTimeout = null; portLabelHover = showTooltip( (pos[0]+(portType===PORT_TYPE_INPUT?-2:12)), @@ -3734,6 +3740,10 @@ RED.view = (function() { if (d.hasOwnProperty('l')?!d.l : (d.type === "link in" || d.type === "link out")) { var parentNode = this.parentNode; portLabelHoverTimeout = setTimeout(function() { + //check see if node has been deleted since timeout started + if(!parentNode || !parentNode.parentNode || !RED.nodes.node(parentNode.id)) { + return; //node is gone! + } var tooltip; if (d._def.label) { tooltip = d._def.label; From 257b1f89f30cb430479e10854979b43f2ed2ad90 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Thu, 7 Jul 2022 16:47:26 +0900 Subject: [PATCH 088/237] fix display direction of context sub-menu --- .../@node-red/editor-client/src/js/ui/contextMenu.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js index a8b787ba5..44090aa1a 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js @@ -152,8 +152,16 @@ RED.contextMenu = (function() { } } + + var direction = "right"; + var MENU_WIDTH = 600; // can not use menu width here + if ((options.x -$(document).scrollLeft()) > + ($(window).height() -MENU_WIDTH)) { + direction = "left"; + } + menu = RED.menu.init({ - direction: 'right', + direction: direction, onpreselect: function() { disposeMenu() }, From bd4a5ac8441c473f3b6da2b9c1c949157eca984f Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Thu, 7 Jul 2022 19:39:28 +0900 Subject: [PATCH 089/237] fixed menu width and window width --- .../@node-red/editor-client/src/js/ui/contextMenu.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js index 44090aa1a..7d893225b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js @@ -154,9 +154,9 @@ RED.contextMenu = (function() { } var direction = "right"; - var MENU_WIDTH = 600; // can not use menu width here + var MENU_WIDTH = 500; // can not use menu width here if ((options.x -$(document).scrollLeft()) > - ($(window).height() -MENU_WIDTH)) { + ($(window).width() -MENU_WIDTH)) { direction = "left"; } From 18f9ab0cdae802f5c857b3a283a79d6db8108fa1 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Thu, 7 Jul 2022 20:23:27 +0900 Subject: [PATCH 090/237] make left sub-menu caret fit within context menu --- .../@node-red/editor-client/src/sass/dropdownMenu.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss b/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss index 5cb5f725c..84edd405e 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss @@ -199,7 +199,7 @@ width: 0; height: 0; margin-top: 5px; - margin-left: -30px; + margin-left: -8px; /* Caret Arrow */ border-color: transparent; border-right-color: var(--red-ui-menuCaret); From 5ae566952be124329a194a805cd9a940ef7c1b9e Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Fri, 8 Jul 2022 13:10:21 +0900 Subject: [PATCH 091/237] do not show clear pinned if not available --- .../core/common/lib/debug/debug-utils.js | 50 +++++++++++-------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/common/lib/debug/debug-utils.js b/packages/node_modules/@node-red/nodes/core/common/lib/debug/debug-utils.js index 5782c6ffc..70dc33605 100644 --- a/packages/node_modules/@node-red/nodes/core/common/lib/debug/debug-utils.js +++ b/packages/node_modules/@node-red/nodes/core/common/lib/debug/debug-utils.js @@ -459,30 +459,38 @@ RED.debug = (function() { function showMessageMenu(button,dbgMessage,sourceId) { activeMenuMessage = dbgMessage; if (!menuOptionMenu) { - menuOptionMenu = RED.menu.init({id:"red-ui-debug-msg-option-menu", - options: [ - {id:"red-ui-debug-msg-menu-item-collapse",label:RED._("node-red:debug.messageMenu.collapseAll"),onselect:function(){ - activeMenuMessage.collapse(); - }}, + var opts = [ + {id:"red-ui-debug-msg-menu-item-collapse",label:RED._("node-red:debug.messageMenu.collapseAll"),onselect:function(){ + activeMenuMessage.collapse(); + }}, + ]; + if (activeMenuMessage.clearPinned) { + opts.push( {id:"red-ui-debug-msg-menu-item-clear-pins",label:RED._("node-red:debug.messageMenu.clearPinned"),onselect:function(){ activeMenuMessage.clearPinned(); }}, - null, - {id:"red-ui-debug-msg-menu-item-filter", label:RED._("node-red:debug.messageMenu.filterNode"),onselect:function(){ - var candidateNodes = RED.nodes.filterNodes({type:'debug'}); - candidateNodes.forEach(function(n) { - filteredNodes[n.id] = true; - }); - delete filteredNodes[sourceId]; - $("#red-ui-sidebar-debug-filterSelected").trigger("click"); - RED.settings.set('debug.filteredNodes',Object.keys(filteredNodes)) - refreshMessageList(); - }}, - {id:"red-ui-debug-msg-menu-item-clear-filter",label:RED._("node-red:debug.messageMenu.clearFilter"),onselect:function(){ - $("#red-ui-sidebar-debug-filterAll").trigger("click"); - refreshMessageList(); - }} - ] + ); + } + opts.push( + null, + {id:"red-ui-debug-msg-menu-item-filter", label:RED._("node-red:debug.messageMenu.filterNode"),onselect:function(){ + var candidateNodes = RED.nodes.filterNodes({type:'debug'}); + candidateNodes.forEach(function(n) { + filteredNodes[n.id] = true; + }); + delete filteredNodes[sourceId]; + $("#red-ui-sidebar-debug-filterSelected").trigger("click"); + RED.settings.set('debug.filteredNodes',Object.keys(filteredNodes)) + refreshMessageList(); + }}, + {id:"red-ui-debug-msg-menu-item-clear-filter",label:RED._("node-red:debug.messageMenu.clearFilter"),onselect:function(){ + $("#red-ui-sidebar-debug-filterAll").trigger("click"); + refreshMessageList(); + }} + ); + + menuOptionMenu = RED.menu.init({id:"red-ui-debug-msg-option-menu", + options: opts }); menuOptionMenu.css({ position: "absolute" From 69beecf334aafe21f1f344c652d3699f7347a71b Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 8 Jul 2022 13:06:05 +0100 Subject: [PATCH 092/237] Fix menu padding for pull-left submenus --- .../editor-client/src/sass/dropdownMenu.scss | 11 +++++++---- .../@node-red/editor-client/src/sass/header.scss | 3 ++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss b/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss index 84edd405e..f6a2d6fde 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/dropdownMenu.scss @@ -63,11 +63,14 @@ padding: 4px 12px 4px 12px; } - &.red-ui-menu-dropdown-submenus > li > a, - &.red-ui-menu-dropdown-submenus > li > a:focus { + &.red-ui-menu-dropdown-submenus.red-ui-menu-dropdown-direction-right > li > a, + &.red-ui-menu-dropdown-submenus.red-ui-menu-dropdown-direction-right > li > a:focus { padding-right: 20px; } - + &.red-ui-menu-dropdown-submenus.red-ui-menu-dropdown-direction-left > li > a, + &.red-ui-menu-dropdown-submenus.red-ui-menu-dropdown-direction-left > li > a:focus { + padding-left: 20px; + } & > .active > a, @@ -199,7 +202,7 @@ width: 0; height: 0; margin-top: 5px; - margin-left: -8px; + margin-left: -15px; /* Caret Arrow */ border-color: transparent; border-right-color: var(--red-ui-menuCaret); diff --git a/packages/node_modules/@node-red/editor-client/src/sass/header.scss b/packages/node_modules/@node-red/editor-client/src/sass/header.scss index e837f0805..723c1e9bd 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/header.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/header.scss @@ -191,7 +191,7 @@ margin-top: 0; li a { color: var(--red-ui-header-menu-color); - padding: 3px 10px 3px 40px; + padding: 3px 10px 3px 30px; img { max-width: 100%; margin-right: 10px; @@ -243,6 +243,7 @@ } .red-ui-menu-dropdown-submenu>a:before { border-right-color: var(--red-ui-headerMenuCaret); + margin-left: -25px !important; } /* Deploy menu customisations */ From 639030924fa969def5e210c8d78332ce5a6379a8 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Fri, 8 Jul 2022 14:09:54 +0100 Subject: [PATCH 093/237] include dirty state in history event --- .../@node-red/editor-client/src/js/ui/view-tools.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js index 2bec5669c..23316b1a1 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js @@ -1089,6 +1089,7 @@ RED.view.tools = (function() { linkGroups.sort(function(A,B) { return groupedLinks[B].length - groupedLinks[A].length }) + const wasDirty = RED.nodes.dirty() linkGroups.forEach(function(gid) { var links = groupedLinks[gid] var junction = { @@ -1179,6 +1180,7 @@ RED.view.tools = (function() { }) if (addedJunctions.length > 0) { RED.history.push({ + dirty: wasDirty, t: 'add', links: addedLinks, junctions: addedJunctions, From 7a048d5b32ff8dfbcd7a7f015b69dd276ed74c76 Mon Sep 17 00:00:00 2001 From: Franck Mourre Date: Mon, 11 Jul 2022 10:51:31 +0200 Subject: [PATCH 094/237] Fix change node, not handling from field properly when using context --- .../node_modules/@node-red/nodes/core/function/15-change.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/function/15-change.js b/packages/node_modules/@node-red/nodes/core/function/15-change.js index d177caec8..94f47fcbf 100644 --- a/packages/node_modules/@node-red/nodes/core/function/15-change.js +++ b/packages/node_modules/@node-red/nodes/core/function/15-change.js @@ -168,9 +168,9 @@ module.exports = function(RED) { return getFromValueType(RED.util.getMessageProperty(msg,rule.from),done); } else if (rule.fromt === 'flow' || rule.fromt === 'global') { var contextKey = RED.util.parseContextStore(rule.from); - if (/\[msg\./.test(context.key)) { + if (/\[msg\./.test(contextKey.key)) { // The key has a nest msg. reference to evaluate first - context.key = RED.util.normalisePropertyExpression(contextKey.key,msg,true); + contextKey.key = RED.util.normalisePropertyExpression(contextKey.key,msg,true); } node.context()[rule.fromt].get(contextKey.key, contextKey.store, (err,fromValue) => { if (err) { From 55a94d659bccce27e90ffa0bf554493e1bdf1f94 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Mon, 11 Jul 2022 10:27:30 +0100 Subject: [PATCH 095/237] fix const reassignment --- packages/node_modules/@node-red/nodes/core/common/60-link.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/nodes/core/common/60-link.js b/packages/node_modules/@node-red/nodes/core/common/60-link.js index 08bad7a62..24a964807 100644 --- a/packages/node_modules/@node-red/nodes/core/common/60-link.js +++ b/packages/node_modules/@node-red/nodes/core/common/60-link.js @@ -29,7 +29,7 @@ module.exports = function(RED) { "use strict"; const crypto = require("crypto"); const targetCache = (function () { - const registry = { id: {}, name: {} }; + let registry = { id: {}, name: {} } function getIndex(/** @type {[LinkTarget]}*/ targets, id) { for (let index = 0; index < (targets || []).length; index++) { const element = targets[index]; From e4098d39912edd4f3272811c7b5bf1303b544387 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Mon, 11 Jul 2022 10:28:17 +0100 Subject: [PATCH 096/237] fix undecalred variable access --- packages/node_modules/@node-red/nodes/core/common/60-link.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/nodes/core/common/60-link.js b/packages/node_modules/@node-red/nodes/core/common/60-link.js index 24a964807..90d1d5741 100644 --- a/packages/node_modules/@node-red/nodes/core/common/60-link.js +++ b/packages/node_modules/@node-red/nodes/core/common/60-link.js @@ -115,7 +115,7 @@ module.exports = function(RED) { targs.splice(idx, 1); } if (targs.length === 0) { - delete registry.name[tn.name]; + delete registry.name[target.name] } delete registry.id[target.id]; }, From 5c69599e78f68b09a7376656ee96731a2ee3804b Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Mon, 11 Jul 2022 10:28:31 +0100 Subject: [PATCH 097/237] Apply linting suggestions to the `registry` enclosure --- .../@node-red/nodes/core/common/60-link.js | 76 +++++++++---------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/common/60-link.js b/packages/node_modules/@node-red/nodes/core/common/60-link.js index 90d1d5741..90b7966e0 100644 --- a/packages/node_modules/@node-red/nodes/core/common/60-link.js +++ b/packages/node_modules/@node-red/nodes/core/common/60-link.js @@ -30,22 +30,22 @@ module.exports = function(RED) { const crypto = require("crypto"); const targetCache = (function () { let registry = { id: {}, name: {} } - function getIndex(/** @type {[LinkTarget]}*/ targets, id) { + function getIndex (/** @type {[LinkTarget]} */ targets, id) { for (let index = 0; index < (targets || []).length; index++) { - const element = targets[index]; + const element = targets[index] if (element.id === id) { - return index; + return index } } - return -1; + return -1 } /** * Generate a target object from a node - * @param {LinkInNode} node + * @param {LinkInNode} node * @returns {LinkTarget} a link target object */ - function generateTarget(node) { - const isSubFlow = node._flow.TYPE === "subflow"; + function generateTarget (node) { + const isSubFlow = node._flow.TYPE === 'subflow' return { id: node.id, name: node.name || node.id, @@ -58,72 +58,72 @@ module.exports = function(RED) { /** * Get a list of targets registerd to this name * @param {string} name Name of the target - * @param {boolean} [excludeSubflows] set `true` to exclude + * @param {boolean} [excludeSubflows] set `true` to exclude * @returns {[LinkTarget]} Targets registerd to this name. */ - getTargets(name, excludeSubflows) { - const targets = registry.name[name] || []; + getTargets (name, excludeSubflows) { + const targets = registry.name[name] || [] if (excludeSubflows) { - return targets.filter(e => e.isSubFlow != true); + return targets.filter(e => e.isSubFlow !== true) } - return targets; + return targets }, /** * Get a single target by registered name. * To restrict to a single flow, include the `flowId` * If there is no targets OR more than one target, null is returned * @param {string} name Name of the node - * @param {string} [flowId] + * @param {string} [flowId] * @returns {LinkTarget} target */ - getTarget(name, flowId) { - /** @type {[LinkTarget]}*/ - let possibleTargets = this.getTargets(name); - /** @type {LinkTarget}*/ - let target; + getTarget (name, flowId) { + /** @type {[LinkTarget]} */ + let possibleTargets = this.getTargets(name) + /** @type {LinkTarget} */ + let target if (possibleTargets.length && flowId) { - possibleTargets = possibleTargets.filter(e => e.flowId == flowId); + possibleTargets = possibleTargets.filter(e => e.flowId === flowId) } if (possibleTargets.length === 1) { - target = possibleTargets[0]; + target = possibleTargets[0] } - return target; + return target }, /** * Get a target by node ID * @param {string} nodeId ID of the node * @returns {LinkTarget} target */ - getTargetById(nodeId) { - return registry.id[nodeId]; + getTargetById (nodeId) { + return registry.id[nodeId] }, - register(/** @type {LinkInNode} */ node) { - const target = generateTarget(node); - const tByName = this.getTarget(target.name, target.flowId); + register (/** @type {LinkInNode} */ node) { + const target = generateTarget(node) + const tByName = this.getTarget(target.name, target.flowId) if (!tByName || tByName.id !== target.id) { - registry.name[target.name] = registry.name[target.name] || []; + registry.name[target.name] = registry.name[target.name] || [] registry.name[target.name].push(target) } - registry.id[target.id] = target; - return target; + registry.id[target.id] = target + return target }, - remove(node) { - const target = generateTarget(node); - const targs = this.getTargets(target.name); - const idx = getIndex(targs, target.id); + remove (node) { + const target = generateTarget(node) + const targs = this.getTargets(target.name) + const idx = getIndex(targs, target.id) if (idx > -1) { - targs.splice(idx, 1); + targs.splice(idx, 1) } if (targs.length === 0) { delete registry.name[target.name] } - delete registry.id[target.id]; + delete registry.id[target.id] }, - clear() { - registry = { id: {}, name: {} }; + clear () { + registry = { id: {}, name: {} } } } - })(); + })() function LinkInNode(n) { RED.nodes.createNode(this,n); From 44216310ca5bc87e5670c9903bc64612d3de58f2 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Mon, 11 Jul 2022 13:09:49 +0100 Subject: [PATCH 098/237] Ensure global/config and flow/config have info fixes #3750 --- .../@node-red/nodes/core/common/21-debug.html | 36 +++++++------------ 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/common/21-debug.html b/packages/node_modules/@node-red/nodes/core/common/21-debug.html index a85a4892b..88d51b283 100644 --- a/packages/node_modules/@node-red/nodes/core/common/21-debug.html +++ b/packages/node_modules/@node-red/nodes/core/common/21-debug.html @@ -245,47 +245,37 @@ // complete parentage of the node that generated this message. // flow-id/subflow-A-instance/subflow-B-instance - // If it has one id, that is a top level flow + // If it has one id, that is a top level flow or config node/global // each subsequent id is the instance id of a subflow node // pathParts = o.path.split("/"); if (pathParts.length === 1) { - // The source node is on a flow - so can use its id to find + // The source node is on a flow or is a global/config - so can use its id to find sourceNode = RED.nodes.node(o.id); - if (pathParts[0] === "global") { - pathParts = []; - } } else if (pathParts.length > 1) { // Highlight the subflow instance node. sourceNode = RED.nodes.node(pathParts[1]); } + const getNodeLabel = (n) => n.name || (typeof n.label === "function" && n.label()) || (typeof n.label === "string" && n.label) || (n.type + ":" + n.id); pathHierarchy = pathParts.map((id,index) => { if (index === 0) { - return { - id: id, - label: RED.nodes.workspace(id).label - } + if (id === "global") { + return { id: sourceNode.id, label: getNodeLabel(sourceNode) } + } + return { id: id, label: RED.nodes.workspace(id).label } //flow id + name } else { - var instanceNode = RED.nodes.node(id) - return { - id: id, - label: (instanceNode.name || RED.nodes.subflow(instanceNode.type.substring(8)).name) - } + const instanceNode = RED.nodes.node(id) + const pathLabel = (instanceNode.name || RED.nodes.subflow(instanceNode.type.substring(8)).name) + return { id: id, label: pathLabel } } }) - if (pathParts.length === 1) { - pathHierarchy.push({ - id: o.id, - label: sourceNode.name || sourceNode.type+":"+sourceNode.id - }) + if (pathParts.length === 1 && pathParts[0] !== "global") { + pathHierarchy.push({ id: o.id, label: getNodeLabel(sourceNode) }) } if (o._alias) { let aliasNode = RED.nodes.node(o._alias) if (aliasNode) { - pathHierarchy.push({ - id: o._alias, - label: aliasNode.name || aliasNode.type+":"+aliasNode.id - }) + pathHierarchy.push({ id: o._alias, label: getNodeLabel(aliasNode) }) } } } else { From c86e4f52a0e827b543b8056811025425bbd9289f Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Tue, 12 Jul 2022 00:10:51 +0900 Subject: [PATCH 099/237] Fix shade for node icon to be rounded rectangle --- .../node_modules/@node-red/editor-client/src/sass/palette.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/palette.scss b/packages/node_modules/@node-red/editor-client/src/sass/palette.scss index 0d123918a..fdfe77f09 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/palette.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/palette.scss @@ -171,6 +171,7 @@ left:0; width: 30px; border-right: 1px solid var(--red-ui-node-icon-background-color); + border-radius: 4px 0px 0px 4px; background-color: var(--red-ui-node-icon-background-color); } .red-ui-palette-icon-container-right { @@ -178,6 +179,7 @@ right: 0; border-right: none; border-left: 1px solid var(--red-ui-node-icon-background-color); + border-radius: 0px 4px 4px 0px; } .red-ui-palette-icon { display: inline-block; From cee287da99f1533b0a88c0ed74f95de906e74e8c Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Tue, 12 Jul 2022 02:09:20 +0900 Subject: [PATCH 100/237] Focus editor for undo after some actions in menu --- .../node_modules/@node-red/editor-client/src/js/ui/group.js | 4 ++++ .../node_modules/@node-red/editor-client/src/js/ui/subflow.js | 1 + 2 files changed, 5 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/group.js b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js index e11a55660..add6da6c9 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/group.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js @@ -308,6 +308,7 @@ RED.group = (function() { RED.history.push(historyEvent); RED.view.select({nodes:[group]}); RED.nodes.dirty(true); + RED.view.focus(); } } } @@ -330,6 +331,7 @@ RED.group = (function() { RED.history.push(historyEvent); RED.view.select({nodes:newSelection}) RED.nodes.dirty(true); + RED.view.focus(); } } @@ -424,6 +426,7 @@ RED.group = (function() { }); RED.history.push(historyEvent); RED.nodes.dirty(true); + RED.view.focus(); } } @@ -451,6 +454,7 @@ RED.group = (function() { } } RED.view.select({nodes:selection.nodes}) + RED.view.focus(); } } function createGroup(nodes) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js index 3aeb7151f..d15e52283 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js @@ -877,6 +877,7 @@ RED.subflow = (function() { RED.nodes.dirty(true); RED.view.updateActive(); RED.view.select(null); + RED.view.focus(); } From 0a0a7ca39b233ad4d8f99a2b920dc728d3cfd086 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 11 Jul 2022 20:19:48 +0100 Subject: [PATCH 101/237] Fix storing subflow credential type when input has multiple types Fixes #3749 --- .../@node-red/editor-client/src/js/ui/subflow.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js index 3aeb7151f..10fece9a2 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js @@ -931,6 +931,7 @@ RED.subflow = (function() { function buildEnvUIRow(row, tenv, ui, node) { + console.log(tenv, ui) ui.label = ui.label||{}; if ((tenv.type === "cred" || (tenv.parent && tenv.parent.type === "cred")) && !ui.type) { ui.type = "cred"; @@ -991,6 +992,17 @@ RED.subflow = (function() { default: inputType }) input.typedInput('value',val.value) + if (inputType === 'cred') { + if (node.credentials) { + if (node.credentials[tenv.name]) { + input.typedInput('value', node.credentials[tenv.name]); + } else if (node.credentials['has_'+tenv.name]) { + input.typedInput('value', "__PWRD__") + } else { + input.typedInput('value', ""); + } + } + } } else { input.val(val.value) } From 6ff2232df3b0cfff1bc7e44cd97ac7f76054d055 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 12 Jul 2022 09:27:45 +0100 Subject: [PATCH 102/237] Ensure node icon shade has properly rounded corners --- .../@node-red/editor-client/src/js/ui/view.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 5a9df8ed7..8c7ea22a3 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -4580,12 +4580,10 @@ RED.view = (function() { icon_groupEl.setAttribute("y",0); icon_groupEl.style["pointer-events"] = "none"; node[0][0].__iconGroup__ = icon_groupEl; - var icon_shade = document.createElementNS("http://www.w3.org/2000/svg","rect"); + var icon_shade = document.createElementNS("http://www.w3.org/2000/svg","path"); icon_shade.setAttribute("x",0); icon_shade.setAttribute("y",0); icon_shade.setAttribute("class","red-ui-flow-node-icon-shade") - icon_shade.setAttribute("width",30); - icon_shade.setAttribute("height",Math.min(50,d.h-4)); icon_groupEl.appendChild(icon_shade); node[0][0].__iconShade__ = icon_shade; @@ -4878,9 +4876,20 @@ RED.view = (function() { } icon.attr("y",function(){return (d.h-d3.select(this).attr("height"))/2;}); - this.__iconShade__.setAttribute("height", d.h ); + + + const iconShadeHeight = d.h + const iconShadeWidth = 30 + this.__iconShade__.setAttribute("d", hideLabel ? + `M5 0 h${iconShadeWidth-10} a 5 5 0 0 1 5 5 v${iconShadeHeight-10} a 5 5 0 0 1 -5 5 h-${iconShadeWidth-10} a 5 5 0 0 1 -5 -5 v-${iconShadeHeight-10} a 5 5 0 0 1 5 -5` : ( + "right" === d._def.align ? + `M 0 0 h${iconShadeWidth-5} a 5 5 0 0 1 5 5 v${iconShadeHeight-10} a 5 5 0 0 1 -5 5 h-${iconShadeWidth-5} v-${iconShadeHeight}` : + `M5 0 h${iconShadeWidth-5} v${iconShadeHeight} h-${iconShadeWidth-5} a 5 5 0 0 1 -5 -5 v-${iconShadeHeight-10} a 5 5 0 0 1 5 -5` + ) + ) + this.__iconShadeBorder__.style.display = hideLabel?'none':'' this.__iconShadeBorder__.setAttribute("d", - "M " + (((!d._def.align && d.inputs !== 0 && d.outputs === 0) || "right" === d._def.align) ? 0 : 30) + " 1 l 0 " + (d.h - 2) + "M " + (((!d._def.align && d.inputs !== 0 && d.outputs === 0) || "right" === d._def.align) ? 0.5 : 29.5) + " "+(d.selected?1:0.5)+" l 0 " + (d.h - (d.selected?2:1)) ); faIcon.attr("y",(d.h+13)/2); } From 4c784af55db40455e77274c24eadfd58b78fffba Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 12 Jul 2022 21:12:00 +0100 Subject: [PATCH 103/237] Update dependencies --- package.json | 20 +++++++++---------- .../@node-red/editor-api/package.json | 2 +- .../@node-red/registry/package.json | 2 +- .../node_modules/@node-red/util/package.json | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 1f5759634..e585567ed 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "hash-sum": "2.0.0", "hpagent": "1.0.0", "https-proxy-agent": "5.0.1", - "i18next": "21.8.10", + "i18next": "21.8.13", "iconv-lite": "0.6.3", "is-utf8": "0.2.1", "js-yaml": "4.1.0", @@ -59,7 +59,7 @@ "media-typer": "1.1.0", "memorystore": "1.6.7", "mime": "3.0.0", - "moment": "2.29.3", + "moment": "2.29.4", "moment-timezone": "0.5.34", "mqtt": "4.3.7", "multer": "1.4.5-lts.1", @@ -69,14 +69,14 @@ "nopt": "5.0.0", "oauth2orize": "1.11.1", "on-headers": "1.0.2", - "passport": "0.5.2", + "passport": "0.6.0", "passport-http-bearer": "1.0.1", "passport-oauth2-client-password": "0.1.2", "raw-body": "2.5.1", "semver": "7.3.7", "tar": "6.1.11", "tough-cookie": "4.0.0", - "uglify-js": "3.16.0", + "uglify-js": "3.16.2", "uuid": "8.3.2", "ws": "7.5.6", "xml2js": "0.4.23" @@ -85,7 +85,7 @@ "bcrypt": "5.0.1" }, "devDependencies": { - "dompurify": "2.3.8", + "dompurify": "2.3.9", "grunt": "1.5.3", "grunt-chmod": "~1.1.1", "grunt-cli": "~1.4.3", @@ -95,7 +95,7 @@ "grunt-contrib-concat": "2.1.0", "grunt-contrib-copy": "1.0.0", "grunt-contrib-jshint": "3.2.0", - "grunt-contrib-uglify": "5.2.1", + "grunt-contrib-uglify": "5.2.2", "grunt-contrib-watch": "1.1.0", "grunt-jsdoc": "2.4.1", "grunt-jsdoc-to-markdown": "6.0.0", @@ -108,17 +108,17 @@ "i18next-http-backend": "1.4.1", "jquery-i18next": "1.2.1", "jsdoc-nr-template": "github:node-red/jsdoc-nr-template", - "marked": "4.0.17", + "marked": "4.0.18", "minami": "1.2.3", "mocha": "9.2.2", "node-red-node-test-helper": "^0.3.0", - "nodemon": "2.0.16", + "nodemon": "2.0.19", "proxy": "^1.0.2", - "sass": "1.52.3", + "sass": "1.53.0", "should": "13.2.3", "sinon": "11.1.2", "stoppable": "^1.1.0", - "supertest": "6.2.3" + "supertest": "6.2.4" }, "engines": { "node": ">=14" diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index 777e15cc3..9056b0a1d 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -31,7 +31,7 @@ "oauth2orize": "1.11.1", "passport-http-bearer": "1.0.1", "passport-oauth2-client-password": "0.1.2", - "passport": "0.5.2", + "passport": "0.6.0", "ws": "7.5.6" }, "optionalDependencies": { diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index f4808c8ad..2aa0504e1 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -21,6 +21,6 @@ "fs-extra": "10.1.0", "semver": "7.3.7", "tar": "6.1.11", - "uglify-js": "3.16.0" + "uglify-js": "3.16.2" } } diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index 6723b9b6c..e4fdb92c0 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -16,11 +16,11 @@ ], "dependencies": { "fs-extra": "10.1.0", - "i18next": "21.8.10", + "i18next": "21.8.13", "json-stringify-safe": "5.0.1", "jsonata": "1.8.6", "lodash.clonedeep": "^4.5.0", - "moment": "2.29.3", + "moment": "2.29.4", "moment-timezone": "0.5.34" } } From 613d34e6e62dfcb1fa9977c324e2dd67697c2cb1 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 12 Jul 2022 21:12:31 +0100 Subject: [PATCH 104/237] Update tourGuide for 3.0 --- .../editor-client/src/js/ui/tour/tourGuide.js | 6 +++--- .../@node-red/editor-client/src/tours/welcome.js | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js index 913582c10..406715651 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js @@ -437,17 +437,17 @@ RED.tourGuide = (function() { return [ { id: "3_0", - label: "3.0.0-beta.4", + label: "3.0", path: "./tours/welcome.js" }, { id: "2_2", - label: "2.2.0", + label: "2.2", path: "./tours/2.2/welcome.js" }, { id: "2_1", - label: "2.1.0", + label: "2.1", path: "./tours/2.1/welcome.js" } ]; diff --git a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js index 35a276606..7d095ba8c 100644 --- a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js +++ b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js @@ -1,15 +1,15 @@ export default { - version: "3.0.0-beta.4", + version: "3.0.0", steps: [ { titleIcon: "fa fa-map-o", title: { - "en-US": "Welcome to Node-RED 3.0 Beta 4!", - "ja": "Node-RED 3.0 ベータ4へようこそ!" + "en-US": "Welcome to Node-RED 3.0!", + "ja": "Node-RED 3.0へようこそ!" }, description: { - "en-US": "

    This is another final beta release of Node-RED 3.0.

    Let's take a moment to discover the new features in this release.

    ", - "ja": "

    これはNode-RED 3.0のもう一つの最後のベータリリースです。

    本リリースの新機能を見つけてみましょう。

    " + "en-US": "

    Let's take a moment to discover the new features in this release.

    ", + "ja": "

    本リリースの新機能を見つけてみましょう。

    " } }, { From ee378ea0c4b0b809f0376601420a872c52477cc3 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 12 Jul 2022 21:18:49 +0100 Subject: [PATCH 105/237] Update version to 3.0 --- package.json | 2 +- .../node_modules/@node-red/editor-api/package.json | 6 +++--- .../node_modules/@node-red/editor-client/package.json | 2 +- packages/node_modules/@node-red/nodes/package.json | 2 +- packages/node_modules/@node-red/registry/package.json | 4 ++-- packages/node_modules/@node-red/runtime/package.json | 6 +++--- packages/node_modules/@node-red/util/package.json | 2 +- packages/node_modules/node-red/package.json | 10 +++++----- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index e585567ed..448501d98 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "3.0.0-beta.4", + "version": "3.0.0", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index 9056b0a1d..fd2f7d469 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-api", - "version": "3.0.0-beta.4", + "version": "3.0.0", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/util": "3.0.0-beta.4", - "@node-red/editor-client": "3.0.0-beta.4", + "@node-red/util": "3.0.0", + "@node-red/editor-client": "3.0.0", "bcryptjs": "2.4.3", "body-parser": "1.20.0", "clone": "2.1.2", diff --git a/packages/node_modules/@node-red/editor-client/package.json b/packages/node_modules/@node-red/editor-client/package.json index 1041c9745..a72fedc96 100644 --- a/packages/node_modules/@node-red/editor-client/package.json +++ b/packages/node_modules/@node-red/editor-client/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-client", - "version": "3.0.0-beta.4", + "version": "3.0.0", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index 68c7099bf..ff0161f86 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/nodes", - "version": "3.0.0-beta.4", + "version": "3.0.0", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index 2aa0504e1..f9ab44be8 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/registry", - "version": "3.0.0-beta.4", + "version": "3.0.0", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,7 +16,7 @@ } ], "dependencies": { - "@node-red/util": "3.0.0-beta.4", + "@node-red/util": "3.0.0", "clone": "2.1.2", "fs-extra": "10.1.0", "semver": "7.3.7", diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json index c6dc2130e..f33cb5175 100644 --- a/packages/node_modules/@node-red/runtime/package.json +++ b/packages/node_modules/@node-red/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/runtime", - "version": "3.0.0-beta.4", + "version": "3.0.0", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/registry": "3.0.0-beta.4", - "@node-red/util": "3.0.0-beta.4", + "@node-red/registry": "3.0.0", + "@node-red/util": "3.0.0", "async-mutex": "0.3.2", "clone": "2.1.2", "express": "4.18.1", diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index e4fdb92c0..e1b9f7aa2 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/util", - "version": "3.0.0-beta.4", + "version": "3.0.0", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/node-red/package.json b/packages/node_modules/node-red/package.json index 3457e03af..dfd98f6fb 100644 --- a/packages/node_modules/node-red/package.json +++ b/packages/node_modules/node-red/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "3.0.0-beta.4", + "version": "3.0.0", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "flow" ], "dependencies": { - "@node-red/editor-api": "3.0.0-beta.4", - "@node-red/runtime": "3.0.0-beta.4", - "@node-red/util": "3.0.0-beta.4", - "@node-red/nodes": "3.0.0-beta.4", + "@node-red/editor-api": "3.0.0", + "@node-red/runtime": "3.0.0", + "@node-red/util": "3.0.0", + "@node-red/nodes": "3.0.0", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "express": "4.18.1", From f93fe684c0faf23daeddad4887895beac8011c50 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 12 Jul 2022 21:26:16 +0100 Subject: [PATCH 106/237] Update changelog --- CHANGELOG.md | 558 ++++----------------------------------------------- 1 file changed, 36 insertions(+), 522 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb99d8e0e..37fba1de7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,39 @@ +#### 3.0.0: Milestone Release + +Editor + + - Focus editor for undo after some actions in menu (#3759) @kazuhitoyokoi + - Ensure node icon shade has properly rounded corners (#3763) @knolleary + - Fix storing subflow credential type when input has multiple types (#3762) @knolleary + - Ensure global-config and flow-config have info in the hierarchy popover (#3752) @Steve-Mcl + - Include dirty state in history event (#3748) @Steve-Mcl + - Fix display direction of context sub-menu (#3746) @knolleary + - Fix clear pinned paths of debug sidebar menu (#3745) @HiroyasuNishiyama + - prevent exception generating tooltip for deleted nodes (#3742) @Steve-Mcl + - Fix context menu issues ready for v3 beta.5 (#3741) @Steve-Mcl + - Do not generate new node-ids when pasting a cut flow (#3729) @knolleary + - Fix to prevent node from moving out of workspace (#3731) @HiroyasuNishiyama + - Don't let themes change disabled config node background color (#3736) @bonanitech + - Move colors left behind in #3692 to CSS variables (#3737) @bonanitech + - Fix handling of global debug message (#3733) @HiroyasuNishiyama + - Fix label overflow @ config-node palette (#3730) @ralphwetzel + - Fix defaulting to monaco if settings does not contain codeEditor (#3732) @knolleary + - Disable keyboard shortcut mapping when showing Edit[..]Dialog (#3700) @ralphwetzel + - Update add-junction menu to work in more cases (#3727) @knolleary + - Ensure importMap is not null when using import UI (#3723) @Steve-Mcl + - Add Japanese translations for v3.0-beta.4 (#3724) @kazuhitoyokoi + +Runtime + + - Do not remove unknown credentials of Subflow Modules (#3728) @knolleary + - Add missing entries from beta.4 changelog (#3721) @knolleary + +Nodes + + - Change: Fix change node, not handling from field properly when using context (#3754) @Fadoli + - Link Call: Fix linkcall registry bugs (#3751) @Steve-Mcl + - WebSocket: Fix close timeout of websocket node (#3734) @HiroyasuNishiyama + #### 3.0.0-beta.4: Beta Release Editor @@ -185,528 +221,6 @@ Nodes - Watch: Update Watch node to use node-watch module (#3559 #3569) @knolleary - WebSocket: call done after ws disconnects (#3531) @Steve-Mcl - -#### 2.2.2: Maintenance Release - -Nodes - - - Fix "close timed out" error when performing full deploy or modifying broker node. (#3451) @Steve-Mcl - - -#### 2.2.1: Maintenance Release - -Editor - - - Handle mixed-cased filter terms in keyboard shortcut dialog (#3444) @knolleary - - Prevent duplicate links being added between nodes (#3442) @knolleary - - Fix to hide tooltip after removing subflow tab (#3391) @HiroyasuNishiyama - - Fix node validation to be applied to config node (#3397) @HiroyasuNishiyama - - Fix: Dont add wires to undo buffer twice (#3437) @Steve-Mcl - -Runtime - - - Improve module location parsing (of stack info) when adding hook (#3447) @Steve-Mcl - - Fix substitution of NR_NODE_PATH (#3445) @HiroyasuNishiyama - - Remove console.log when ignoring disabled module (#3439) @knolleary - - Improve "Unexpected Node Error" logging (#3446) @Steve-Mcl - -Nodes - - - Debug: Fix no-prototype-builtins bug in debug node and utils (#3394) @Alkarex - - Delay: Fix Japanese message of delay node (#3434) - - Allow nbRateUnits to be undefined when validating (#3443) @knolleary - - Coding help for recently added node-red Predefined Environment Variables (#3440) @Steve-Mcl - - -#### 2.2.0: Milestone Release - -Editor - - - Add editorTheme.tours property to default settings file (#3375) @knolleary - - Remember Zoom level and Sidebar tab selection between sessions (#3361) @knolleary - - Fix timing issue when merging background changes fixes #3364 (#3373) @Steve-Mcl - - Use a nodes palette label in help tree (#3372) @Steve-Mcl - - Subflow: Add labels to OUTPUT nodes (#3352) @ralphwetzel - - Fix vertical align subflow port (#3370) @knolleary - - Make actions list i18n ready and Japanese translation (#3359) @HiroyasuNishiyama - - Update tour for 2.2.0 (#3378) @knolleary - - Include paletteLabel when building search index (#3380) @Steve-Mcl - - Fix opening/closing subflow template not to make subflow changed (#3382) @HiroyasuNishiyama - - Add Japanese translations for v2.2.0 (#3353, #3381) @kazuhitoyokoi - -Runtime - - - Add support for accessing node id & name as environment variable (#3356) @HiroyasuNishiyama - - Clear context contents when switching projects (#3243) @knolleary - -Nodes - - - MQTT: reject invalid topics (#3374) @Steve-Mcl - - Function: Expose node.path property (#3371) @knolleary - - Function: Update `node` declarations in func.d.ts (#3377) @Steve-Mcl - -#### 2.2.0-beta.1: Beta Release - -Editor - - - Add search history to main search box (#3262) @knolleary - - Check availability of type of config node on deploy (#3304) @k-toumura - - Add wire-slice mode to delete wires with Ctrl-RHClick-Drag (#3340) @knolleary - - Wiring keyboard shortcuts (#3288) @knolleary - - Snap nodes on grid using either edge as reference (#3289) @knolleary - - Detach node action (#3338) @knolleary - - Highlight links when selecting nodes (#3323) @knolleary - - Allow multiple links to be selected by ctrl-click (#3294) @knolleary - -Nodes - - - JSON: Let JSON node attempt to parse buffer if it contains a valid string (#3296) @dceejay - - Remove use of verbose flag in core nodes - and use node.debug level instead (#3300) @dceejay - - TCP: Add TLS option to tcp client nodes (#3307) @dceejay - - WebSocket: Implemented support for Websocket Subprotocols in WS Client Node. (#3333) @tobiasoort - -#### 2.1.6: Maintenance Release - -Editor - - - Revert copy-text change and apply alternative fix (#3363) @knolleary - - Update marked to latest (#3362) @knolleary - - fix to make start of property error tooltip messages aligned (#3358) @HiroyasuNishiyama - -Nodes - - - Inject: fix JSON propety validation of inject node (#3349) @HiroyasuNishiyama - - Delay: fix unit value validation of delay node (#3351) @HiroyasuNishiyama - -#### 2.1.5: Maintenance Release - -Runtime - - - Handle reporting error location when stack is truncated (#3346) @knolleary - - Initialize passport when only adminAuth.tokens is set (#3343) @knolleary - - Add log logging (#3342) @knolleary - -Editor - - - Fix copy buttons on the debug window (another method) (#3331) @kazuhitoyokoi - - Add Japanese translations for hidden flow (#3302) @kazuhitoyokoi - - Improve jsonata legacy mode detection regex (#3345) @knolleary - - Fix generating flow name with incrementing number (#3347) @knolleary - - resume focus after import/export dialog close (#3337) @HiroyasuNishiyama - - Fix findPreviousVisibleTab action (#3321) @knolleary - - Fix storing hidden tab state when not hidden via action (#3312) @knolleary - - Avoid adding empty env properties to tabs/groups (#3311) @knolleary - - Fix hide icon in tour guide (#3301) @kazuhitoyokoi - -Nodes - - - File: Update file node examples according to node name change (#3335) @HiroyasuNishiyama - - Filter (RBE): Fix for filter node narrrowbandEq mode start condition failure (#3339) @dceejay - - Function: Prevent function scrollbar from obscuring expand button (#3348) @knolleary - - Function: load extralibs when expanding monaco. fixes #3319 (#3334) @Steve-Mcl - - Function: Update Function to use correct api to access env vars (#3310) @knolleary - - HTTP Request: Fix basic auth with empty username or password (#3325) @hardillb - - Inject: Fix incorrect clearing of blank payload property in Inject node (#3322) @knolleary - - Link Call: add link call example (#3336) @HiroyasuNishiyama - - WebSocket: Only setup ws client heartbeat once it is connected (#3344) @knolleary - - Update Japanese translations in node help (#3332) @kazuhitoyokoi - -#### 2.1.4: Maintenance Release - -Runtime - - - fix env var access using $parent for groups (#3278) @HiroyasuNishiyama - - Add proper error handling for 404 errors when serving debug files (#3277) @knolleary - - Add Japanese translations for Node-RED v2.1.0-beta.1 (#3179) @kazuhitoyokoi - - Include full user object on login audit events (#3269) @knolleary - - Remove styling from de locale files (#3237) @knolleary - -Editor - - - Change tab hide button icon to an eye and add search option (#3282) @knolleary - - Fix i18n handling of namespaces with spaces in (#3281) @knolleary - - Trigger change event when autoComplete fills in input (#3280) @knolleary - - Apply CN i18n fix (#3279) @knolleary - - fix select menu label of config node to use paletteLabel (#3273) @HiroyasuNishiyama - - fix removed tab not to cause node conflict (#3275) @HiroyasuNishiyama - - Group diff fix (#3239) @knolleary - - Only toggle disabled workspace flag if on activeWorkspace (#3252) @knolleary - - Do not show status for disabled nodes (#3253) @knolleary - - Set dimension value for tour guide (#3265) @kazuhitoyokoi - - Avoid redundant initialisation of TypedInput type (#3263) @knolleary - - Don't let themes change flow port label color (#3270) @bonanitech - - Fix treeList gutter calculation to handle floating gutters (#3238) @knolleary - -Nodes - -- Debug: Handle RegExp types in Debug sidebar (#3251) @knolleary -- Delay: fix 2nd output when in rate limit per topic modes (#3261) @dceejay -- Link: fix to show link target when selected (#3267) @HiroyasuNishiyama -- Inject: Do not modify inject node props in oneditprepare (#3242) @knolleary -- HTTP Request: HTTP Basic Auth should always add : between username and password even if empty (#3236) @hardillb - -#### 2.1.3: Maintenance Release - -Runtime - - - Update gen-publish script to update 'next' tag for main releases - - Add environment variable to enable/disable tours (#3221) @hardillb - - Fix loading non-default language files leaving runtime in wrong locale (#3225) @knolleary - -Editor - - - Refresh editor settings whenever a node is added or enabled (#3227) @knolleary - - Revert spinner css change that made it shrink in some cases (#3229) @knolleary - - Fix import notification message when importing config nodes (#3224) @knolleary - - Handle changing types of TypedInput repeatedly (#3223) @knolleary - - -#### 2.1.2: Maintenance Release - - -Runtime - - - node-red-pi: Remove bash dependency (#3216) @a16bitsysop - -Editor - - - Improved regex for markdown renderer (#3213) @GerwinvBeek - - Fix TypedInput initialisation (#3220) @knolleary - -Nodes - - - MQTT: fix datatype in node config not used. fixes #3215 (#3219) @Steve-Mcl - -#### 2.1.1: Maintenance Release - -Editor - - - Ensure tourGuide popover doesn't fall offscreen (#3212) @knolleary - - Fix issue with old inject nodes that migrated topic to 'string' type (#3210) @knolleary - - Add cache-busting query params to index.mst (#3211) @knolleary - - Fix TypedInput validation of type without options (#3207) @knolleary - -#### 2.1.0: Milestone Release - -Editor - - - Position popover properly on a scrolled page - - Fixes from 2.1.0-beta.2 (#3202) @knolleary - -Nodes - -- Link Out: Fix saving link out node links (#3201) @knolleary - - Switch: Refix #3170 - copy switch rule type when adding new rule - - TCP Request: Add string option to TCP request node output (#3204) @dceejay - -#### 2.1.0-beta.2: Beta Release - -Editor - - - Fix switching projects (#3199) @knolleary - - Use locale setting when installing/enabling node (#3198) @knolleary - - Do not show projects-wecome dialog until welcome tour completes (#3197) @knolleary - - Fix converting selection to subflow (#3196) @knolleary - - Avoid conflicts with native browser cmd-ctrl type shortcuts (#3195) @knolleary - - Ensure message tools stay attached to top-level entry in Debug/Context (#3186) @knolleary - - Ensure tab state updates properly when toggling enable state (#3175) @knolleary - - Improve handling of long labels in TreeList (#3176) @knolleary - - Shift-click tab scroll arrows to jump to start/end (#3177) @knolleary - -Runtime - - - Update package dependencies - - Update to latest node-red-admin - -Nodes - - - Dynamic MQTT connections (#3189) - - Link: Filter out Link Out Return nodes in Link In edit dialog Fixes #3187 - - Link: Fix link call label (#3200) @knolleary - - Debug: Redesign debug filter options and make them persistant (#3183) @knolleary - - Inject: Widen Inject interval box for >1 digit (#3184) @knolleary - - Switch: Fix rule focus when switch 'otherwise' rule is used (#3185) @knolleary - -#### 2.1.0-beta.1: Beta Release - -Editor - - - Add Tour Guide component (#3136) @knolleary - - Allow tabs to be hidden (#3120) @knolleary - - Add align actions to editor (#3110) @knolleary - - Add support of environment variable for tab & group (#3112) @HiroyasuNishiyama - - Add autoComplete widget and add to TypedInput for msg. props (#3171) @knolleary - - Render node documentation to node-red style guide when written in markdown. (#3169) @Steve-Mcl - - Allow colouring of tab icon svg (#3140) @harmonic7 - - Restore tab selection after merging conflicts (#3151) @GerwinvBeek - - Fix serving of theme files on Windows (#3154) @knolleary - - Ensure config-node select inherits width properly from input (#3155) @knolleary - - Do better remembering TypedInput values whilst switching types (#3159) @knolleary - - Update monaco to 0.28.1 (#3153) @knolleary - - Improve themeing of tourGuide (#3161) @knolleary - - Allow a node to specify a filter for the config nodes it can pick from (#3160) @knolleary - - Allow RED.notify.update to modify any notification setting (#3163) @knolleary - - Fix typo in ko editor.json Fixes #3119 - - Improve RED.actions api to ensure actions cannot be overridden - - Ensure treeList row has suitable min-height when no content Fixes #3109 - - Refactor edit dialogs to use separate edit panes - - Ensure type select button is not focussable when TypedInput only has one type - - Place close tab link in front of fade - -Runtime - - - Improve error reporting with oauth login strategies (#3148) @knolleary - - Add allowUpdate feature to externalModules.palette (#3143) @knolleary - - Improve node install error reporting (#3158) @knolleary - - Improve unit test coverage (#3168) @knolleary - - Allow coreNodesDir to be set to false (#3149) @hardillb - - Update package dependencies - - uncaughtException debug improvements (#3146) @renatojuniorrs - -Nodes - - - Change: Add option to deep-clone properties in Change node (#3156) @knolleary - - Delay: Add push to front of rate limit queue. (#3069) @dceejay - - File: Add paletteLabel to file nodes to make read/write more obvious (#3157) @knolleary - - HTTP Request: Extend HTTP request node to log detailed timing information (#3116) @k-toumura - - HTTP Response: Fix sizing of HTTP Response header fields (#3164) @knolleary - - Join: Support for msg.restartTimeout (#3121) @magma1447 - - Link Call: Add Link Call node (#3152) @knolleary - - Switch: Copy previous rule type when adding rule to switch node (#3170) @knolleary - - Delay node: add option to send intermediate messages on separate output (#3166) @knolleary - - Typo in http request set method translation (#3173) @mailsvb - -#### 2.0.6: Maintenance Release - -Editor - - - Fix typo in ko editor.json Fixes #3119 - - Change fade color when hovering an inactive tab (#3106) @bonanitech - - Ensure treeList row has suitable min-height when no content Fixes #3109 - -Runtime - - - Update tar to latest (#3128) @aksswami - - Give passport verify callback the same arity as the original callback (#3117) @dschmidt - - Handle HTTPS Key and certificate as string or buffer (#3115) @bartbutenaers - -#### 2.0.5: Maintenance Release - -Editor - - - Remove default ctrl-enter keybinding from monaco editor Fixes #3093 - -Runtime - - - Update tar dependency - - Add support for maintenance streams in generate-publish-script - - -Nodes - - - Fix regression in Join node when manual joining array with msg.parts present Fixes #3096 - -#### 2.0.4: Maintenance Release - -Editor - - - Fix tab fade CSS for when using themes (#3085) @bonanitech - - Handle just-copied-but-not-deployed node with credentials in editor Fixes #3090 - -Nodes - - - Filter: Fix RBE node handling of default topi property Fixes #3087 - - HTTP Request: Handle partially encoded url query strings in request node - - HTTP Request: Fix support for supplied CA certs (#3089) @hardillb - - HTTP Request: Ensure TLS Cert is used (#3092) @hardillb - - Inject: Fix inject now button unable to send empty props - - Inject: Inject now button success notification should use label with updated props - -#### 2.0.3: Maintenance Release - -Nodes - - - HTML: Fix HTML parsing when body is included in the select tag Fixes #3079 - - HTTP Request: Preserve case of user-provided http headers in request node Fixes #3081 - - HTTP Request: Set decompress to false for HTTP Request to keep 1.x compatibility Fixes #3083 - - HTTP Request: Add unit tests for HTTP Request encodeURI and error response - - HTTP Request: Do not throw HTTP errors in request node Fixes #3082 - - HTTP Request: Ensure uri is properly encoded before passing to got module Fixes #3080 - -#### 2.0.2: Maintenance Release - -Runtime - - - Use file:// url with dynamic import - - Detect if agent-base has patched https.request and undo it Fixes #3072 - -Editor - - - Fix tab fade css because Safari Fixes #3073 - - Fix error closing library dialog with monaco - - Handle other error types in Manage Palette view - - -Nodes - - - HTTP Request node - ignore invalid cookies rather than fail request Fixes #3075 - - Fix msg.reset handling in Delay node Fixes #3074 - -#### 2.0.1: Maintenance Release - -Nodes - - - Function: Ensure default module export is exposed in Function node - -#### 2.0.0: Milestone Release - -**Migration from 1.x** - - - Node-RED now requires Node.js 12.x or later. - - - The following nodes have had significant dependency updates. Unless stated, - they should be fully backward compatible. - - - RBE: Relabelled as 'filter' to make it more discoverable and made part of - the core palette, rather than as a separate module. - - Tail: This node has been removed from the default palette. You can reinstall it - from node-red-node-tail - - HTTP Request: Reimplemented with a different underlying module. We have - tried to maintain 100% functional compatibility, but it is possible - some edge cases remain. - - JSON: The schema validation option no longer supports JSON-Schema draft-04 - - HTML: Its underlying module has had a major version update. Should be fully - backward compatible. - - - `functionExternalModules` is now enabled by default for new installs. - If you have an existing settings file that contains this setting, you will - need to set it to `true` yourself. - - The external modules will now get installed in your Node-RED user directory, - (`~/.node-red`) rather than in a subdirectory. This means all dependencies will - be listed in your top-level `package.json`. If you have existing external modules, - they will get reinstalled to the new location when you first run Node-RED 2.0. - - -Runtime - - - Fix missing dependencies (#3052, #2057) @kazuhitoyokoi - - Ensure node.types is defined if node html file missing - - Fix reporting of type_already_registered error - - Move install location of external modules (#3064) @knolleary - -Editor - - - Update translations (#3063) @kazuhitoyokoi - - Add a slight fade to tab labels that overflow - - Show config node details when selected in outliner - - Fix layout of info outliner for subflow entries - -Nodes - - - Delay: let `msg.flush` specify how many messages to flush from node (#3059) @dceejay - - Function: external modules is now enabled by default (#3065) @knolleary - - Function: external modules now supports both ES6 and CJS modules (#3065) @knolleary - - WebSocket: add option for client node to send automatic pings (#3056) @knolleary - - -##### 2.0.0-beta.2: Beta Release - -Runtime - - - Add `node-red admin init` (via `node-red-admin@2.1.0`) - - Move to GH Actions rather than Travis for build (#3042) @knolleary - -Editor - - - Include hasUser=false config nodes when exporting whole flow (#3048) - - Emit nodes:change for any updated config node when node deleted/added - - Fix padding of compact notification Closes #3045 - - Ensure any html in changelog is escaped before displaying - - Add support for Map/Set property types on Debug (#3040) @knolleary - - Add 'theme' to default settings file - - Add RED.view.annotations api (#3032) @knolleary - - Update monaco editor to V0.25.2 (#3031) @Steve-Mcl - - Lower tray zIndex when overlay tray being opened Fixes #3019 - - Reduce z-Index of Function expand buttons to prevent overlap Part of #3019 - - Ensure RED.clipboard.import displays the right library Fixes #3021 - - Batch messages sent over comms to prevent flooding (#3025) @knolleary - - Allow RED.popover.panel to specify a closeButton to ignore click events on - - Use browser default language for initial page load - - Add css var for node font color - - Fix label padding of toggleButton - - Give sidebar open tab a bit more room for its label - - Various Monaco updates (#3015) @Steve-Mcl - - Log readOnly on startup (#3024) @sammachin - - Translation updates (#3020 #3022) @HiroyasuNishiyama @kazuhitoyokoi - -Nodes - - - HTTP Request: Fix proxy handling (#3044) @hardillb - - HTTP Request: Handle basic auth with @ in username (#3017) @hardillb - - Add Japanese translation for file-in node (#3037 #3039) @kazuhitoyokoi - - File In: Add option for file-in node to include all properties (default off) (#3035) @dceejay - - Exec: add windowsHide option to hide windows under Windows (#3026) @natcl - - Support loading external module sub path Fixes #3023 - -##### 2.0.0-beta.1: Beta Release - - - -Runtime - - - [MAJOR] Set minimum node version to 12. - - [MAJOR] Fix flowfile name to flows.json in settings (#2951) @dceejay - - [MAJOR] Update to latest i18n in editor and runtime (#2940) @knolleary - - [MAJOR] Deprecate usage of httpRoot (#2953) @knolleary - - Add pre/postInstall hooks to npm install handling (#2936) @knolleary - - Add engine-strict flag to npm install args (#2965) @nileio - - Restructure default settings.js to be more organised (#3012) @knolleary - - Ensure httpServerOptions gets applied to ALL the express apps - - Allow RED.settings.set to replace string property with object property - - Update debug tests to handle compact comms format - - Updates to encode/decode message when passed over debug comms link - - Remove all input event listeners on a node once it is closed - - Move hooks to util package - - Rework hooks structure to be a linkedlist - - Update dependencies (#2922) @knolleary - -Editor - - - [MAJOR] Change node id generation to give fixed length values without '.' (#2987) @knolleary - - [MAJOR] Add Monaco code editor (#2971) @Steve-Mcl - - Update to latest Monaco (#3007) @Steve-Mcl - - Update Node-RED Function typings in Monaco (#3008) @Steve-Mcl - - Add css named variables for certain key colours (#2994) @knolleary - - Improve contrast of export dialog JSON font color - - Switch editableList buttons from to
    +
    + + +
    +
    @@ -227,6 +232,7 @@ persist: {value:false}, proxy: {type:"http proxy",required: false, label:RED._("node-red:httpin.proxy-config") }, + insecureHTTPParser: {value: false}, authType: {value: ""}, senderr: {value: false}, headers: { value: [] } @@ -338,6 +344,12 @@ } else { $("#node-input-useProxy").prop("checked", false); } + + if (node.insecureHTTPParser) { + $("node-intput-insecureHTTPParser").prop("checked", true) + } else { + $("node-intput-insecureHTTPParser").prop("checked", false) + } updateProxyOptions(); $("#node-input-useProxy").on("click", function() { updateProxyOptions(); diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js index 6a19d2ed2..b94d59dc7 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js +++ b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js @@ -244,6 +244,10 @@ in your Node-RED user directory (${RED.settings.userDir}). delete options.headers[h]; } }) + + if (node.insecureHTTPParser) { + options.insecureHTTPParser = true + } } ], beforeRedirect: [ diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index 62d5f351f..b38ab3026 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -554,7 +554,8 @@ }, "status": { "requesting": "requesting" - } + }, + "insecureHTTPParser": "Lenient HTTP Header Parsing" }, "websocket": { "label": { From d546a4a15b9c5d14982ce9d17e3e2b7543589fae Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 20 Jul 2022 10:14:06 +0100 Subject: [PATCH 116/237] Remove use of Object.hasOwn Fixes #3778 --- packages/node_modules/@node-red/editor-client/src/js/ui/view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 8c7ea22a3..949d2473f 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -4906,7 +4906,7 @@ RED.view = (function() { if (d._def.button) { var buttonEnabled = isButtonEnabled(d); this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-disabled", !buttonEnabled); - if (RED.runtime && Object.hasOwn(RED.runtime,'started')) { + if (RED.runtime && RED.runtime.started !== undefined) { this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-stopped", !RED.runtime.started); } From e120bad779017682d64bf071bc950946c48db93b Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 20 Jul 2022 10:37:40 +0100 Subject: [PATCH 117/237] Ensure quick-add dialog does not obscure ghost node when shifted --- .../@node-red/editor-client/src/js/ui/typeSearch.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js index fc5b8e99e..989cb78ab 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js @@ -269,8 +269,8 @@ RED.typeSearch = (function() { moveCallback = opts.move; RED.events.emit("type-search:open"); //shade.show(); - if ($("#red-ui-main-container").height() - opts.y - 150 < 0) { - opts.y = opts.y - 235; + if ($("#red-ui-main-container").height() - opts.y - 195 < 0) { + opts.y = opts.y - 275; } dialog.css({left:opts.x+"px",top:opts.y+"px"}).show(); searchResultsDiv.slideDown(300); From 5944fdb5dc1446d227344dec33407998472b7027 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 20 Jul 2022 10:40:20 +0100 Subject: [PATCH 118/237] Properly position quick-add dialog in all cases Fixes #3781 --- .../editor-client/src/js/ui/contextMenu.js | 14 +++++--------- .../@node-red/editor-client/src/js/ui/view.js | 7 +++++-- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js index 66ae2b943..8e1d0d19a 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js @@ -44,14 +44,10 @@ RED.contextMenu = (function () { const canRemoveFromGroup = hasSelection && !!selection.nodes[0].g const offset = $("#red-ui-workspace-chart").offset() - let addX = options.x - offset.left + $("#red-ui-workspace-chart").scrollLeft() - let addY = options.y - offset.top + $("#red-ui-workspace-chart").scrollTop() - - if (RED.view.snapGrid) { - const gridSize = RED.view.gridSize() - addX = gridSize * Math.floor(addX / gridSize) - addY = gridSize * Math.floor(addY / gridSize) - } + // addX/addY must be the position in the workspace accounting for both scroll and scale + // The +5 is because we display the contextMenu -5,-5 to actual click position + let addX = (options.x + 5 - offset.left + $("#red-ui-workspace-chart").scrollLeft()) / RED.view.scale() + let addY = (options.y + 5 - offset.top + $("#red-ui-workspace-chart").scrollTop()) / RED.view.scale() const menuItems = [ { onselect: 'core:show-action-list', onpostselect: function () { } }, @@ -144,7 +140,7 @@ RED.contextMenu = (function () { ($(window).width() -MENU_WIDTH)) { direction = "left"; } - + menu = RED.menu.init({ direction: direction, onpreselect: function() { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 8c7ea22a3..601c625ce 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -1071,12 +1071,15 @@ RED.view = (function() { RED.view.redraw(); } + // `point` is the place in the workspace the mouse has clicked. + // This takes into account scrolling and scaling of the workspace. var ox = point[0]; var oy = point[1]; + // Need to map that to browser location to position the pop-up const offset = $("#red-ui-workspace-chart").offset() - var clientX = ox + offset.left - $("#red-ui-workspace-chart").scrollLeft() - var clientY = oy + offset.top - $("#red-ui-workspace-chart").scrollTop() + var clientX = (ox * scaleFactor) + offset.left - $("#red-ui-workspace-chart").scrollLeft() + var clientY = (oy * scaleFactor) + offset.top - $("#red-ui-workspace-chart").scrollTop() if (RED.settings.get("editor").view['view-snap-grid']) { // eventLayer.append("circle").attr("cx",point[0]).attr("cy",point[1]).attr("r","2").attr('fill','red') From 8d3c5d09f695622bed0507299d0f5f7dd3501c90 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 20 Jul 2022 10:47:57 +0100 Subject: [PATCH 119/237] Do not flag hasUsers=false nodes as unused in search Fixes #3777 --- .../@node-red/editor-client/src/js/ui/search.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/search.js b/packages/node_modules/@node-red/editor-client/src/js/ui/search.js index 217fb5a4e..f9cc79f08 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/search.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/search.js @@ -187,7 +187,7 @@ RED.search = (function() { } if (flags.hasOwnProperty("unused")) { var isUnused = (node.node.type === 'subflow' && node.node.instances.length === 0) || - (isConfigNode && node.node.users.length === 0) + (isConfigNode && node.node.users.length === 0 && node.node._def.hasUsers !== false) if (flags.unused !== isUnused) { continue; } @@ -538,7 +538,7 @@ RED.search = (function() { $(previousActiveElement).trigger("focus"); } previousActiveElement = null; - } + } if(!keepSearchToolbar) { clearActiveSearch(); } @@ -630,7 +630,7 @@ RED.search = (function() { $("#red-ui-sidebar-shade").on('mousedown',hide); $("#red-ui-view-searchtools-close").on("click", function close() { - clearActiveSearch(); + clearActiveSearch(); updateSearchToolbar(); }); $("#red-ui-view-searchtools-close").trigger("click"); From 39b2fe45a5639824ce58583227f92fc35ffc4a30 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 20 Jul 2022 11:11:44 +0100 Subject: [PATCH 120/237] Ensure all typedInput buttons have button type set Fixes #3780 Otherwise they act as type="submit" and the browser will click on them when enter is pressed in an input --- .../editor-client/src/js/ui/common/typedInput.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js index 549479d85..ef1be05bc 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js @@ -90,10 +90,10 @@ optEl.append(generateSpans(srcMatch)); optEl.appendTo(element); } - matches.push({ - value: optVal, - label: element, - i: (valMatch.found ? valMatch.index : srcMatch.index) + matches.push({ + value: optVal, + label: element, + i: (valMatch.found ? valMatch.index : srcMatch.index) }); } }) @@ -501,7 +501,7 @@ this.options.types = this.options.types||Object.keys(allOptions); } - this.selectTrigger = $('').prependTo(this.uiSelect); + this.selectTrigger = $('').prependTo(this.uiSelect); $('').toggle(this.options.types.length > 1).appendTo(this.selectTrigger); this.selectLabel = $('').appendTo(this.selectTrigger); @@ -570,7 +570,7 @@ }) // explicitly set optionSelectTrigger display to inline-block otherwise jQ sets it to 'inline' - this.optionSelectTrigger = $('').appendTo(this.uiSelect); + this.optionSelectTrigger = $('').appendTo(this.uiSelect); this.optionSelectLabel = $('').prependTo(this.optionSelectTrigger); // RED.popover.tooltip(this.optionSelectLabel,function() { // return that.optionValue; @@ -591,7 +591,7 @@ that.uiSelect.addClass('red-ui-typedInput-focus'); }); - this.optionExpandButton = $('').appendTo(this.uiSelect); + this.optionExpandButton = $('').appendTo(this.uiSelect); this.optionExpandButtonIcon = $('').appendTo(this.optionExpandButton); this.type(this.typeField.val() || this.options.default||this.typeList[0].value); From 7c5413e568eab28c1fe892c51e39557492cbaaef Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Wed, 20 Jul 2022 12:29:16 +0100 Subject: [PATCH 121/237] ensure red-ui-editor-stack is focusable --- packages/node_modules/@node-red/editor-client/src/js/red.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/red.js b/packages/node_modules/@node-red/editor-client/src/js/red.js index 55446418b..7544434b7 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/red.js +++ b/packages/node_modules/@node-red/editor-client/src/js/red.js @@ -766,7 +766,7 @@ var RED = (function() { $('
    ').appendTo(header); $('
    '+ '
    '+ - '
    '+ + '
    '+ '
    '+ '
    '+ '
    '+ From 1b53b5b92792b714600869f5d0b8822f1e140079 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Wed, 20 Jul 2022 12:30:15 +0100 Subject: [PATCH 122/237] focus stack when re-showing nested editor --- .../node_modules/@node-red/editor-client/src/js/ui/editor.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js index fb4c200f5..be114c4a8 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js @@ -1105,6 +1105,10 @@ RED.editor = (function() { if (editing_node) { RED.sidebar.info.refresh(editing_node); RED.sidebar.help.show(editing_node.type, false); + //ensure focused element is NOT body (for keyboard scope to operate correctly) + if (document.activeElement.tagName === 'BODY') { + $('#red-ui-editor-stack').trigger('focus') + } } } } From 273404e24df2ca863ef7397a82cc1b344fed50ac Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Wed, 20 Jul 2022 12:30:52 +0100 Subject: [PATCH 123/237] focus search input when opened via context menu --- .../@node-red/editor-client/src/js/ui/contextMenu.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js index 66ae2b943..1ace19a75 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js @@ -67,6 +67,10 @@ RED.contextMenu = (function () { splice: isSingleLink ? selection.links[0] : undefined, // spliceMultiple: isMultipleLinks }) + }, + onpostselect: function() { + // ensure quick add dialog search input has focus + $('#red-ui-type-search-input').trigger('focus') } }, (hasLinks) ? { // has least 1 wire selected From f7120b32f54cb1a919e82b3e25b25c7b404418f5 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Wed, 20 Jul 2022 13:58:03 +0100 Subject: [PATCH 124/237] Allow `mode` and `title` to be empty fixes #3774 --- .../editor-client/src/js/ui/editors/code-editors/monaco.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js index 701e3da44..497116125 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js @@ -764,7 +764,7 @@ RED.editor.codeEditor.monaco = (function() { if(!options.stateId && options.stateId !== false) { - options.stateId = RED.editor.generateViewStateId("monaco", options, (options.mode || options.title).split("/").pop()); + options.stateId = RED.editor.generateViewStateId("monaco", options, (options.mode || options.title || "").split("/").pop()); } var el = options.element || $("#"+options.id)[0]; var toolbarRow = $("
    ").appendTo(el); From af4f07cb263f98d27ed424fca48a3eb1212afa6a Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Thu, 21 Jul 2022 09:29:51 +0100 Subject: [PATCH 125/237] include flows stop/start state --- .../node_modules/@node-red/runtime/lib/api/diagnostics.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/runtime/lib/api/diagnostics.js b/packages/node_modules/@node-red/runtime/lib/api/diagnostics.js index cbf9fc9d7..443751ecc 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/diagnostics.js +++ b/packages/node_modules/@node-red/runtime/lib/api/diagnostics.js @@ -100,9 +100,13 @@ function buildDiagnosticReport(scope, callback) { version: os.version(), }, runtime: { - isStarted: runtime.isStarted(), - modules: modules, version: runtime.settings.version, + isStarted: runtime.isStarted(), + flows: { + state: runtime.flows && runtime.flows.state(), + started: runtime.flows && runtime.flows.started, + }, + modules: modules, settings: { available: runtime.settings.available(), apiMaxLength: runtime.settings.apiMaxLength || "UNSET", From a4d66622a5130728e5391e0478db8fa6746e4d45 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Thu, 21 Jul 2022 09:30:49 +0100 Subject: [PATCH 126/237] add a handful of missing settings to basic report --- .../node_modules/@node-red/runtime/lib/api/diagnostics.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/node_modules/@node-red/runtime/lib/api/diagnostics.js b/packages/node_modules/@node-red/runtime/lib/api/diagnostics.js index 443751ecc..b0debf96e 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/diagnostics.js +++ b/packages/node_modules/@node-red/runtime/lib/api/diagnostics.js @@ -118,6 +118,11 @@ function buildDiagnosticReport(scope, callback) { flowFile: runtime.settings.flowFile || "UNSET", mqttReconnectTime: runtime.settings.mqttReconnectTime || "UNSET", serialReconnectTime: runtime.settings.serialReconnectTime || "UNSET", + socketReconnectTime: runtime.settings.socketReconnectTime || "UNSET", + socketTimeout: runtime.settings.socketTimeout || "UNSET", + tcpMsgQueueSize: runtime.settings.tcpMsgQueueSize || "UNSET", + inboundWebSocketTimeout: runtime.settings.inboundWebSocketTimeout || "UNSET", + runtimeState: runtime.settings.runtimeState || "UNSET", adminAuth: runtime.settings.adminAuth ? "SET" : "UNSET", @@ -135,6 +140,7 @@ function buildDiagnosticReport(scope, callback) { uiHost: runtime.settings.uiHost ? "SET" : "UNSET", uiPort: runtime.settings.uiPort ? "SET" : "UNSET", userDir: runtime.settings.userDir ? "SET" : "UNSET", + nodesDir: runtime.settings.nodesDir && runtime.settings.nodesDir.length ? "SET" : "UNSET", } } } From bcd31610f69c81edf6e2abc5cb625457964eae25 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Thu, 21 Jul 2022 10:07:40 +0100 Subject: [PATCH 127/237] update tests for sys info diagnostics ammendments --- .../runtime/lib/api/diagnostics_spec.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/test/unit/@node-red/runtime/lib/api/diagnostics_spec.js b/test/unit/@node-red/runtime/lib/api/diagnostics_spec.js index 07e499344..136e4826c 100644 --- a/test/unit/@node-red/runtime/lib/api/diagnostics_spec.js +++ b/test/unit/@node-red/runtime/lib/api/diagnostics_spec.js @@ -33,6 +33,11 @@ describe("runtime-api/diagnostics", function() { flowFile: "flows.json", mqttReconnectTime: 321, serialReconnectTime: 432, + socketReconnectTime: 2222, + socketTimeout: 3333, + tcpMsgQueueSize: 4444, + inboundWebSocketTimeout: 5555, + runtimeState: {enabled: true, ui: false}, adminAuth: {},//should be sanitised to "SET" httpAdminRoot: "/admin/root/", httpAdminCors: {},//should be sanitised to "SET" @@ -45,6 +50,7 @@ describe("runtime-api/diagnostics", function() { uiHost: "something.secret.com",//should be sanitised to "SET" uiPort: 1337,//should be sanitised to "SET" userDir: "/var/super/secret/",//should be sanitised to "SET", + nodesDir: "/var/super/secret/",//should be sanitised to "SET", contextStorage: { default : { module: "memory" }, file: { module: "localfilesystem" }, @@ -73,8 +79,9 @@ describe("runtime-api/diagnostics", function() { //result.runtime.xxxxx const runtimeCount = Object.keys(result.runtime).length; - runtimeCount.should.eql(4);//ensure no more than 4 keys are present in runtime + runtimeCount.should.eql(5);//ensure 5 keys are present in runtime result.runtime.should.have.property('isStarted',true) + result.runtime.should.have.property('flows') result.runtime.should.have.property('modules').type("object"); result.runtime.should.have.property('settings').type("object"); result.runtime.should.have.property('version','7.7.7'); @@ -87,7 +94,7 @@ describe("runtime-api/diagnostics", function() { //result.runtime.settings.xxxxx const settingsCount = Object.keys(result.runtime.settings).length; - settingsCount.should.eql(21);//ensure no more than the 21 settings listed below are present in the settings object + settingsCount.should.eql(27);//ensure no more than the 21 settings listed below are present in the settings object result.runtime.settings.should.have.property('available',true); result.runtime.settings.should.have.property('apiMaxLength', "UNSET");//deliberately disabled to ensure UNSET is returned result.runtime.settings.should.have.property('debugMaxLength', 1111); @@ -96,6 +103,11 @@ describe("runtime-api/diagnostics", function() { result.runtime.settings.should.have.property('flowFile', "flows.json"); result.runtime.settings.should.have.property('mqttReconnectTime', 321); result.runtime.settings.should.have.property('serialReconnectTime', 432); + result.runtime.settings.should.have.property('socketReconnectTime', 2222); + result.runtime.settings.should.have.property('socketTimeout', 3333); + result.runtime.settings.should.have.property('tcpMsgQueueSize', 4444); + result.runtime.settings.should.have.property('inboundWebSocketTimeout', 5555); + result.runtime.settings.should.have.property('runtimeState', {enabled: true, ui: false}); result.runtime.settings.should.have.property("adminAuth", "SET"); //should be sanitised to "SET" result.runtime.settings.should.have.property("httpAdminCors", "SET"); //should be sanitised to "SET" result.runtime.settings.should.have.property('httpAdminRoot', "/admin/root/"); @@ -109,6 +121,7 @@ describe("runtime-api/diagnostics", function() { result.runtime.settings.should.have.property("uiPort", "SET"); //should be sanitised to "SET" result.runtime.settings.should.have.property("userDir", "SET"); //should be sanitised to "SET" result.runtime.settings.should.have.property('contextStorage').type("object"); + result.runtime.settings.should.have.property('nodesDir', "SET") //result.runtime.settings.contextStorage.xxxxx const contextCount = Object.keys(result.runtime.settings.contextStorage).length; From bc7852c1cc0889ec9f4686d0efca00592a99a0d0 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Thu, 21 Jul 2022 14:44:29 +0100 Subject: [PATCH 128/237] Allow codeEditor theme to be set missing from settings.js --- .../node_modules/@node-red/editor-api/lib/editor/theme.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/theme.js b/packages/node_modules/@node-red/editor-api/lib/editor/theme.js index 7be8868d3..c21a7e6e7 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/theme.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/theme.js @@ -327,9 +327,8 @@ module.exports = { themeContext.header.url = themePlugin.header.url || themeContext.header.url } } - if(theme.codeEditor) { - theme.codeEditor.options = Object.assign({}, themePlugin.monacoOptions, theme.codeEditor.options); - } + theme.codeEditor = theme.codeEditor || {} + theme.codeEditor.options = Object.assign({}, themePlugin.monacoOptions, theme.codeEditor.options); } activeThemeInitialised = true; } From da65bf7292428fe8d9b64bd2bfe2260ec8aa045a Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 22 Jul 2022 09:50:37 +0100 Subject: [PATCH 129/237] Update changelog and bump for 3.0.1 --- CHANGELOG.md | 14 ++++++++++++++ package.json | 2 +- .../node_modules/@node-red/editor-api/package.json | 6 +++--- .../@node-red/editor-client/package.json | 2 +- packages/node_modules/@node-red/nodes/package.json | 2 +- .../node_modules/@node-red/registry/package.json | 4 ++-- .../node_modules/@node-red/runtime/package.json | 6 +++--- packages/node_modules/@node-red/util/package.json | 2 +- packages/node_modules/node-red/package.json | 10 +++++----- 9 files changed, 31 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c6f52aeb..a6a9016d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +#### 3.0.1: Maintenance Release + +Editor + + - Allow codeEditor theme to be set even if `codeEditor` is not set in settings.js (#3794) @Steve-Mcl + - Sys info (diagnostics report) amendments (#3793) @Steve-Mcl + - Allow `mode` and `title` to be omitted in `options` argument for `createEditor` (#3791) @Steve-Mcl + - Fix focus issues (#3789) @Steve-Mcl + - Ensure all typedInput buttons have button type set (#3788) @knolleary + - Do not flag hasUsers=false nodes as unused in search (#3787) @knolleary + - Properly position quick-add dialog in all cases (#3786) @knolleary + - Ensure quick-add dialog does not obscure ghost node when shifted (#3785) @knolleary + - Remove use of Object.hasOwn (#3784) @knolleary + #### 3.0.0: Milestone Release Editor diff --git a/package.json b/package.json index 8b64aa743..d5ab3bf5d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "3.0.0", + "version": "3.0.1", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index fd2f7d469..e95604bab 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-api", - "version": "3.0.0", + "version": "3.0.1", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/util": "3.0.0", - "@node-red/editor-client": "3.0.0", + "@node-red/util": "3.0.1", + "@node-red/editor-client": "3.0.1", "bcryptjs": "2.4.3", "body-parser": "1.20.0", "clone": "2.1.2", diff --git a/packages/node_modules/@node-red/editor-client/package.json b/packages/node_modules/@node-red/editor-client/package.json index a72fedc96..a06359c0c 100644 --- a/packages/node_modules/@node-red/editor-client/package.json +++ b/packages/node_modules/@node-red/editor-client/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-client", - "version": "3.0.0", + "version": "3.0.1", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index ff0161f86..c7a31fa10 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/nodes", - "version": "3.0.0", + "version": "3.0.1", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index f9ab44be8..c8c582ebe 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/registry", - "version": "3.0.0", + "version": "3.0.1", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,7 +16,7 @@ } ], "dependencies": { - "@node-red/util": "3.0.0", + "@node-red/util": "3.0.1", "clone": "2.1.2", "fs-extra": "10.1.0", "semver": "7.3.7", diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json index f33cb5175..8b1991a5c 100644 --- a/packages/node_modules/@node-red/runtime/package.json +++ b/packages/node_modules/@node-red/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/runtime", - "version": "3.0.0", + "version": "3.0.1", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/registry": "3.0.0", - "@node-red/util": "3.0.0", + "@node-red/registry": "3.0.1", + "@node-red/util": "3.0.1", "async-mutex": "0.3.2", "clone": "2.1.2", "express": "4.18.1", diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index 9ce885200..8d677e2cd 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/util", - "version": "3.0.0", + "version": "3.0.1", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/node-red/package.json b/packages/node_modules/node-red/package.json index dfd98f6fb..faa98c48d 100644 --- a/packages/node_modules/node-red/package.json +++ b/packages/node_modules/node-red/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "3.0.0", + "version": "3.0.1", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "flow" ], "dependencies": { - "@node-red/editor-api": "3.0.0", - "@node-red/runtime": "3.0.0", - "@node-red/util": "3.0.0", - "@node-red/nodes": "3.0.0", + "@node-red/editor-api": "3.0.1", + "@node-red/runtime": "3.0.1", + "@node-red/util": "3.0.1", + "@node-red/nodes": "3.0.1", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "express": "4.18.1", From 4fb40f9077d687cde6231b7439e708758a533d92 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sat, 23 Jul 2022 21:39:07 +0900 Subject: [PATCH 130/237] Support color reset to the default in subflow --- .../@node-red/editor-client/src/js/ui/editors/colorPicker.js | 3 +++ .../editor-client/src/js/ui/editors/panes/appearance.js | 1 + .../node_modules/@node-red/editor-client/src/js/ui/group.js | 3 +++ 3 files changed, 7 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/colorPicker.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/colorPicker.js index 4b2e19e5c..5b76d020b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/colorPicker.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/colorPicker.js @@ -76,6 +76,9 @@ RED.editor.colorPicker = RED.colorPicker = (function() { var focusTarget = colorInput; colorInput.on("change", function (e) { var color = colorInput.val(); + if (options.defaultValue && !color.match(/^([a-z]+|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3})$/)) { + color = options.defaultValue; + } colorHiddenInput.val(color).trigger('change'); refreshDisplay(color); }); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js index 912fa3528..e1e6b2ba3 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js @@ -235,6 +235,7 @@ RED.editor.colorPicker.create({ id: "red-ui-editor-node-color", value: color, + defaultValue: "#DDAA99", palette: recommendedColors, sortPalette: function (a, b) {return a.l - b.l;} }).appendTo(colorRow); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/group.js b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js index add6da6c9..a35098e52 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/group.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js @@ -101,6 +101,7 @@ RED.group = (function() { RED.editor.colorPicker.create({ id:"node-input-style-stroke", value: style.stroke || defaultGroupStyle.stroke || "#a4a4a4", + defaultValue: "#a4a4a4", palette: colorPalette, cellPerRow: colorCount, cellWidth: 16, @@ -112,6 +113,7 @@ RED.group = (function() { RED.editor.colorPicker.create({ id:"node-input-style-fill", value: style.fill || defaultGroupStyle.fill ||"none", + defaultValue: "none", palette: colorPalette, cellPerRow: colorCount, cellWidth: 16, @@ -129,6 +131,7 @@ RED.group = (function() { RED.editor.colorPicker.create({ id:"node-input-style-color", value: style.color || defaultGroupStyle.color ||"#a4a4a4", + defaultValue: "#a4a4a4", palette: colorPalette, cellPerRow: colorCount, cellWidth: 16, From b50ba3e0e44c34034cb73603a4a9f81c152062d6 Mon Sep 17 00:00:00 2001 From: Dennis Neufeld Date: Sat, 23 Jul 2022 18:53:39 +0200 Subject: [PATCH 131/237] Update german translation --- .../editor-client/locales/de/editor.json | 77 +++++++++++-------- .../@node-red/runtime/locales/de/runtime.json | 32 ++++---- 2 files changed, 60 insertions(+), 49 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/de/editor.json b/packages/node_modules/@node-red/editor-client/locales/de/editor.json index 41fe1459a..36bf537de 100755 --- a/packages/node_modules/@node-red/editor-client/locales/de/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/de/editor.json @@ -105,7 +105,7 @@ "search": "Flows durchsuchen", "searchInput": "Flows durchsuchen", "subflows": "Subflow", - "createSubflow": "Subflow", + "createSubflow": "Subflow hinzufügen", "selectionToSubflow": "Auswahl in Subflow umwandeln", "flows": "Flow", "add": "Hinzufügen", @@ -152,7 +152,8 @@ "zoom-in": "Vergrößern", "search-flows": "Flows durchsuchen", "search-prev": "Vorherige", - "search-next": "Nächste" + "search-next": "Nächste", + "search-counter": "\"__term__\" __result__ von __count__" }, "user": { "loggedInAs": "Angemeldet als __name__", @@ -168,7 +169,11 @@ } }, "notification": { - "warning": "Warnung: __message__", + "state": { + "flowsStopped": "Flows gestoppt", + "flowsStarted": "Flows gestartet" + }, + "warning": "Warnung: __message__", "warnings": { "undeployedChanges": "Node hat nicht übernommene (deploy) Änderungen", "nodeActionDisabled": "Node-Aktionen deaktiviert", @@ -177,15 +182,15 @@ "missing-modules": "

    Flows angehalten aufgrund fehlender Module

    ", "safe-mode": "

    Flows sind im abgesicherten Modus gestoppt.

    Flows können bearbeitet und übernommen (deploy) werden, um sie neu zu starten.

    ", "restartRequired": "Node-RED muss neu gestartet werden, damit die Module nach Upgrade aktiviert werden", - "credentials_load_failed": "

    Flows gestoppt, da die Berechtigungen nicht entschlüsselt werden konnten.

    Die Datei mit dem Flow-Berechtigungen ist verschlüsselt, aber der Schlüssel des Projekts fehlt oder ist ungültig.

    ", - "credentials_load_failed_reset": "

    Die Berechtigungen konnten nicht entschlüsselt werden.

    Die Datei mit den Flow-Berechtigungen ist verschlüsselt, aber der Schlüssel des Projekts fehlt oder ist ungültig.

    Die Datei mit den Flow-Berechtigungen wird bei der nächsten Übernahme (deploy) zurückgesetzt. Alle vorhandenen Flow-Berechtigungen werden gelöscht.

    ", + "credentials_load_failed": "

    Flows gestoppt, da die Anmeldeinformationen nicht entschlüsselt werden konnten.

    Die Datei mit den Flow-Anmeldeinformationen ist verschlüsselt, aber der Schlüssel des Projekts fehlt oder ist ungültig.

    ", + "credentials_load_failed_reset": "

    Die Anmeldeinformationen konnten nicht entschlüsselt werden.

    Die Datei mit den Flow-Anmeldeinformationen ist verschlüsselt, aber der Schlüssel des Projekts fehlt oder ist ungültig.

    Die Datei mit den Flow-Anmeldeinformationen wird bei der nächsten Übernahme (deploy) zurückgesetzt. Alle vorhandenen Flow-Anmeldeinformationen werden gelöscht.

    ", "missing_flow_file": "

    Die Flow-Datei des Projekts wurde nicht gefunden.

    Das Projekt ist nicht mit einer Flow-Datei konfiguriert.

    ", "missing_package_file": "

    Die Paket-Datei des Projekts wurde nicht gefunden.

    In dem Projekt fehlt die 'package.json'-Datei.

    ", "project_empty": "

    Das Projekt ist leer.

    Soll ein Standardsatz an Projektdateien erstellen werden?
    Andernfalls müssen die Dateien manuell außerhalb des Editors dem Projekt hinzugefügt werden.

    ", "project_not_found": "

    Das Projekt '__project__' wurde nicht gefunden.

    ", "git_merge_conflict": "

    Der automatische Merge der Änderungen ist fehlgeschlagen.

    Die Merge-Konflikte müssen behoben und die Ergebnisse ins Repository übertragen werden (commit).

    " }, - "error": "Fehler: __message__", + "error": "Fehler: __message__", "errors": { "lostConnection": "Verbindung zum Server verloren. Verbindung wird erneut hergestellt ...", "lostConnectionReconnect": "Verbindung zum Server verloren. Wiederherstellung der Verbindung in __time__s.", @@ -203,7 +208,7 @@ "pull": "Projekt '__project__' erneut geladen", "revert": "Änderungen im Projekt '__project__' rückgängig gemacht", "merge-complete": "Git-Merge abgeschlossen", - "setupCredentials": "Berechtigungen einrichten", + "setupCredentials": "Anmeldeinformationen einrichten", "setupProjectFiles": "Projektdateien einrichten", "no": "Nein, Danke", "createDefault": "Standardprojektdateien erstellen", @@ -211,7 +216,7 @@ }, "label": { "manage-project-dep": "Projektabhängigkeiten verwalten", - "setup-cred": "Berechtigungen einrichten", + "setup-cred": "Anmeldeinformationen einrichten", "setup-project": "Projektdateien einrichten", "create-default-package": "Standardpaketdatei erstellen", "no-thanks": "Nein, Danke", @@ -295,6 +300,10 @@ "modifiedFlowsDesc": "Übernimmt nur Flows, die geänderte Nodes enthalten", "modifiedNodes": "Geänderte Nodes", "modifiedNodesDesc": "Übernimmt nur Nodes, die sich geändert haben", + "startFlows": "Start", + "startFlowsDesc": "Flows starten", + "stopFlows": "Stop", + "stopFlowsDesc": "Flows stoppen", "restartFlows": "Flows neustarten", "restartFlowsDesc": "Startet die aktuell übernommenen Flows (ohne vorheriges Deploy)", "successfulDeploy": "Erfolgreich übernommen (deploy)", @@ -376,7 +385,7 @@ "confirmDelete": "Sind Sie sicher mit dem Löschen dieses Subflows?", "info": "Beschreibung", "category": "Kategorie", - "module": "Module", + "module": "Modul", "license": "Lizenz", "licenseNone": "Keine", "licenseOther": "Andere", @@ -434,7 +443,7 @@ "icon": "Icon", "inputType": "Eingangstyp", "selectType": "Wähle Typen ...", - "loadCredentials": "Lade Node-Berechtigungen", + "loadCredentials": "Lade Node-Anmeldeinformationen", "inputs": { "input": "Eingang", "select": "Auswahl", @@ -450,7 +459,7 @@ "json": "JSON", "bin": "buffer", "env": "Umgebungsvariable", - "cred": "Berechtigung" + "cred": "Anmeldeinformation" }, "menu": { "input": "Eingang", @@ -470,7 +479,7 @@ "errors": { "scopeChange": "Wenn Sie den Geltungsbereich (scope) ändern, wird er für Nodes in anderen Flows nicht verfügbar sein", "invalidProperties": "Ungültige Eigenschaften:", - "credentialLoadFailed": "Laden der Node-Berechtigungen fehlgeschlagen" + "credentialLoadFailed": "Laden der Node-Anmeldeinformationen fehlgeschlagen" } }, "keyboard": { @@ -683,7 +692,8 @@ "showHelp": "Hilfe zeigen", "showInOutline": "Zeige im Editor", "showTopics": "Zeige Hilfethemen", - "noHelp": "Kein Hilfethema ausgewählt" + "noHelp": "Kein Hilfethema ausgewählt", + "changeLog": "Änderungsprotokoll" }, "config": { "name": "Konfigurations-Node", @@ -737,7 +747,7 @@ "addToProject": "Zu Projekt hinzufügen", "files": "Dateien", "flow": "Flow", - "credentials": "Berechtigungen", + "credentials": "Anmeldeinformationen", "package": "Paket", "packageCreate": "Datei wird erstellt beim Speichern der Änderungen", "fileNotExist": "Datei existiert nicht", @@ -750,7 +760,7 @@ "changeTheEncryptionKey": "Schlüssel ändern", "currentKey": "Aktueller Schlüssel", "newKey": "Neuer Schlüssel", - "credentialsAlert": "Dadurch werden alle vorhandenen Berechtigungen gelöscht", + "credentialsAlert": "Dadurch werden alle vorhandenen Anmeldeinformationen gelöscht", "versionControl": "Versionsverwaltung (Git)", "branches": "Branches", "noBranches": "Keine Branches", @@ -886,7 +896,7 @@ "date": "timestamp", "jsonata": "JSONata", "env": "Umgebungsvariable", - "cred": "Berechtigung" + "cred": "Anmeldeinformation" } }, "editableList": { @@ -1026,7 +1036,7 @@ "passphrase": "Passphrase", "ssh-key-desc": "Bevor Sie ein Repository über SSH lokal klonen können, müssen Sie einen SSH-Schlüssel hinzufügen, um auf diesen zugreifen zu können", "ssh-key-add": "SSH-Schlüssel hinzufügen", - "credential-key": "Schlüssel für Berechtigungen", + "credential-key": "Schlüssel für Anmeldeinformationen", "cant-get-ssh-key": "Fehler! Der ausgewählte SSH-Schlüsselpfad kann nicht abgerufen werden", "already-exists2": "bereits vorhanden", "git-error": "Git-Fehler", @@ -1038,27 +1048,27 @@ "create": "Erstellen Sie Ihre Projektdateien", "desc0": "Ein Projekt enthält Ihre Flow-Dateien, eine README-Datei und die 'package.json'-Datei.", "desc1": "Es kann alle anderen Dateien enthalten, die im Git-Repository verwaltet werden sollen.", - "desc2": "Ihre vorhandenen Flow- und Berechtigungs-Dateien werden in das Projekt kopiert.", + "desc2": "Ihre vorhandenen Flow- und Anmeldeinformations-Dateien werden in das Projekt kopiert.", "flow-file": "Flow-Datei", - "credentials-file": "Datei mit Berechtigungen" + "credentials-file": "Datei mit Anmeldeinformationen" }, "encryption-config": { - "setup": "Einrichtung der Verschlüsselung Ihrer Datei mit den Berechtigungen", - "desc0": "Die Datei mit den Flow-Berechtigungen kann verschlüsselt werden, um ihren Inhalt zu schützen.", - "desc1": "Wenn Sie diese Berechtigungen in einem öffentlichen Repository speichern möchten, müssen Sie sie mit einen geheimen Schlüsselausdruck verschlüsseln.", - "desc2": "Die Datei mit den Flow-Berechtigungen ist derzeit nicht verschlüsselt.", + "setup": "Einrichtung der Verschlüsselung Ihrer Datei mit den Anmeldeinformationen", + "desc0": "Die Datei mit den Flow-Anmeldeinformationen kann verschlüsselt werden, um ihren Inhalt zu schützen.", + "desc1": "Wenn Sie diese Anmeldeinformationen in einem öffentlichen Repository speichern möchten, müssen Sie sie mit einen geheimen Schlüsselausdruck verschlüsseln.", + "desc2": "Die Datei mit den Flow-Anmeldeinformationen ist derzeit nicht verschlüsselt.", "desc3": "D.h. ihr Inhalt (z.B. Passwörter und Zugriffs-Tokens) kann von jedem mit Zugriff auf die Datei gelesen werden.", - "desc4": "Wenn Sie diese Berechtigungen in einen öffentlichen Repository speichern möchten, müssen Sie diese verschlüsseln, indem Sie einen geheimen Schlüsselausdruck eingeben.", - "desc5": "Ihre Datei mit den Flow-Berechtigungen wird derzeit mit dem Eintrag 'credentialSecret' Ihrer Einstellungsdatei als Schlüssel verschlüsselt.", - "desc6": "Die Datei mit den Flow-Berechtigungen wird derzeit mit einem vom System generierten Schlüssel verschlüsselt. Sie sollten einen neuen geheimen Schlüssel für dieses Projekt vorgeben.", + "desc4": "Wenn Sie diese Anmeldeinformationen in einen öffentlichen Repository speichern möchten, müssen Sie diese verschlüsseln, indem Sie einen geheimen Schlüsselausdruck eingeben.", + "desc5": "Ihre Datei mit den Flow-Anmeldeinformationen wird derzeit mit dem Eintrag 'credentialSecret' Ihrer Einstellungsdatei als Schlüssel verschlüsselt.", + "desc6": "Die Datei mit den Flow-Anmeldeinformationen wird derzeit mit einem vom System generierten Schlüssel verschlüsselt. Sie sollten einen neuen geheimen Schlüssel für dieses Projekt vorgeben.", "desc7": "Der Schlüssel wird separat von den Projektdateien gespeichert. Sie müssen den Schlüssel angeben, damit dieses Projekt auch in einem anderen Node-RED-System verwendet werden kann.", - "credentials": "Berechtigung", + "credentials": "Anmeldeinformationen", "enable": "Verschlüsselung aktivieren", "disable": "Verschlüsselung deaktivieren", "disabled": "deaktiviert", "copy": "Vorhandenen Schlüssel ersetzen", "use-custom": "Eigenen Schlüssel verwenden", - "desc8": "Die Datei mit den Berechtigungen wird nicht verschlüsselt, und ihr Inhalt kann leicht gelesen werden", + "desc8": "Die Datei mit den Anmeldeinformationen wird nicht verschlüsselt und ihr Inhalt kann leicht gelesen werden", "create-project-files": "Projektdateien erstellen", "create-project": "Projekt erstellen", "already-exists": "bereits vorhanden", @@ -1083,12 +1093,12 @@ "desc": "Beschreibung", "opt": "Optional", "flow-file": "Flow-Datei", - "credentials": "Berechtigungen", + "credentials": "Anmeldeinformationen", "enable-encryption": "Verschlüsselung aktivieren", "disable-encryption": "Verschlüsselung deaktivieren", "encryption-key": "Schlüssel", - "desc0": "Eine Floskel, mit der Sie Ihre Berechtigungen schützen", - "desc1": "Die Datei mit den Berechtigungen wird nicht verschlüsselt, und ihr Inhalt kann leicht gelesen werden", + "desc0": "Eine Ausdruck, mit der Sie Ihre Anmeldeinformationen schützen", + "desc1": "Die Datei mit den Anmeldeinformationen wird nicht verschlüsselt und ihr Inhalt kann leicht gelesen werden", "git-url": "Git-Repository-URL", "protocols": "https://, ssh:// oder file://", "auth-failed": "Authentifizierung fehlgeschlagen", @@ -1098,7 +1108,7 @@ "passphrase": "Passphrase", "desc2": "Bevor Sie ein Repository über SSH klonen können, müssen Sie einen SSH-Schlüssel hinzufügen, um auf diesen zu zugreifen", "add-ssh-key": "Einen SSH-Schlüssel hinzufügen", - "credentials-encryption-key": "Schlüssel für Berechtigungen", + "credentials-encryption-key": "Schlüssel für Anmeldeinformationen", "already-exists-2": "bereits vorhanden", "git-error": "Git-Fehler", "con-failed": "Verbindung fehlgeschlagen", @@ -1156,7 +1166,8 @@ "tourGuide": { "takeATour": "Tour starten", "start": "Start", - "next": "Nächste" + "next": "Nächste", + "welcomeTours": "Willkommens-Touren" }, "diagnostics": { "title": "System-Informationen" diff --git a/packages/node_modules/@node-red/runtime/locales/de/runtime.json b/packages/node_modules/@node-red/runtime/locales/de/runtime.json index 6811ba86a..99800c628 100755 --- a/packages/node_modules/@node-red/runtime/locales/de/runtime.json +++ b/packages/node_modules/@node-red/runtime/locales/de/runtime.json @@ -1,6 +1,6 @@ { "runtime": { - "welcome": "Willkommen bei Node-RED!", + "welcome": "Willkommen bei Node-RED", "version": "__component__ Version: __version__", "unsupported_version": "Nicht unterstützte Version von __component__. Erforderlich: __requires__, jedoch gefunden: __version__", "paths": { @@ -8,7 +8,6 @@ "httpStatic": "HTTP-Statisch: __path__" } }, - "server": { "loading": "Paletten-Nodes werden geladen", "palette-editor": { @@ -34,17 +33,19 @@ "install-failed-not-found": "Das Modul $t(server.install.install-failed-long) wurde nicht gefunden", "install-failed-name": "$t(server.install.install-failed-long). Ungültiger Modulname: __name__", "install-failed-url": "$t(server.install.install-failed-long). Ungültige URL: __url__", + "post-install-error": "Fehler bei der Ausführung des 'postInstall'-Hooks:", "upgrading": "Upgrade von Modul __name__ auf Version __version__ gestartet", "upgraded": "Upgrade von Modul __name__ war erfolgreich. Neustart von Node-RED für die Verwendung der neuen Version erforderlich.", "upgrade-failed-not-found": "Upgrade fehlgeschlagen. $t(server.install.install-failed-long). Version nicht gefunden.", "uninstalling": "Das Modul __name__ wird deinstalliert", "uninstall-failed": "Deinstallation fehlgeschlagen", "uninstall-failed-long": "Die Deinstallation des Moduls __name__ ist fehlgeschlagen:", - "uninstalled": "Das Modul __name__ ist deinstalliert" + "uninstalled": "Das Modul __name__ ist deinstalliert", + "old-ext-mod-dir-warning": "\n\n---------------------------------------------------------------------\nNode-RED 1.3 Verzeichnis externer Module erkannt:\n __oldDir__\nDieses Verzeichnis wird nicht mehr verwendet. Die externen Module werden\nin Ihrem Node-RED-Benutzerverzeichnis neu installiert:\n __newDir__\nLöschen Sie das alte externalModules-Verzeichnis, um diese Meldung abzustellen.\n---------------------------------------------------------------------\n" }, "deprecatedOption": "Die Verwendung von __old__ ist abgekündigt. Stattdessen __new__ verwenden.", "unable-to-listen": "Überwachen (listen) von __listenpath__ nicht möglich", - "port-in-use": "FEHLER: Port wird verwendet", + "port-in-use": "Fehler: Port wird verwendet", "uncaught-exception": "Nicht abgefangene Ausnahmebedingung:", "admin-ui-disabled": "Administrator-Benutzeroberfläche deaktiv", "now-running": "Server wird jetzt auf __listenpath__ ausgeführt", @@ -55,11 +56,10 @@ "refresh-interval": "Erneuerung der https-Einstellungen erfolgt alle __interval__ Stunden", "settings-refreshed": "https-Einstellungen wurden erneuert", "refresh-failed": "Erneuerung der https-Einstellungen fehlgeschlagen: __message__", - "nodejs-version": "httpsRefreshInterval erfordert Node.js 11 or later", + "nodejs-version": "httpsRefreshInterval erfordert Node.js 11 oder höher", "function-required": "httpsRefreshInterval erfordert die https-Eigenschaft in Form einer Funktion" } }, - "api": { "flows": { "error-save": "Fehler beim Speichern der Flows: __message__", @@ -77,17 +77,16 @@ "error-enable": "Der Node konnte nicht aktiviert werden:" } }, - "comms": { "error": "Kommunikationskanal-Fehler: __message__", "error-server": "Kommunikationsserver-Fehler: __message__", "error-send": "Kommunikationsende-Fehler: __message__" }, - "settings": { "user-not-available": "Einstellungen konnten nicht gespeichert werden: __message__", "not-available": "Einstellungen nicht verfügbar", - "property-read-only": "Die Eigenschaft '__prop__ 'ist schreibgeschützt" + "property-read-only": "Die Eigenschaft '__prop__ 'ist schreibgeschützt", + "readonly-mode": "Laufzeitumgebung im Nur-Lese-Modus. Änderungen werden nicht gespeichert." }, "library": { "unknownLibrary": "Unbekannte Bibliothek (Library): __library__", @@ -98,10 +97,12 @@ }, "nodes": { "credentials": { - "error": "Fehler beim Laden der Berechtigungen: __message__", - "error-saving": "Fehler beim Speichern der Berechtigungen: __message__", - "not-registered": "Der Berechtigung-Typ '__type__' ist nicht registriert", - "system-key-warning": "\n---------------------------------------------------------------------\nDie Datei mit den Flow-Berechtigungen wird mit einem vom System\ngenerierten Schlüssel verschlüsselt.\nWenn der vom System generierte Schlüssel aus irgendeinem Grund\nverloren geht, kann die Datei mit den Berechtigungen nicht\nwiederhergestellt werden. Sie muss dann gelöscht und die\nBerechtigungen müssen erneut eingestellt werden.\nEs sollte ein eigener Schlüssel mit Hilfe der Option\n'credentialSecret' in der Einstellungsdatei vorgegeben werden.\nNode-RED wird dann die Datei mit den Flow-Berechtigungen\nbei der nächsten Übernahme (deploy) einer Änderung erneut\nverschlüsseln.\n---------------------------------------------------------------------" + "error": "Fehler beim Laden der Anmeldeinformationen: __message__", + "error-saving": "Fehler beim Speichern der Anmeldeinformationen: __message__", + "not-registered": "Der Anmeldeinformations-Typ '__type__' ist nicht registriert", + "system-key-warning": "\n---------------------------------------------------------------------\nDie Datei mit den Flow-Anmeldeinformationen wird mit einem vom System\ngenerierten Schlüssel verschlüsselt.\nWenn der vom System generierte Schlüssel aus irgendeinem Grund\nverloren geht, kann die Datei mit den Anmeldeinformationen nicht\nwiederhergestellt werden. Sie muss dann gelöscht und die\nAnmeldeinformationen müssen erneut eingestellt werden.\nEs sollte ein eigener Schlüssel mit Hilfe der Option\n'credentialSecret' in der Einstellungsdatei vorgegeben werden.\nNode-RED wird dann die Datei mit den Flow-Anmeldeinformationen\nbei der nächsten Übernahme (deploy) einer Änderung erneut\nverschlüsseln.\n---------------------------------------------------------------------", + "unencrypted": "Verwende unverschlüsselte Anmeldeinformationen", + "encryptedNotFound": "Verschlüsselte Anmeldeinformationen nicht gefunden" }, "flows": { "safe-mode": "Die Flows sind gestoppt im abgesicherten Modus. Übernahme (deploy) zum Starten.", @@ -121,6 +122,7 @@ "stopped-flows": "Flows sind gestoppt", "stopped": "gestoppt", "stopping-error": "Fehler beim Stoppen des Nodes: __message__", + "updated-flows": "Flows aktualisiert", "added-flow": "Flow wird hinzugefügt: __label__", "updated-flow": "Aktualisierter Flow: __label__", "removed-flow": "Entfernter Flow: __label__", @@ -145,7 +147,6 @@ } } }, - "storage": { "index": { "forbidden-flow-name": "Unzulässiger Flow-Name" @@ -159,6 +160,7 @@ "restore": "Die '__type__'-Dateisicherung wird wiederhergestellt: __path__", "restore-fail": "Die Wiederherstellung der '__type__'-Dateisicherung ist fehlgeschlagen: __message__", "fsync-fail": "Die Übertragung der Datei __path__ auf das Laufwerk ist fehlgeschlagen: __message__", + "warn_name": "Flows Dateiname nicht festgelegt. Name wird unter Verwendung des Hostnamens generiert.", "projects": { "changing-project": "Aktives Projekt wird festgelegt: __project__", "active-project": "Aktives Projekt: __project__", @@ -174,7 +176,6 @@ } } }, - "context": { "log-store-init": "Kontextspeicher: __name__ [__info__]", "error-loading-module": "Fehler beim Laden des Kontextspeichers: __message__", @@ -189,5 +190,4 @@ "error-write": "Fehler beim Schreiben des Kontextes: __message__" } } - } From fe5132be1dad0eced29995fba7091b376a8a1139 Mon Sep 17 00:00:00 2001 From: Dennis Neufeld Date: Sun, 24 Jul 2022 16:31:01 +0200 Subject: [PATCH 132/237] Update german translation 2 --- .../editor-client/locales/de/editor.json | 56 +++++++++---------- .../@node-red/runtime/locales/de/runtime.json | 16 +++--- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/de/editor.json b/packages/node_modules/@node-red/editor-client/locales/de/editor.json index 36bf537de..93a2f5946 100755 --- a/packages/node_modules/@node-red/editor-client/locales/de/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/de/editor.json @@ -105,7 +105,7 @@ "search": "Flows durchsuchen", "searchInput": "Flows durchsuchen", "subflows": "Subflow", - "createSubflow": "Subflow hinzufügen", + "createSubflow": "Hinzufügen", "selectionToSubflow": "Auswahl in Subflow umwandeln", "flows": "Flow", "add": "Hinzufügen", @@ -182,8 +182,8 @@ "missing-modules": "

    Flows angehalten aufgrund fehlender Module

    ", "safe-mode": "

    Flows sind im abgesicherten Modus gestoppt.

    Flows können bearbeitet und übernommen (deploy) werden, um sie neu zu starten.

    ", "restartRequired": "Node-RED muss neu gestartet werden, damit die Module nach Upgrade aktiviert werden", - "credentials_load_failed": "

    Flows gestoppt, da die Anmeldeinformationen nicht entschlüsselt werden konnten.

    Die Datei mit den Flow-Anmeldeinformationen ist verschlüsselt, aber der Schlüssel des Projekts fehlt oder ist ungültig.

    ", - "credentials_load_failed_reset": "

    Die Anmeldeinformationen konnten nicht entschlüsselt werden.

    Die Datei mit den Flow-Anmeldeinformationen ist verschlüsselt, aber der Schlüssel des Projekts fehlt oder ist ungültig.

    Die Datei mit den Flow-Anmeldeinformationen wird bei der nächsten Übernahme (deploy) zurückgesetzt. Alle vorhandenen Flow-Anmeldeinformationen werden gelöscht.

    ", + "credentials_load_failed": "

    Flows gestoppt, da die Credentials nicht entschlüsselt werden konnten.

    Die Datei mit den Flow-Credentials ist verschlüsselt, aber der Schlüssel des Projekts fehlt oder ist ungültig.

    ", + "credentials_load_failed_reset": "

    Die Credentials konnten nicht entschlüsselt werden.

    Die Datei mit den Flow-Credentials ist verschlüsselt, aber der Schlüssel des Projekts fehlt oder ist ungültig.

    Die Datei mit den Flow-Credentials wird bei der nächsten Übernahme (deploy) zurückgesetzt. Alle vorhandenen Flow-Credentials werden gelöscht.

    ", "missing_flow_file": "

    Die Flow-Datei des Projekts wurde nicht gefunden.

    Das Projekt ist nicht mit einer Flow-Datei konfiguriert.

    ", "missing_package_file": "

    Die Paket-Datei des Projekts wurde nicht gefunden.

    In dem Projekt fehlt die 'package.json'-Datei.

    ", "project_empty": "

    Das Projekt ist leer.

    Soll ein Standardsatz an Projektdateien erstellen werden?
    Andernfalls müssen die Dateien manuell außerhalb des Editors dem Projekt hinzugefügt werden.

    ", @@ -208,7 +208,7 @@ "pull": "Projekt '__project__' erneut geladen", "revert": "Änderungen im Projekt '__project__' rückgängig gemacht", "merge-complete": "Git-Merge abgeschlossen", - "setupCredentials": "Anmeldeinformationen einrichten", + "setupCredentials": "Credentials einrichten", "setupProjectFiles": "Projektdateien einrichten", "no": "Nein, Danke", "createDefault": "Standardprojektdateien erstellen", @@ -216,7 +216,7 @@ }, "label": { "manage-project-dep": "Projektabhängigkeiten verwalten", - "setup-cred": "Anmeldeinformationen einrichten", + "setup-cred": "Credentials einrichten", "setup-project": "Projektdateien einrichten", "create-default-package": "Standardpaketdatei erstellen", "no-thanks": "Nein, Danke", @@ -443,7 +443,7 @@ "icon": "Icon", "inputType": "Eingangstyp", "selectType": "Wähle Typen ...", - "loadCredentials": "Lade Node-Anmeldeinformationen", + "loadCredentials": "Lade Node-Credentials", "inputs": { "input": "Eingang", "select": "Auswahl", @@ -459,7 +459,7 @@ "json": "JSON", "bin": "buffer", "env": "Umgebungsvariable", - "cred": "Anmeldeinformation" + "cred": "Credentials" }, "menu": { "input": "Eingang", @@ -479,7 +479,7 @@ "errors": { "scopeChange": "Wenn Sie den Geltungsbereich (scope) ändern, wird er für Nodes in anderen Flows nicht verfügbar sein", "invalidProperties": "Ungültige Eigenschaften:", - "credentialLoadFailed": "Laden der Node-Anmeldeinformationen fehlgeschlagen" + "credentialLoadFailed": "Laden der Node-Credentials fehlgeschlagen" } }, "keyboard": { @@ -747,7 +747,7 @@ "addToProject": "Zu Projekt hinzufügen", "files": "Dateien", "flow": "Flow", - "credentials": "Anmeldeinformationen", + "credentials": "Credentials", "package": "Paket", "packageCreate": "Datei wird erstellt beim Speichern der Änderungen", "fileNotExist": "Datei existiert nicht", @@ -760,7 +760,7 @@ "changeTheEncryptionKey": "Schlüssel ändern", "currentKey": "Aktueller Schlüssel", "newKey": "Neuer Schlüssel", - "credentialsAlert": "Dadurch werden alle vorhandenen Anmeldeinformationen gelöscht", + "credentialsAlert": "Dadurch werden alle vorhandenen Credentials gelöscht", "versionControl": "Versionsverwaltung (Git)", "branches": "Branches", "noBranches": "Keine Branches", @@ -896,7 +896,7 @@ "date": "timestamp", "jsonata": "JSONata", "env": "Umgebungsvariable", - "cred": "Anmeldeinformation" + "cred": "Credentials" } }, "editableList": { @@ -1036,7 +1036,7 @@ "passphrase": "Passphrase", "ssh-key-desc": "Bevor Sie ein Repository über SSH lokal klonen können, müssen Sie einen SSH-Schlüssel hinzufügen, um auf diesen zugreifen zu können", "ssh-key-add": "SSH-Schlüssel hinzufügen", - "credential-key": "Schlüssel für Anmeldeinformationen", + "credential-key": "Schlüssel für Credentials", "cant-get-ssh-key": "Fehler! Der ausgewählte SSH-Schlüsselpfad kann nicht abgerufen werden", "already-exists2": "bereits vorhanden", "git-error": "Git-Fehler", @@ -1048,27 +1048,27 @@ "create": "Erstellen Sie Ihre Projektdateien", "desc0": "Ein Projekt enthält Ihre Flow-Dateien, eine README-Datei und die 'package.json'-Datei.", "desc1": "Es kann alle anderen Dateien enthalten, die im Git-Repository verwaltet werden sollen.", - "desc2": "Ihre vorhandenen Flow- und Anmeldeinformations-Dateien werden in das Projekt kopiert.", + "desc2": "Ihre vorhandenen Flow- und Credential-Dateien werden in das Projekt kopiert.", "flow-file": "Flow-Datei", - "credentials-file": "Datei mit Anmeldeinformationen" + "credentials-file": "Datei mit Credentials" }, "encryption-config": { - "setup": "Einrichtung der Verschlüsselung Ihrer Datei mit den Anmeldeinformationen", - "desc0": "Die Datei mit den Flow-Anmeldeinformationen kann verschlüsselt werden, um ihren Inhalt zu schützen.", - "desc1": "Wenn Sie diese Anmeldeinformationen in einem öffentlichen Repository speichern möchten, müssen Sie sie mit einen geheimen Schlüsselausdruck verschlüsseln.", - "desc2": "Die Datei mit den Flow-Anmeldeinformationen ist derzeit nicht verschlüsselt.", + "setup": "Einrichtung der Verschlüsselung Ihrer Datei mit den Credentials", + "desc0": "Die Datei mit den Flow-Credentials kann verschlüsselt werden, um ihren Inhalt zu schützen.", + "desc1": "Wenn Sie diese Credentials in einem öffentlichen Repository speichern möchten, müssen Sie sie mit einen geheimen Schlüsselausdruck verschlüsseln.", + "desc2": "Die Datei mit den Flow-Credentials ist derzeit nicht verschlüsselt.", "desc3": "D.h. ihr Inhalt (z.B. Passwörter und Zugriffs-Tokens) kann von jedem mit Zugriff auf die Datei gelesen werden.", - "desc4": "Wenn Sie diese Anmeldeinformationen in einen öffentlichen Repository speichern möchten, müssen Sie diese verschlüsseln, indem Sie einen geheimen Schlüsselausdruck eingeben.", - "desc5": "Ihre Datei mit den Flow-Anmeldeinformationen wird derzeit mit dem Eintrag 'credentialSecret' Ihrer Einstellungsdatei als Schlüssel verschlüsselt.", - "desc6": "Die Datei mit den Flow-Anmeldeinformationen wird derzeit mit einem vom System generierten Schlüssel verschlüsselt. Sie sollten einen neuen geheimen Schlüssel für dieses Projekt vorgeben.", + "desc4": "Wenn Sie diese Credentials in einen öffentlichen Repository speichern möchten, müssen Sie diese verschlüsseln, indem Sie einen geheimen Schlüsselausdruck eingeben.", + "desc5": "Ihre Datei mit den Flow-Credentials wird derzeit mit dem Eintrag 'credentialSecret' Ihrer Einstellungsdatei als Schlüssel verschlüsselt.", + "desc6": "Die Datei mit den Flow-Credentials wird derzeit mit einem vom System generierten Schlüssel verschlüsselt. Sie sollten einen neuen geheimen Schlüssel für dieses Projekt vorgeben.", "desc7": "Der Schlüssel wird separat von den Projektdateien gespeichert. Sie müssen den Schlüssel angeben, damit dieses Projekt auch in einem anderen Node-RED-System verwendet werden kann.", - "credentials": "Anmeldeinformationen", + "credentials": "Credentials", "enable": "Verschlüsselung aktivieren", "disable": "Verschlüsselung deaktivieren", "disabled": "deaktiviert", "copy": "Vorhandenen Schlüssel ersetzen", "use-custom": "Eigenen Schlüssel verwenden", - "desc8": "Die Datei mit den Anmeldeinformationen wird nicht verschlüsselt und ihr Inhalt kann leicht gelesen werden", + "desc8": "Die Datei mit den Credentials wird nicht verschlüsselt und ihr Inhalt kann leicht gelesen werden", "create-project-files": "Projektdateien erstellen", "create-project": "Projekt erstellen", "already-exists": "bereits vorhanden", @@ -1093,12 +1093,12 @@ "desc": "Beschreibung", "opt": "Optional", "flow-file": "Flow-Datei", - "credentials": "Anmeldeinformationen", + "credentials": "Credentials", "enable-encryption": "Verschlüsselung aktivieren", "disable-encryption": "Verschlüsselung deaktivieren", "encryption-key": "Schlüssel", - "desc0": "Eine Ausdruck, mit der Sie Ihre Anmeldeinformationen schützen", - "desc1": "Die Datei mit den Anmeldeinformationen wird nicht verschlüsselt und ihr Inhalt kann leicht gelesen werden", + "desc0": "Eine Ausdruck, mit der Sie Ihre Credentials schützen", + "desc1": "Die Datei mit den Credentials wird nicht verschlüsselt und ihr Inhalt kann leicht gelesen werden", "git-url": "Git-Repository-URL", "protocols": "https://, ssh:// oder file://", "auth-failed": "Authentifizierung fehlgeschlagen", @@ -1108,7 +1108,7 @@ "passphrase": "Passphrase", "desc2": "Bevor Sie ein Repository über SSH klonen können, müssen Sie einen SSH-Schlüssel hinzufügen, um auf diesen zu zugreifen", "add-ssh-key": "Einen SSH-Schlüssel hinzufügen", - "credentials-encryption-key": "Schlüssel für Anmeldeinformationen", + "credentials-encryption-key": "Schlüssel für Credentials", "already-exists-2": "bereits vorhanden", "git-error": "Git-Fehler", "con-failed": "Verbindung fehlgeschlagen", @@ -1167,7 +1167,7 @@ "takeATour": "Tour starten", "start": "Start", "next": "Nächste", - "welcomeTours": "Willkommens-Touren" + "welcomeTours": "Welcome Tours" }, "diagnostics": { "title": "System-Informationen" diff --git a/packages/node_modules/@node-red/runtime/locales/de/runtime.json b/packages/node_modules/@node-red/runtime/locales/de/runtime.json index 99800c628..0249d638d 100755 --- a/packages/node_modules/@node-red/runtime/locales/de/runtime.json +++ b/packages/node_modules/@node-red/runtime/locales/de/runtime.json @@ -85,7 +85,7 @@ "settings": { "user-not-available": "Einstellungen konnten nicht gespeichert werden: __message__", "not-available": "Einstellungen nicht verfügbar", - "property-read-only": "Die Eigenschaft '__prop__ 'ist schreibgeschützt", + "property-read-only": "Die Eigenschaft '__prop__' ist schreibgeschützt", "readonly-mode": "Laufzeitumgebung im Nur-Lese-Modus. Änderungen werden nicht gespeichert." }, "library": { @@ -97,12 +97,12 @@ }, "nodes": { "credentials": { - "error": "Fehler beim Laden der Anmeldeinformationen: __message__", - "error-saving": "Fehler beim Speichern der Anmeldeinformationen: __message__", - "not-registered": "Der Anmeldeinformations-Typ '__type__' ist nicht registriert", - "system-key-warning": "\n---------------------------------------------------------------------\nDie Datei mit den Flow-Anmeldeinformationen wird mit einem vom System\ngenerierten Schlüssel verschlüsselt.\nWenn der vom System generierte Schlüssel aus irgendeinem Grund\nverloren geht, kann die Datei mit den Anmeldeinformationen nicht\nwiederhergestellt werden. Sie muss dann gelöscht und die\nAnmeldeinformationen müssen erneut eingestellt werden.\nEs sollte ein eigener Schlüssel mit Hilfe der Option\n'credentialSecret' in der Einstellungsdatei vorgegeben werden.\nNode-RED wird dann die Datei mit den Flow-Anmeldeinformationen\nbei der nächsten Übernahme (deploy) einer Änderung erneut\nverschlüsseln.\n---------------------------------------------------------------------", - "unencrypted": "Verwende unverschlüsselte Anmeldeinformationen", - "encryptedNotFound": "Verschlüsselte Anmeldeinformationen nicht gefunden" + "error": "Fehler beim Laden der Credentials: __message__", + "error-saving": "Fehler beim Speichern der Credentials: __message__", + "not-registered": "Der Credentials-Typ '__type__' ist nicht registriert", + "system-key-warning": "\n---------------------------------------------------------------------\nDie Datei mit den Flow-Credentials wird mit einem vom System\ngenerierten Schlüssel verschlüsselt.\nWenn der vom System generierte Schlüssel aus irgendeinem Grund\nverloren geht, kann die Datei mit den Credentials nicht\nwiederhergestellt werden. Sie muss dann gelöscht und die\nCredentials müssen erneut eingestellt werden.\nEs sollte ein eigener Schlüssel mit Hilfe der Option\n'credentialSecret' in der Einstellungsdatei vorgegeben werden.\nNode-RED wird dann die Datei mit den Flow-Credentials\nbei der nächsten Übernahme (deploy) einer Änderung erneut\nverschlüsseln.\n---------------------------------------------------------------------", + "unencrypted": "Verwende unverschlüsselte Credentials", + "encryptedNotFound": "Verschlüsselte Credentials nicht gefunden" }, "flows": { "safe-mode": "Die Flows sind gestoppt im abgesicherten Modus. Übernahme (deploy) zum Starten.", @@ -160,7 +160,7 @@ "restore": "Die '__type__'-Dateisicherung wird wiederhergestellt: __path__", "restore-fail": "Die Wiederherstellung der '__type__'-Dateisicherung ist fehlgeschlagen: __message__", "fsync-fail": "Die Übertragung der Datei __path__ auf das Laufwerk ist fehlgeschlagen: __message__", - "warn_name": "Flows Dateiname nicht festgelegt. Name wird unter Verwendung des Hostnamens generiert.", + "warn_name": "Name der Flows-Datei nicht festgelegt. Name wird unter Verwendung des Hostnamens generiert.", "projects": { "changing-project": "Aktives Projekt wird festgelegt: __project__", "active-project": "Aktives Projekt: __project__", From f454c29b8c37942175ff0f764facf5249a00b9cd Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Thu, 28 Jul 2022 10:56:38 -0400 Subject: [PATCH 133/237] Hide scrollbars until they're needed --- .../@node-red/editor-client/src/js/ui/common/editableList.js | 2 +- .../@node-red/editor-client/src/js/ui/tab-help.js | 2 +- .../@node-red/editor-client/src/js/ui/tab-info.js | 2 +- .../node_modules/@node-red/editor-client/src/sass/debug.scss | 2 +- .../node_modules/@node-red/editor-client/src/sass/editor.scss | 4 ++-- .../@node-red/editor-client/src/sass/projects.scss | 4 ++-- .../@node-red/editor-client/src/sass/tab-context.scss | 2 +- .../@node-red/editor-client/src/sass/userSettings.scss | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js index ea1938e5c..f308614d7 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js @@ -160,7 +160,7 @@ this.element.css("maxHeight",null); } if (this.options.height !== 'auto') { - this.uiContainer.css("overflow-y","scroll"); + this.uiContainer.css("overflow-y","auto"); if (!isNaN(this.options.height)) { this.uiHeight = this.options.height; } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js index e5199b5bc..7ed93f48d 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js @@ -50,7 +50,7 @@ RED.sidebar.help = (function() { tocPanel = $("
    ", {class: "red-ui-sidebar-help-toc"}).appendTo(stackContainer); var helpPanel = $("
    ").css({ - "overflow-y": "scroll" + "overflow-y": "auto" }).appendTo(stackContainer); panels = RED.panels.create({ diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js index dfd4b1e43..cc8539363 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js @@ -98,7 +98,7 @@ RED.sidebar.info = (function() { propertiesPanelContent = $("
    ").css({ "flex":"1 1 auto", - "overflow-y":"scroll", + "overflow-y":"auto", }).appendTo(propertiesPanel); diff --git a/packages/node_modules/@node-red/editor-client/src/sass/debug.scss b/packages/node_modules/@node-red/editor-client/src/sass/debug.scss index 58099877f..eb550c6f5 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/debug.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/debug.scss @@ -30,7 +30,7 @@ bottom: 0px; left:0px; right: 0px; - overflow-y: scroll; + overflow-y: auto; } .red-ui-debug-filter-box { position:absolute; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/editor.scss b/packages/node_modules/@node-red/editor-client/src/sass/editor.scss index 1730b9e35..b6d12faf2 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/editor.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/editor.scss @@ -368,7 +368,7 @@ button.red-ui-button-small border:1px solid var(--red-ui-secondary-border-color); border-radius:5px; height: calc(100% - 21px); - overflow-y: scroll; + overflow-y: auto; background: var(--red-ui-secondary-background); } @@ -562,7 +562,7 @@ div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle { .red-ui-icon-list { width: 308px; height: 200px; - overflow-y: scroll; + overflow-y: auto; line-height: 0px; position: relative; &.red-ui-icon-list-dark { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/projects.scss b/packages/node_modules/@node-red/editor-client/src/sass/projects.scss index ee43c7a87..a32fd53b4 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/projects.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/projects.scss @@ -26,7 +26,7 @@ } } #red-ui-project-settings-tab-settings { - overflow-y: scroll; + overflow-y: auto; } .red-ui-sidebar-vc-shade { background: var(--red-ui-primary-background); @@ -183,7 +183,7 @@ } .red-ui-projects-dialog-project-list-inner-container { flex-grow: 1 ; - overflow-y: scroll; + overflow-y: auto; position:relative; .red-ui-editableList-border { border: none; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-context.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-context.scss index fc4c78afb..94450f337 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tab-context.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-context.scss @@ -20,7 +20,7 @@ bottom: 0; left: 0; right: 0; - overflow-y: scroll; + overflow-y: auto; .red-ui-palette-category { &:not(.expanded) button { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/userSettings.scss b/packages/node_modules/@node-red/editor-client/src/sass/userSettings.scss index 36ab67e3f..5e0c7fa47 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/userSettings.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/userSettings.scss @@ -67,7 +67,7 @@ left: 0; bottom: 0; padding: 8px 20px 20px; - overflow-y: scroll; + overflow-y: auto; } .red-ui-settings-row { padding: 5px 10px 2px; From 5ea0c6fca1c8bf3bf5ccb5672bfd0b4696878625 Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Fri, 29 Jul 2022 09:49:59 -0400 Subject: [PATCH 134/237] Fix workspace chart bottom property --- .../@node-red/editor-client/src/sass/workspace.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss b/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss index 24e156b1e..a5734570d 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss @@ -29,7 +29,7 @@ #red-ui-workspace-chart { overflow: auto; position: absolute; - bottom:25px; + bottom:26px; top: 35px; left:0px; right:0px; From 14a33668508d68efa5caf286eb214d96dd3cac05 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 1 Aug 2022 20:54:05 +0100 Subject: [PATCH 135/237] Include junctins/groups when exporting subflows plus related fixes Fixes #3805 --- .../@node-red/editor-client/src/js/nodes.js | 15 ++++++--------- .../@node-red/editor-client/src/js/ui/subflow.js | 8 ++++++++ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 9da5aad05..d387f01b3 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -868,14 +868,7 @@ RED.nodes = (function() { var node; if (allNodes.hasTab(id)) { - removedNodes = allNodes.getNodes(id).filter(n => { - if (n.type === 'junction') { - removedJunctions.push(n) - return false - } else { - return true - } - }) + removedNodes = allNodes.getNodes(id).slice() } for (i in configNodes) { if (configNodes.hasOwnProperty(i)) { @@ -885,6 +878,7 @@ RED.nodes = (function() { } } } + removedJunctions = RED.nodes.junctions(id) for (i=0;i Date: Mon, 1 Aug 2022 21:05:52 +0100 Subject: [PATCH 136/237] Allow generateNodeNames to handle names containing regex control characters Fixes #3813 --- .../@node-red/editor-client/src/js/ui/view-tools.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js index bc81d4a43..3588b38bf 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js @@ -1015,7 +1015,7 @@ RED.view.tools = (function() { const nodeDef = n._def || RED.nodes.getType(n.type) if (nodeDef && nodeDef.defaults && nodeDef.defaults.name) { const paletteLabel = RED.utils.getPaletteLabel(n.type, nodeDef) - const defaultNodeNameRE = new RegExp('^'+paletteLabel+' (\\d+)$') + const defaultNodeNameRE = new RegExp('^'+paletteLabel.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')+' (\\d+)$') if (!typeIndex.hasOwnProperty(n.type)) { const existingNodes = RED.nodes.filterNodes({type: n.type}) let maxNameNumber = 0; From 5c29feec635387dfdbd436da3a4879a33bd266f8 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 2 Aug 2022 00:05:21 +0100 Subject: [PATCH 137/237] Register subflow module instance node with parent flow Fixes #3798 --- packages/node_modules/@node-red/runtime/lib/flows/util.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/runtime/lib/flows/util.js b/packages/node_modules/@node-red/runtime/lib/flows/util.js index 0f3435b77..7c98fc041 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/util.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/util.js @@ -134,10 +134,12 @@ function createNode(flow,config) { subflowInstanceConfig, instanceConfig ); + // Register this subflow as an instance node of the parent flow. + // This allows nodes inside the subflow to get ahold of each other + // such as a node accessing its config node + flow.subflowInstanceNodes[config.id] = subflow subflow.start(); return subflow.node; - - Log.error(Log._("nodes.flow.unknown-type", {type:type})); } } catch(err) { Log.error(err); From b008a6a2aa39e6f355bea887c93328cddb91ce63 Mon Sep 17 00:00:00 2001 From: Stephen McLaughlin <44235289+Steve-Mcl@users.noreply.github.com> Date: Thu, 4 Aug 2022 11:05:27 +0100 Subject: [PATCH 138/237] remove console.log fixes #3819 --- .../node_modules/@node-red/editor-client/src/js/ui/subflow.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js index ca4f651ab..772985101 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js @@ -932,7 +932,6 @@ RED.subflow = (function() { function buildEnvUIRow(row, tenv, ui, node) { - console.log(tenv, ui) ui.label = ui.label||{}; if ((tenv.type === "cred" || (tenv.parent && tenv.parent.type === "cred")) && !ui.type) { ui.type = "cred"; From abccdc7f215ccde8a358f7f6aa69ca4567c435b0 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 4 Aug 2022 13:10:05 +0100 Subject: [PATCH 139/237] Update packages/node_modules/@node-red/nodes/locales/en-US/messages.json --- .../node_modules/@node-red/nodes/locales/en-US/messages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index b38ab3026..282485ca2 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -555,7 +555,7 @@ "status": { "requesting": "requesting" }, - "insecureHTTPParser": "Lenient HTTP Header Parsing" + "insecureHTTPParser": "Disable strict HTTP parsing" }, "websocket": { "label": { From c9b0a7c2ddc272eb133907c42b9b68de34aa925a Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 4 Aug 2022 13:50:31 +0100 Subject: [PATCH 140/237] Bump for 3.0.2 --- CHANGELOG.md | 20 +++++++++++++++++++ package.json | 12 +++++------ .../@node-red/editor-api/package.json | 6 +++--- .../@node-red/editor-client/package.json | 2 +- .../node_modules/@node-red/nodes/package.json | 4 ++-- .../@node-red/registry/package.json | 6 +++--- .../@node-red/runtime/package.json | 6 +++--- .../node_modules/@node-red/util/package.json | 4 ++-- packages/node_modules/node-red/package.json | 10 +++++----- 9 files changed, 45 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6a9016d9..630d91b4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +#### 3.0.2: Maintenance Release + +Editor + + - Fix workspace chart bottom property (#3812) @bonanitech + - Update german translation (#3802) @Dennis14e + - Support color reset to the default in subflow and group (#3801) @kazuhitoyokoi + - Allow generateNodeNames to handle names containing regex control chars (#3817) @knolleary + - Hide scrollbars until they're needed (#3808) @bonanitech + - Include junctions/groups when exporting subflows plus related fixes (#3816) @knolleary + - remove console.log (#3820) @Steve-Mcl + +Runtime + + - Register subflow module instance node with parent flow (#3818) @knolleary + +Nodes + + - HTTP Request: Allow HTTP Headers not in spec (#3776) @hardillb + #### 3.0.1: Maintenance Release Editor diff --git a/package.json b/package.json index d5ab3bf5d..a57de03c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "3.0.1", + "version": "3.0.2", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", @@ -40,7 +40,7 @@ "cookie-parser": "1.4.6", "cors": "2.8.5", "cronosjs": "1.7.1", - "denque": "2.0.1", + "denque": "2.1.0", "express": "4.18.1", "express-session": "1.17.3", "form-data": "4.0.0", @@ -49,7 +49,7 @@ "hash-sum": "2.0.0", "hpagent": "1.0.0", "https-proxy-agent": "5.0.1", - "i18next": "21.8.14", + "i18next": "21.8.16", "iconv-lite": "0.6.3", "is-utf8": "0.2.1", "js-yaml": "4.1.0", @@ -76,7 +76,7 @@ "semver": "7.3.7", "tar": "6.1.11", "tough-cookie": "4.0.0", - "uglify-js": "3.16.2", + "uglify-js": "3.16.3", "uuid": "8.3.2", "ws": "7.5.6", "xml2js": "0.4.23" @@ -85,7 +85,7 @@ "bcrypt": "5.0.1" }, "devDependencies": { - "dompurify": "2.3.9", + "dompurify": "2.3.10", "grunt": "1.5.3", "grunt-chmod": "~1.1.1", "grunt-cli": "~1.4.3", @@ -114,7 +114,7 @@ "node-red-node-test-helper": "^0.3.0", "nodemon": "2.0.19", "proxy": "^1.0.2", - "sass": "1.53.0", + "sass": "1.54.2", "should": "13.2.3", "sinon": "11.1.2", "stoppable": "^1.1.0", diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index e95604bab..e2c53fe84 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-api", - "version": "3.0.1", + "version": "3.0.2", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/util": "3.0.1", - "@node-red/editor-client": "3.0.1", + "@node-red/util": "3.0.2", + "@node-red/editor-client": "3.0.2", "bcryptjs": "2.4.3", "body-parser": "1.20.0", "clone": "2.1.2", diff --git a/packages/node_modules/@node-red/editor-client/package.json b/packages/node_modules/@node-red/editor-client/package.json index a06359c0c..0ef939fb3 100644 --- a/packages/node_modules/@node-red/editor-client/package.json +++ b/packages/node_modules/@node-red/editor-client/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-client", - "version": "3.0.1", + "version": "3.0.2", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index c7a31fa10..818110f2f 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/nodes", - "version": "3.0.1", + "version": "3.0.2", "license": "Apache-2.0", "repository": { "type": "git", @@ -25,7 +25,7 @@ "cookie": "0.5.0", "cors": "2.8.5", "cronosjs": "1.7.1", - "denque": "2.0.1", + "denque": "2.1.0", "form-data": "4.0.0", "fs-extra": "10.1.0", "got": "11.8.5", diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index c8c582ebe..6b707a87e 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/registry", - "version": "3.0.1", + "version": "3.0.2", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,11 +16,11 @@ } ], "dependencies": { - "@node-red/util": "3.0.1", + "@node-red/util": "3.0.2", "clone": "2.1.2", "fs-extra": "10.1.0", "semver": "7.3.7", "tar": "6.1.11", - "uglify-js": "3.16.2" + "uglify-js": "3.16.3" } } diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json index 8b1991a5c..c7d02e948 100644 --- a/packages/node_modules/@node-red/runtime/package.json +++ b/packages/node_modules/@node-red/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/runtime", - "version": "3.0.1", + "version": "3.0.2", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/registry": "3.0.1", - "@node-red/util": "3.0.1", + "@node-red/registry": "3.0.2", + "@node-red/util": "3.0.2", "async-mutex": "0.3.2", "clone": "2.1.2", "express": "4.18.1", diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index 8d677e2cd..66f5750b8 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/util", - "version": "3.0.1", + "version": "3.0.2", "license": "Apache-2.0", "repository": { "type": "git", @@ -16,7 +16,7 @@ ], "dependencies": { "fs-extra": "10.1.0", - "i18next": "21.8.14", + "i18next": "21.8.16", "json-stringify-safe": "5.0.1", "jsonata": "1.8.6", "lodash.clonedeep": "^4.5.0", diff --git a/packages/node_modules/node-red/package.json b/packages/node_modules/node-red/package.json index faa98c48d..aaaccad22 100644 --- a/packages/node_modules/node-red/package.json +++ b/packages/node_modules/node-red/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "3.0.1", + "version": "3.0.2", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "flow" ], "dependencies": { - "@node-red/editor-api": "3.0.1", - "@node-red/runtime": "3.0.1", - "@node-red/util": "3.0.1", - "@node-red/nodes": "3.0.1", + "@node-red/editor-api": "3.0.2", + "@node-red/runtime": "3.0.2", + "@node-red/util": "3.0.2", + "@node-red/nodes": "3.0.2", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "express": "4.18.1", From 598bcf675fe9635164e8b6c8abcb2dd1da1fb472 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Fri, 12 Aug 2022 15:45:12 +0100 Subject: [PATCH 141/237] fix searching by type when type name has a space --- .../editor-client/src/js/ui/palette.js | 14 +++++- .../editor-client/src/js/ui/search.js | 46 +++++++++++++------ 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js index 9f20cc674..322283fcb 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js @@ -175,9 +175,19 @@ RED.palette = (function() { $('').appendTo(popOverContent) } - var safeType = type.replace(/'/g,"\\'"); + const safeType = type.replace(/'/g,"\\'"); + function wrapStr(str) { + if(str.indexOf(' ') >= 0) { + return '"' + str + '"' + } + return str + } - $('').appendTo(popOverContent) + $('') + .appendTo(popOverContent) + .on('click', function() { + RED.search.show('type:' + wrapStr(safeType)) + }) $('').appendTo(popOverContent) $('

    ',{style:"font-size: 0.8em"}).text(metaData).appendTo(popOverContent); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/search.js b/packages/node_modules/@node-red/editor-client/src/js/ui/search.js index f9cc79f08..d8035908a 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/search.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/search.js @@ -106,14 +106,26 @@ RED.search = (function() { return val; } + function extractType(val, flags) { + // extracts: type:XYZ & type:"X Y Z" + console.log(`extractType(val, flags): val:${val}`) + const regEx = /(?:type):\s*(?:"([^"]+)"|([^" ]+))/; + let m + while ((m = regEx.exec(val)) !== null) { + // avoid infinite loops with zero-width matches + if (m.index === regEx.lastIndex) { + regEx.lastIndex++; + } + val = val.replace(m[0]," ").trim() + const flag = m[2] || m[1] // quoted entries in capture group 1, unquoted in capture group 2 + flags.type = flags.type || []; + flags.type.push(flag); + } + return val; + } + function search(val) { var results = []; - var typeFilter; - var m = /(?:^| )type:([^ ]+)/.exec(val); - if (m) { - val = val.replace(/(?:^| )type:[^ ]+/,""); - typeFilter = m[1]; - } var flags = {}; val = extractFlag(val,"invalid",flags); val = extractFlag(val,"unused",flags); @@ -121,18 +133,20 @@ RED.search = (function() { val = extractFlag(val,"subflow",flags); val = extractFlag(val,"hidden",flags); val = extractFlag(val,"modified",flags); - val = extractValue(val,"flow",flags);// flow:active or flow: + val = extractValue(val,"flow",flags);// flow:current or flow: val = extractValue(val,"uses",flags);// uses: + val = extractType(val,flags);// uses: val = val.trim(); var hasFlags = Object.keys(flags).length > 0; if (flags.flow && flags.flow.indexOf("current") >= 0) { let idx = flags.flow.indexOf("current"); - flags.flow[idx] = RED.workspaces.active();//convert active to flow ID + flags.flow[idx] = RED.workspaces.active();//convert 'current' to active flow ID } if (flags.flow && flags.flow.length) { flags.flow = [ ...new Set(flags.flow) ]; //deduplicate } - if (val.length > 0 || typeFilter || hasFlags) { + const hasTypeFilter = flags.type && flags.type.length > 0 + if (val.length > 0 || hasFlags) { val = val.toLowerCase(); var i; var j; @@ -146,8 +160,8 @@ RED.search = (function() { } for (i=0;i -1) { + var kpos = val ? keys[i].indexOf(val) : -1; + if (kpos > -1 || (val === "" && hasTypeFilter)) { var ids = Object.keys(index[key]||{}); for (j=0;j -1) { + nodes[node.node.id] = nodes[node.node.id] || { node: node.node, label: node.label }; - nodes[node.node.id].index = Math.min(nodes[node.node.id].index||Infinity,kpos); + nodes[node.node.id].index = Math.min(nodes[node.node.id].index || Infinity, typeIndex > -1 ? typeIndex : kpos); } } } From 7d4c857a43c61002844a841af6a1a8f1dd7a62c9 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Fri, 12 Aug 2022 15:47:15 +0100 Subject: [PATCH 142/237] ensure sessionExpiry(Interval) is applied --- .../node_modules/@node-red/nodes/core/network/10-mqtt.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js b/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js index 9e6195b5d..dccf1310b 100644 --- a/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js +++ b/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js @@ -474,7 +474,6 @@ module.exports = function(RED) { setIfHasProperty(opts, node, "protocolVersion", init); setIfHasProperty(opts, node, "keepalive", init); setIfHasProperty(opts, node, "cleansession", init); - setIfHasProperty(opts, node, "sessionExpiry", init); setIfHasProperty(opts, node, "topicAliasMaximum", init); setIfHasProperty(opts, node, "maximumPacketSize", init); setIfHasProperty(opts, node, "receiveMaximum", init); @@ -484,6 +483,11 @@ module.exports = function(RED) { } else if (hasProperty(opts, "userProps")) { node.userProperties = opts.userProps; } + if (hasProperty(opts, "sessionExpiry")) { + node.sessionExpiryInterval = opts.sessionExpiry; + } else if (hasProperty(opts, "sessionExpiryInterval")) { + node.sessionExpiryInterval = opts.sessionExpiryInterval + } function createLWT(topic, payload, qos, retain, v5opts, v5SubPropName) { let message = undefined; From 5a36e8fb119c79a1f6c99ee08baaf8d7076253f4 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Fri, 12 Aug 2022 18:20:11 +0100 Subject: [PATCH 143/237] add tests for MQTT v5 (sessionExpiry property) --- test/nodes/core/network/21-mqtt_spec.js | 49 ++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/test/nodes/core/network/21-mqtt_spec.js b/test/nodes/core/network/21-mqtt_spec.js index 655cc9c73..44154a802 100644 --- a/test/nodes/core/network/21-mqtt_spec.js +++ b/test/nodes/core/network/21-mqtt_spec.js @@ -27,7 +27,7 @@ describe('MQTT Nodes', function () { } catch (error) { } }); - it('should be loaded and have default values', function (done) { + it('should be loaded and have default values (MQTT V4)', function (done) { this.timeout = 2000; const { flow, nodes } = buildBasicMQTTSendRecvFlow({ id: "mqtt.broker", name: "mqtt_broker", autoConnect: false }, { id: "mqtt.in", topic: "in_topic" }, { id: "mqtt.out", topic: "out_topic" }); helper.load(mqttNodes, flow, function () { @@ -61,6 +61,52 @@ describe('MQTT Nodes', function () { mqttBroker.options.clientId.should.containEql('nodered_'); mqttBroker.options.should.have.property('keepalive').type("number"); mqttBroker.options.should.have.property('reconnectPeriod').type("number"); + //as this is not a v5 connection, ensure v5 properties are not present + mqttBroker.options.should.not.have.property('protocolVersion', 5); + mqttBroker.options.should.not.have.property('properties'); + done(); + } catch (error) { + done(error) + } + }); + }); + it('should be loaded and have default values (MQTT V5)', function (done) { + this.timeout = 2000; + const { flow, nodes } = buildBasicMQTTSendRecvFlow({ id: "mqtt.broker", name: "mqtt_broker", autoConnect: false, cleansession: false, clientid: 'clientid', keepalive: 35, sessionExpiry: '6000', protocolVersion: '5', userProps: {"prop": "val"}}, { id: "mqtt.in", topic: "in_topic" }, { id: "mqtt.out", topic: "out_topic" }); + helper.load(mqttNodes, flow, function () { + try { + const mqttIn = helper.getNode("mqtt.in"); + const mqttOut = helper.getNode("mqtt.out"); + const mqttBroker = helper.getNode("mqtt.broker"); + + should(mqttIn).be.type("object", "mqtt in node should be an object") + mqttIn.should.have.property('broker', nodes.mqtt_broker.id); //should be the id of the broker node + mqttIn.should.have.property('datatype', 'utf8'); //default: 'utf8' + mqttIn.should.have.property('isDynamic', false); //default: false + mqttIn.should.have.property('inputs', 0); //default: 0 + mqttIn.should.have.property('qos', 2); //default: 2 + mqttIn.should.have.property('topic', "in_topic"); + mqttIn.should.have.property('wires', [["helper.node"]]); + + should(mqttOut).be.type("object", "mqtt out node should be an object") + mqttOut.should.have.property('broker', nodes.mqtt_broker.id); //should be the id of the broker node + mqttOut.should.have.property('topic', "out_topic"); + + should(mqttBroker).be.type("object", "mqtt broker node should be an object") + mqttBroker.should.have.property('broker', BROKER_HOST); + mqttBroker.should.have.property('port', BROKER_PORT); + mqttBroker.should.have.property('brokerurl'); + // mqttBroker.should.have.property('autoUnsubscribe', true);//default: true + mqttBroker.should.have.property('autoConnect', false);//Set "autoConnect:false" in brokerOptions + mqttBroker.should.have.property('options'); + mqttBroker.options.should.have.property('clean', false); + mqttBroker.options.should.have.property('clientId', 'clientid'); + mqttBroker.options.should.have.property('keepalive').type("number", 35); + mqttBroker.options.should.have.property('reconnectPeriod').type("number"); + //as this IS a v5 connection, ensure v5 properties are not present + mqttBroker.options.should.have.property('protocolVersion', 5); + mqttBroker.options.should.have.property('properties'); + mqttBroker.options.properties.should.have.property('sessionExpiryInterval'); done(); } catch (error) { done(error) @@ -666,6 +712,7 @@ function buildMQTTBrokerNode(id, name, brokerHost, brokerPort, options) { node.cleansession = String(options.cleansession) == "false" ? false : true; node.autoUnsubscribe = String(options.autoUnsubscribe) == "false" ? false : true; node.autoConnect = String(options.autoConnect) == "false" ? false : true; + node.sessionExpiry = options.sessionExpiry ? options.sessionExpiry : undefined; if (options.birthTopic) { node.birthTopic = options.birthTopic; From 5c6b8e9e50dfd26e5acbac5dec6bd3b65c468048 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Fri, 12 Aug 2022 18:21:36 +0100 Subject: [PATCH 144/237] opportunistic tidy up MQTT tests --- test/nodes/core/network/21-mqtt_spec.js | 29 ++++++++++++++++++------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/test/nodes/core/network/21-mqtt_spec.js b/test/nodes/core/network/21-mqtt_spec.js index 44154a802..fd1309b74 100644 --- a/test/nodes/core/network/21-mqtt_spec.js +++ b/test/nodes/core/network/21-mqtt_spec.js @@ -219,7 +219,7 @@ describe('MQTT Nodes', function () { topic: nextTopic(), payload: '{prop:"value3", "num":3}', // send invalid JSON ... } - const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null } + const hooks = { done: null, beforeLoad: null, afterLoad: null, afterConnect: null } hooks.afterLoad = (helperNode, mqttBroker, mqttIn, mqttOut) => { helperNode.on("input", function (msg) { try { @@ -345,7 +345,7 @@ describe('MQTT Nodes', function () { topic: nextTopic(), payload: '{prop:"value3", "num":3}', contentType: "application/json", // send invalid JSON ... } - const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null } + const hooks = { done: null, beforeLoad: null, afterLoad: null, afterConnect: null } hooks.afterLoad = (helperNode, mqttBroker, mqttIn, mqttOut) => { helperNode.on("input", function (msg) { try { @@ -431,7 +431,7 @@ describe('MQTT Nodes', function () { if (skipTests) { return this.skip() } this.timeout = 2000; const options = {} - const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null } + const hooks = { beforeLoad: null, afterLoad: null, afterConnect: null } hooks.beforeLoad = (flow) => { //add a status node pointed at MQTT Out node (to watch for connection status change) flow.push({ "id": "status.node", "type": "status", "name": "status_node", "scope": ["mqtt.out"], "wires": [["helper.node"]] });//add status node to watch mqtt_out } @@ -462,18 +462,31 @@ describe('MQTT Nodes', function () { this.timeout = 2000; const baseTopic = nextTopic(); const brokerOptions = { + autoConnect: false, protocolVersion: 4, birthTopic: baseTopic + "/birth", - birthPayload: "broker connected", + birthPayload: "broker birth", birthQos: 2, } - const options = {}; - const hooks = { done: done, beforeLoad: null, afterLoad: null, afterConnect: null }; - options.expectMsg = { + const expectMsg = { topic: brokerOptions.birthTopic, payload: brokerOptions.birthPayload, qos: brokerOptions.birthQos }; + const options = { }; + const hooks = { }; + hooks.afterLoad = (helperNode, mqttBroker, mqttIn, mqttOut) => { + helperNode.on("input", function (msg) { + try { + compareMsgToExpected(msg, expectMsg); + done(); + } catch (error) { + done(error) + } + }) + mqttIn.receive({ "action": "connect" }); //now request connect action + return true; //handled + } testSendRecv(brokerOptions, { topic: brokerOptions.birthTopic }, {}, options, hooks); }); itConditional('should publish close message', function (done) { @@ -807,8 +820,8 @@ function waitBrokerConnect(broker, timeLimit) { let waitConnected = (broker, timeLimit) => { const brokers = Array.isArray(broker) ? broker : [broker]; timeLimit = timeLimit || 1000; - let timer, resolved = false; return new Promise( (resolve, reject) => { + let timer, resolved = false; timer = wait(); function wait() { if (brokers.every(e => e.connected == true)) { From 31b17faa2ade0e8a22b588ae29ae6c86e6eac0e8 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Fri, 12 Aug 2022 18:23:07 +0100 Subject: [PATCH 145/237] fix MQTT test fail due to birth sent before connection done --- packages/node_modules/@node-red/nodes/core/network/10-mqtt.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js b/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js index dccf1310b..50b1d690f 100644 --- a/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js +++ b/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js @@ -786,7 +786,9 @@ module.exports = function(RED) { // Send any birth message if (node.birthMessage) { - node.publish(node.birthMessage); + setTimeout(() => { + node.publish(node.birthMessage); + }, 1); } }); node._clientOn("reconnect", function() { From 9540cfe74931720aa34f1b0e8dbbef566cb91c8b Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Sat, 13 Aug 2022 18:09:11 +0100 Subject: [PATCH 146/237] fix flags search without val --- .../editor-client/src/js/ui/search.js | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/search.js b/packages/node_modules/@node-red/editor-client/src/js/ui/search.js index d8035908a..7d147bde9 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/search.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/search.js @@ -125,8 +125,8 @@ RED.search = (function() { } function search(val) { - var results = []; - var flags = {}; + const results = []; + const flags = {}; val = extractFlag(val,"invalid",flags); val = extractFlag(val,"unused",flags); val = extractFlag(val,"config",flags); @@ -137,7 +137,8 @@ RED.search = (function() { val = extractValue(val,"uses",flags);// uses: val = extractType(val,flags);// uses: val = val.trim(); - var hasFlags = Object.keys(flags).length > 0; + const hasFlags = Object.keys(flags).length > 0; + const hasTypeFilter = flags.type && flags.type.length > 0 if (flags.flow && flags.flow.indexOf("current") >= 0) { let idx = flags.flow.indexOf("current"); flags.flow[idx] = RED.workspaces.active();//convert 'current' to active flow ID @@ -145,13 +146,12 @@ RED.search = (function() { if (flags.flow && flags.flow.length) { flags.flow = [ ...new Set(flags.flow) ]; //deduplicate } - const hasTypeFilter = flags.type && flags.type.length > 0 if (val.length > 0 || hasFlags) { val = val.toLowerCase(); - var i; - var j; - var list = []; - var nodes = {}; + let i; + let j; + let list = []; + const nodes = {}; let keys = []; if (flags.uses) { keys = flags.uses; @@ -159,10 +159,10 @@ RED.search = (function() { keys = Object.keys(index); } for (i=0;i -1 || (val === "" && hasTypeFilter)) { - var ids = Object.keys(index[key]||{}); + const key = keys[i]; + const kpos = val ? keys[i].indexOf(val) : -1; + if (kpos > -1 || (val === "" && hasFlags)) { + const ids = Object.keys(index[key]||{}); for (j=0;j Date: Sun, 14 Aug 2022 15:02:39 +0100 Subject: [PATCH 147/237] Add missing property to node object HTTPRequest Also add tests for broken headers --- .../nodes/core/network/21-httprequest.js | 1 + .../nodes/core/network/21-httprequest_spec.js | 71 +++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js index b94d59dc7..9eac4da91 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js +++ b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js @@ -86,6 +86,7 @@ in your Node-RED user directory (${RED.settings.userDir}). if (n.paytoqs === true || n.paytoqs === "query") { paytoqs = true; } else if (n.paytoqs === "body") { paytobody = true; } + node.insecureHTTPParser = n.insecureHTTPParser var prox, noprox; if (process.env.http_proxy) { prox = process.env.http_proxy; } diff --git a/test/nodes/core/network/21-httprequest_spec.js b/test/nodes/core/network/21-httprequest_spec.js index 688776289..dff99f3b0 100644 --- a/test/nodes/core/network/21-httprequest_spec.js +++ b/test/nodes/core/network/21-httprequest_spec.js @@ -31,6 +31,8 @@ var multer = require("multer"); var RED = require("nr-test-utils").require("node-red/lib/red"); var fs = require('fs-extra'); var auth = require('basic-auth'); +const { version } = require("os"); +const net = require('net') describe('HTTP Request Node', function() { var testApp; @@ -2265,4 +2267,73 @@ describe('HTTP Request Node', function() { }); }); }); + + describe('should parse broken headers', function(done) { + + const versions = process.versions.node.split('.') + + if (( versions[0] == 14 && versions[1] >= 20 ) || + ( versions[0] == 16 && versions[1] >= 16 ) || + ( versions[0] == 18 && versions[1] >= 5 ) || + ( versions[0] > 18)) { + // only test if on new enough NodeJS version + + let port = testPort++ + + let server; + + before(function() { + server = net.createServer(function (socket) { + socket.write("HTTP/1.0 200\nContent-Type: text/plain\n\nHelloWorld") + socket.end() + }) + + server.listen(port,'127.0.0.1', function(err) { + }) + }); + + after(function() { + server.close() + }); + + it('should accept broken headers', function (done) { + var flow = [{id:'n1',type:'http request',wires:[['n2']],method:'GET',ret:'obj',url:`http://localhost:${port}/`, insecureHTTPParser: true}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on('input', function(msg) { + try { + msg.payload.should.equal('HelloWorld') + done() + } catch (err) { + done(err) + } + }) + n1.receive({payload: 'foo'}) + }); + }); + + it('should reject broken headers', function (done) { + var flow = [{id:'n1',type:'http request',wires:[['n2']],method:'GET',ret:'obj',url:`http://localhost:${port}/`}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on('input', function(msg) { + try{ + msg.payload.should.equal('RequestError: Parse Error: Missing expected CR after header value : http://localhost:10234/') + done() + } catch (err) { + done(err) + } + }) + n1.receive({payload: 'foo'}) + + }); + }); + } else { + done() + } + }); }); From 58b951e134ab7815640c7184ea33bd561ec427e3 Mon Sep 17 00:00:00 2001 From: Ben Hardill Date: Sun, 14 Aug 2022 15:09:48 +0100 Subject: [PATCH 148/237] Make port number dynamic in test --- test/nodes/core/network/21-httprequest_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/nodes/core/network/21-httprequest_spec.js b/test/nodes/core/network/21-httprequest_spec.js index dff99f3b0..65f8f5814 100644 --- a/test/nodes/core/network/21-httprequest_spec.js +++ b/test/nodes/core/network/21-httprequest_spec.js @@ -2322,7 +2322,7 @@ describe('HTTP Request Node', function() { var n2 = helper.getNode("n2"); n2.on('input', function(msg) { try{ - msg.payload.should.equal('RequestError: Parse Error: Missing expected CR after header value : http://localhost:10234/') + msg.payload.should.equal(`RequestError: Parse Error: Missing expected CR after header value : http://localhost:${port}/`) done() } catch (err) { done(err) From 0d0d5bafb0f530fdcefe5576b248d63103b4a458 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Thu, 18 Aug 2022 01:29:07 +0900 Subject: [PATCH 149/237] Make pre-defined header list sortable in http request node --- .../@node-red/nodes/core/network/21-httprequest.html | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html index b9a44e1b4..eb0aeaf47 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html +++ b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html @@ -417,6 +417,7 @@ }); }, + sortable: true, removable: true }); if (node.headers) { From 266ba17ebb741aef2e5eefb8285eccc55fecff0f Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Thu, 18 Aug 2022 11:52:56 +0900 Subject: [PATCH 150/237] Use sortable list in http response node and proxy setting --- .../node_modules/@node-red/nodes/core/network/06-httpproxy.html | 1 + .../node_modules/@node-red/nodes/core/network/21-httpin.html | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/node_modules/@node-red/nodes/core/network/06-httpproxy.html b/packages/node_modules/@node-red/nodes/core/network/06-httpproxy.html index 7819ab8ec..446cbd324 100644 --- a/packages/node_modules/@node-red/nodes/core/network/06-httpproxy.html +++ b/packages/node_modules/@node-red/nodes/core/network/06-httpproxy.html @@ -101,6 +101,7 @@ hostField.val(data.host); } }, + sortable: true, removable: true }); if (this.noproxy) { diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httpin.html b/packages/node_modules/@node-red/nodes/core/network/21-httpin.html index ce2f81e12..f828077a1 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httpin.html +++ b/packages/node_modules/@node-red/nodes/core/network/21-httpin.html @@ -227,6 +227,7 @@ } }); }, + sortable: true, removable: true }); From d0d22c333c3d9479d19544b3726bff73fa5738fc Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sun, 28 Aug 2022 02:04:13 +0900 Subject: [PATCH 151/237] Add Japanese translation for v3.0.2 --- .../@node-red/editor-client/locales/ja/editor.json | 3 +-- packages/node_modules/@node-red/nodes/locales/ja/messages.json | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index fb3458eed..dc7858250 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -1168,8 +1168,7 @@ "takeATour": "ツアーを開始", "start": "開始", "next": "次へ", - "welcomeTours": "ウェルカムツアー", - "tours": "ツアー" + "welcomeTours": "ウェルカムツアー" }, "diagnostics": { "title": "システム情報" diff --git a/packages/node_modules/@node-red/nodes/locales/ja/messages.json b/packages/node_modules/@node-red/nodes/locales/ja/messages.json index 6e16daa6f..76c8f8462 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ja/messages.json @@ -554,7 +554,8 @@ }, "status": { "requesting": "要求中" - } + }, + "insecureHTTPParser": "厳密なHTTPパース処理を無効化" }, "websocket": { "label": { From cd8ca8981ba4146de22e79457fb7d46651061005 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sat, 3 Sep 2022 21:03:08 +0900 Subject: [PATCH 152/237] Add button type to the adding SSH key button --- .../@node-red/editor-client/src/js/ui/projects/projects.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js index 190561e15..30a1e51d0 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js @@ -545,7 +545,7 @@ RED.projects = (function() { var sshwarningRow = $('

    ').hide().appendTo(subrow); $('
    '+RED._("projects.clone-project.ssh-key-desc")+'
    ').appendTo(sshwarningRow); subrow = $('
    ').appendTo(sshwarningRow); - $('').appendTo(subrow).on("click", function(e) { + $('').appendTo(subrow).on("click", function(e) { e.preventDefault(); dialog.dialog( "close" ); RED.userSettings.show('gitconfig'); From d6bb3a558f6da935e05488cb1708562b0b242ebb Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Sat, 3 Sep 2022 21:23:38 +0100 Subject: [PATCH 153/237] fix loading node package in nodesDir on linux fixes #3861 --- .../@node-red/registry/lib/loader.js | 39 ++++++++++--------- .../@node-red/registry/lib/localfilesystem.js | 10 +++++ .../@node-red/registry/lib/registry.js | 11 +++++- 3 files changed, 40 insertions(+), 20 deletions(-) diff --git a/packages/node_modules/@node-red/registry/lib/loader.js b/packages/node_modules/@node-red/registry/lib/loader.js index 61f28ab86..3f0487750 100644 --- a/packages/node_modules/@node-red/registry/lib/loader.js +++ b/packages/node_modules/@node-red/registry/lib/loader.js @@ -43,37 +43,40 @@ function load(disableNodePathScan) { return loadModuleFiles(modules); } +function splitPath(p) { + return path.posix.normalize((p || '').replace(/\\/g, path.sep)).split(path.sep) +} function loadModuleTypeFiles(module, type) { const things = module[type]; - var first = true; - var promises = []; - for (var thingName in things) { + let first = true; + const promises = []; + for (let thingName in things) { /* istanbul ignore else */ if (things.hasOwnProperty(thingName)) { if (module.name != "node-red" && first) { // Check the module directory exists first = false; - var fn = things[thingName].file; - var parts = fn.split("/"); - var i = parts.length-1; - for (;i>=0;i--) { - if (parts[i] == "node_modules") { - break; - } + let moduleFn = module.path + const fn = things[thingName].file + const parts = splitPath(fn) + const nmi = parts.indexOf('node_modules') + if(nmi > -1) { + moduleFn = parts.slice(0,nmi+2).join(path.sep); + } + if (!moduleFn) { + // shortcut - skip calling statSync on empty string + break; // Module not found, don't attempt to load its nodes } - var moduleFn = parts.slice(0,i+2).join("/"); - try { - var stat = fs.statSync(moduleFn); + const stat = fs.statSync(moduleFn); } catch(err) { - // Module not found, don't attempt to load its nodes - break; + break; // Module not found, don't attempt to load its nodes } } try { - var promise; + let promise; if (type === "nodes") { promise = loadNodeConfig(things[thingName]); } else if (type === "plugins") { @@ -82,8 +85,7 @@ function loadModuleTypeFiles(module, type) { promises.push( promise.then( (function() { - var m = module.name; - var n = thingName; + const n = thingName; return function(nodeSet) { things[n] = nodeSet; return nodeSet; @@ -93,7 +95,6 @@ function loadModuleTypeFiles(module, type) { ); } catch(err) { console.log(err) - // } } } diff --git a/packages/node_modules/@node-red/registry/lib/localfilesystem.js b/packages/node_modules/@node-red/registry/lib/localfilesystem.js index da4006ecc..edb805572 100644 --- a/packages/node_modules/@node-red/registry/lib/localfilesystem.js +++ b/packages/node_modules/@node-red/registry/lib/localfilesystem.js @@ -156,6 +156,16 @@ function scanDirForNodesModules(dir,moduleName,package) { } } } + + // if we have found a package.json, this IS a node_module, lets see if it is a node-red node + if (!isNodeRedModule && files.indexOf('package.json') > -1) { + package = getPackageDetails(dir) // get package details + if(package && package.isNodeRedModule) { + isNodeRedModule = true + files = ['package.json'] // shortcut the file scan + } + } + for (let i=0;i Date: Sat, 3 Sep 2022 21:37:27 +0100 Subject: [PATCH 154/237] add test for specific arrangement of node package --- .../registry/lib/localfilesystem_spec.js | 21 ++++++++++++++- .../@lower-case2/lower-case2/lower-case.html | 26 +++++++++++++++++++ .../@lower-case2/lower-case2/lower-case.js | 11 ++++++++ .../nodesDir2/@lower-case2/package.json | 9 +++++++ .../lower-case/lower-case/lower-case.html | 26 +++++++++++++++++++ .../lower-case/lower-case/lower-case.js | 11 ++++++++ .../nodesDir2/lower-case/package.json | 9 +++++++ 7 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/lower-case2/lower-case.html create mode 100644 test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/lower-case2/lower-case.js create mode 100644 test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/package.json create mode 100644 test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/lower-case/lower-case.html create mode 100644 test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/lower-case/lower-case.js create mode 100644 test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/package.json diff --git a/test/unit/@node-red/registry/lib/localfilesystem_spec.js b/test/unit/@node-red/registry/lib/localfilesystem_spec.js index 3b1bb63f3..3ce74c158 100644 --- a/test/unit/@node-red/registry/lib/localfilesystem_spec.js +++ b/test/unit/@node-red/registry/lib/localfilesystem_spec.js @@ -329,17 +329,36 @@ describe("red/nodes/registry/localfilesystem",function() { localfilesystem.init({nodesDir:[nodesDir2]}); const nodeModule = localfilesystem.getModuleFiles(); const loaded = Object.keys(nodeModule) - loaded.should.have.a.property("length", 3) loaded.indexOf('@test/testnode').should.greaterThan(-1, "Should load @test/testnode") + loaded.indexOf('lower-case').should.greaterThan(-1, "Should load lower-case") + loaded.indexOf('@lowercase/lower-case2').should.greaterThan(-1, "Should load @lowercase/lower-case2") loaded.indexOf('testnode2').should.greaterThan(-1, "Should load testnode2") loaded.indexOf('test-theme2').should.greaterThan(-1, "Should load test-theme2") + loaded.should.have.a.property("length", 5) + // scoped module with nodes in same dir as package.json nodeModule['@test/testnode'].should.have.a.property('name','@test/testnode'); nodeModule['@test/testnode'].should.have.a.property('version','1.0.0'); nodeModule['@test/testnode'].should.have.a.property('nodes'); nodeModule['@test/testnode'].should.have.a.property('path'); nodeModule['@test/testnode'].should.have.a.property('user', false); + // node-red module with nodes in sub dir + nodeModule['@lowercase/lower-case2'].should.have.a.property('name','@lowercase/lower-case2'); + nodeModule['@lowercase/lower-case2'].should.have.a.property('version','2.0.0'); + nodeModule['@lowercase/lower-case2'].should.have.a.property('nodes'); + nodeModule['@lowercase/lower-case2'].nodes.should.have.a.property('lower-case'); + nodeModule['@lowercase/lower-case2'].should.have.a.property('path'); + nodeModule['@lowercase/lower-case2'].should.have.a.property('user', false); + + // scoped module with nodes in sub dir + nodeModule['lower-case'].should.have.a.property('name', 'lower-case'); + nodeModule['lower-case'].should.have.a.property('version','1.0.0'); + nodeModule['lower-case'].should.have.a.property('nodes'); + nodeModule['lower-case'].nodes.should.have.a.property('lower-case'); + nodeModule['lower-case'].should.have.a.property('path'); + nodeModule['lower-case'].should.have.a.property('user', false); + nodeModule['testnode2'].should.have.a.property('name','testnode2'); nodeModule['testnode2'].should.have.a.property('version','1.0.0'); nodeModule['testnode2'].should.have.a.property('nodes'); diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/lower-case2/lower-case.html b/test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/lower-case2/lower-case.html new file mode 100644 index 000000000..617f48491 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/lower-case2/lower-case.html @@ -0,0 +1,26 @@ + + + + + \ No newline at end of file diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/lower-case2/lower-case.js b/test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/lower-case2/lower-case.js new file mode 100644 index 000000000..73579ba04 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/lower-case2/lower-case.js @@ -0,0 +1,11 @@ +module.exports = function(RED) { +function LowerCaseNode(config) { + RED.nodes.createNode(this,config); + var node = this; + node.on('input', function(msg) { + msg.payload = msg.payload.toLowerCase(); + node.send(msg); + }); + } + RED.nodes.registerType("lower-case2",LowerCaseNode); +} \ No newline at end of file diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/package.json b/test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/package.json new file mode 100644 index 000000000..6b6ce9aa9 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/@lower-case2/package.json @@ -0,0 +1,9 @@ +{ + "name" : "@lowercase/lower-case2", + "node-red" : { + "nodes": { + "lower-case": "lower-case2/lower-case.js" + } + }, + "version": "2.0.0" +} \ No newline at end of file diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/lower-case/lower-case.html b/test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/lower-case/lower-case.html new file mode 100644 index 000000000..e57d51131 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/lower-case/lower-case.html @@ -0,0 +1,26 @@ + + + + + diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/lower-case/lower-case.js b/test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/lower-case/lower-case.js new file mode 100644 index 000000000..006b35eb6 --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/lower-case/lower-case.js @@ -0,0 +1,11 @@ +module.exports = function(RED) { + function LowerCaseNode(config) { + RED.nodes.createNode(this,config); + var node = this; + node.on('input', function(msg) { + msg.payload = msg.payload.toLowerCase(); + node.send(msg); + }); + } + RED.nodes.registerType("lower-case",LowerCaseNode); +} diff --git a/test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/package.json b/test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/package.json new file mode 100644 index 000000000..a632eaddd --- /dev/null +++ b/test/unit/@node-red/registry/lib/resources/nodesDir2/lower-case/package.json @@ -0,0 +1,9 @@ +{ + "name" : "lower-case", + "node-red" : { + "nodes": { + "lower-case": "lower-case/lower-case.js" + } + }, + "version": "1.0.0" +} From 5f159c1fbd4e78373292472263f8caa0d720721d Mon Sep 17 00:00:00 2001 From: Stephen McLaughlin <44235289+Steve-Mcl@users.noreply.github.com> Date: Sat, 3 Sep 2022 21:54:21 +0100 Subject: [PATCH 155/237] Update packages/node_modules/@node-red/editor-client/src/js/ui/search.js Co-authored-by: Nick O'Leary --- .../node_modules/@node-red/editor-client/src/js/ui/search.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/search.js b/packages/node_modules/@node-red/editor-client/src/js/ui/search.js index 7d147bde9..6dcd44b88 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/search.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/search.js @@ -108,7 +108,6 @@ RED.search = (function() { function extractType(val, flags) { // extracts: type:XYZ & type:"X Y Z" - console.log(`extractType(val, flags): val:${val}`) const regEx = /(?:type):\s*(?:"([^"]+)"|([^" ]+))/; let m while ((m = regEx.exec(val)) !== null) { From c038c99f9dbc54f15c88f6ec8a2b5f379d4e8253 Mon Sep 17 00:00:00 2001 From: Stephen McLaughlin <44235289+Steve-Mcl@users.noreply.github.com> Date: Sat, 3 Sep 2022 21:54:41 +0100 Subject: [PATCH 156/237] Update packages/node_modules/@node-red/editor-client/src/js/ui/search.js Co-authored-by: Nick O'Leary --- .../node_modules/@node-red/editor-client/src/js/ui/search.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/search.js b/packages/node_modules/@node-red/editor-client/src/js/ui/search.js index 6dcd44b88..3903a4a0a 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/search.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/search.js @@ -134,7 +134,7 @@ RED.search = (function() { val = extractFlag(val,"modified",flags); val = extractValue(val,"flow",flags);// flow:current or flow: val = extractValue(val,"uses",flags);// uses: - val = extractType(val,flags);// uses: + val = extractType(val,flags);// type: val = val.trim(); const hasFlags = Object.keys(flags).length > 0; const hasTypeFilter = flags.type && flags.type.length > 0 From e11f17672c0b208d7560c249ca617ef989b11ee9 Mon Sep 17 00:00:00 2001 From: Stephen McLaughlin <44235289+Steve-Mcl@users.noreply.github.com> Date: Sat, 3 Sep 2022 22:01:54 +0100 Subject: [PATCH 157/237] Update packages/node_modules/@node-red/editor-client/src/js/ui/palette.js --- .../node_modules/@node-red/editor-client/src/js/ui/palette.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js index 322283fcb..928793610 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js @@ -176,7 +176,7 @@ RED.palette = (function() { } const safeType = type.replace(/'/g,"\\'"); - function wrapStr(str) { + const wrapStr = function (str) { if(str.indexOf(' ') >= 0) { return '"' + str + '"' } From a533943a4043cd915ec40e37a424ae168a0b4793 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Sun, 4 Sep 2022 01:50:54 +0100 Subject: [PATCH 158/237] add function node monaco types util and promisify fixes #3851 --- .../editor-client/src/js/ui/editors/code-editors/monaco.js | 2 +- .../@node-red/editor-client/src/types/node-red/func.d.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js index 497116125..68b9a487e 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js @@ -100,7 +100,7 @@ RED.editor.codeEditor.monaco = (function() { "node-red-util": {package: "node-red", module: "util", path: "node-red/util.d.ts" }, "node-red-func": {package: "node-red", module: "func", path: "node-red/func.d.ts" }, } - const defaultServerSideTypes = [ knownModules["node-red-util"], knownModules["node-red-func"], knownModules["globals"], knownModules["console"], knownModules["buffer"] ]; + const defaultServerSideTypes = [ knownModules["node-red-util"], knownModules["node-red-func"], knownModules["globals"], knownModules["console"], knownModules["buffer"] , knownModules["util"] ]; const modulesCache = {}; diff --git a/packages/node_modules/@node-red/editor-client/src/types/node-red/func.d.ts b/packages/node_modules/@node-red/editor-client/src/types/node-red/func.d.ts index ae411f33c..fd2adcbd8 100644 --- a/packages/node_modules/@node-red/editor-client/src/types/node-red/func.d.ts +++ b/packages/node_modules/@node-red/editor-client/src/types/node-red/func.d.ts @@ -14,6 +14,9 @@ declare var msg: NodeMessage; /** @type {string} the id of the incoming `msg` (alias of msg._msgid) */ declare const __msgid__:string; +declare const util:typeof import('util') +declare const promisify:typeof import('util').promisify + /** * @typedef NodeStatus * @type {object} From fbde0091de47a8393e8e541bebc973b51fa0f721 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Sun, 4 Sep 2022 11:08:41 +0100 Subject: [PATCH 159/237] fix node-red crash with invalid mqtt birth topic fixes #3865 --- .../@node-red/nodes/core/network/10-mqtt.js | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js b/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js index 9e6195b5d..532d54d8e 100644 --- a/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js +++ b/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js @@ -991,14 +991,21 @@ module.exports = function(RED) { } if (topicOK) { - node.client.publish(msg.topic, msg.payload, options, function(err) { - done && done(err); - return - }); + node.client.publish(msg.topic, msg.payload, options, function (err) { + if (done) { + done(err) + } else { + node.error(err, msg) + } + }) } else { - const error = new Error(RED._("mqtt.errors.invalid-topic")); - error.warn = true; - done(error); + const error = new Error(RED._("mqtt.errors.invalid-topic")) + error.warn = true + if (done) { + done(error) + } else { + node.warn(error, msg) + } } } }; From cc5a770b164e8e55713c37d468ee2c4c717be1b3 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sun, 4 Sep 2022 21:58:02 +0900 Subject: [PATCH 160/237] Add button type to buttons on projects dialog --- .../src/js/ui/projects/projects.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js index 30a1e51d0..6294c4c51 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js @@ -1171,11 +1171,11 @@ RED.projects = (function() { row = $('
    ').appendTo(container); - var openProject = $('').appendTo(row); - var createAsEmpty = $('').appendTo(row); - // var createAsCopy = $('').appendTo(row); - var createAsClone = $('').appendTo(row); - // var createAsClone = $('').appendTo(row); + var openProject = $('').appendTo(row); + var createAsEmpty = $('').appendTo(row); + // var createAsCopy = $('').appendTo(row); + var createAsClone = $('').appendTo(row); + // var createAsClone = $('').appendTo(row); row.find(".red-ui-projects-dialog-screen-create-type").on("click", function(evt) { evt.preventDefault(); container.find(".red-ui-projects-dialog-screen-create-type").removeClass('selected'); @@ -1397,7 +1397,7 @@ RED.projects = (function() { var sshwarningRow = $('
    ').hide().appendTo(subrow); $('
    '+RED._("projects.create.desc2")+'
    ').appendTo(sshwarningRow); subrow = $('
    ').appendTo(sshwarningRow); - $('').appendTo(subrow).on("click", function(e) { + $('').appendTo(subrow).on("click", function(e) { e.preventDefault(); $('#red-ui-projects-dialog-cancel').trigger("click"); RED.userSettings.show('gitconfig'); @@ -1631,14 +1631,14 @@ RED.projects = (function() { function deleteProject(row,name,done) { var cover = $('
    ').on("click", function(evt) { evt.stopPropagation(); }).appendTo(row); $('').text(RED._("projects.delete.confirm")).appendTo(cover); - $('') + $('') .appendTo(cover) .on("click", function(e) { e.stopPropagation(); cover.remove(); done(true); }); - $('') + $('') .appendTo(cover) .on("click", function(e) { e.stopPropagation(); @@ -1822,7 +1822,7 @@ RED.projects = (function() { header.addClass("selectable"); var tools = $('
    ').appendTo(header); - $('') + $('') .appendTo(tools) .on("click", function(e) { e.stopPropagation(); From 745607b5bce56c125e76f8e4baf6b0707d542ed5 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sun, 4 Sep 2022 23:21:34 +0900 Subject: [PATCH 161/237] Add button type to buttons on node properties --- .../@node-red/nodes/core/common/24-complete.html | 2 +- .../node_modules/@node-red/nodes/core/common/25-catch.html | 2 +- .../node_modules/@node-red/nodes/core/common/25-status.html | 2 +- .../@node-red/nodes/core/function/10-function.html | 6 +++--- .../@node-red/nodes/core/function/80-template.html | 2 +- .../node_modules/@node-red/nodes/core/network/05-tls.html | 6 +++--- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/common/24-complete.html b/packages/node_modules/@node-red/nodes/core/common/24-complete.html index a6a7a2a45..b2d406bb5 100644 --- a/packages/node_modules/@node-red/nodes/core/common/24-complete.html +++ b/packages/node_modules/@node-red/nodes/core/common/24-complete.html @@ -1,6 +1,6 @@ diff --git a/packages/node_modules/@node-red/nodes/locales/ja/function/80-template.html b/packages/node_modules/@node-red/nodes/locales/ja/function/80-template.html index 1a9c17453..9b64003a5 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/function/80-template.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/function/80-template.html @@ -48,4 +48,7 @@

    注: デフォルトでは、mustache形式は置換対象のHTML要素をエスケープします。これを抑止するには{{{三重}}}括弧形式を使います。

    もし、コンテンツの中で{{ }}を出力する必要がある場合は、テンプレートで使われる記号文字を変えることもできます。例えば、[[ ]]を代わりに用いるには、テンプレートの先頭に以下の行を追加します。

    {{=[[ ]]=}}
    +

    環境変数の利用

    +

    templateノードでは、次の構文を用いると環境変数にアクセスできます:

    +
    私の好きな色は{{env.COLOUR}}です。
    From 7507a7b459fdebf22287dda7b9dc4ac939402a4a Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Fri, 16 Sep 2022 02:10:14 +0900 Subject: [PATCH 169/237] Limit number of ports in function node --- .../@node-red/nodes/core/function/10-function.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/nodes/core/function/10-function.html b/packages/node_modules/@node-red/nodes/core/function/10-function.html index fc57895a2..a647bed49 100644 --- a/packages/node_modules/@node-red/nodes/core/function/10-function.html +++ b/packages/node_modules/@node-red/nodes/core/function/10-function.html @@ -451,7 +451,8 @@ tabs.activateTab("func-tab-body"); $( "#node-input-outputs" ).spinner({ - min:0, + min: 0, + max: 10, change: function(event, ui) { var value = this.value; if (!value.match(/^\d+$/)) { value = 1; } From 9fd4989142243d63f914fe523542fc8f03a1abbe Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 15 Sep 2022 21:22:55 +0100 Subject: [PATCH 170/237] Fix autocomplete entry for responseUrl Fixes #3883 --- .../@node-red/editor-client/src/js/ui/common/typedInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js index ef1be05bc..7440b464e 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js @@ -146,7 +146,7 @@ { value: "reset", source: ["delay","trigger","join","rbe"] }, { value: "responseCookies", source: ["http request"] }, { value: "responseTopic", source: ["mqtt"] }, - { value: "responseURL", source: ["http request"] }, + { value: "responseUrl", source: ["http request"] }, { value: "restartTimeout", source: ["join"] }, { value: "retain", source: ["mqtt"] }, { value: "schema", source: ["json"] }, From 199caccbc3244fe73ac131bf5bd772c09fe2d112 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sat, 17 Sep 2022 00:25:46 +0900 Subject: [PATCH 171/237] Change the maximum number of ports to 500 --- .../node_modules/@node-red/nodes/core/function/10-function.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/nodes/core/function/10-function.html b/packages/node_modules/@node-red/nodes/core/function/10-function.html index a647bed49..345a77aab 100644 --- a/packages/node_modules/@node-red/nodes/core/function/10-function.html +++ b/packages/node_modules/@node-red/nodes/core/function/10-function.html @@ -452,7 +452,7 @@ $( "#node-input-outputs" ).spinner({ min: 0, - max: 10, + max: 500, change: function(event, ui) { var value = this.value; if (!value.match(/^\d+$/)) { value = 1; } From ce31edc80327d4141d5a46b6f07ef33c5113c1ec Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sun, 18 Sep 2022 02:22:52 +0900 Subject: [PATCH 172/237] Fix handling of max and min values in function outputs --- .../@node-red/nodes/core/function/10-function.html | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/function/10-function.html b/packages/node_modules/@node-red/nodes/core/function/10-function.html index 345a77aab..43f8c7b39 100644 --- a/packages/node_modules/@node-red/nodes/core/function/10-function.html +++ b/packages/node_modules/@node-red/nodes/core/function/10-function.html @@ -454,9 +454,10 @@ min: 0, max: 500, change: function(event, ui) { - var value = this.value; - if (!value.match(/^\d+$/)) { value = 1; } - else if (value < this.min) { value = this.min; } + var value = parseInt(this.value); + value = isNaN(value) ? 1 : value; + value = Math.max(value, parseInt($(this).attr("aria-valuemin"))); + value = Math.min(value, parseInt($(this).attr("aria-valuemax"))); if (value !== this.value) { $(this).spinner("value", value); } } }); From efc0f1ab9144f6444544095fe7fbdfcd4732f2f0 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sun, 18 Sep 2022 16:24:25 +0900 Subject: [PATCH 173/237] Fix default values for MQTT retain settings --- .../node_modules/@node-red/nodes/core/network/10-mqtt.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/10-mqtt.html b/packages/node_modules/@node-red/nodes/core/network/10-mqtt.html index 03ef4b2e3..cf443f48b 100644 --- a/packages/node_modules/@node-red/nodes/core/network/10-mqtt.html +++ b/packages/node_modules/@node-red/nodes/core/network/10-mqtt.html @@ -499,17 +499,17 @@ cleansession: {value: true}, birthTopic: {value:"", validate:validateMQTTPublishTopic}, birthQos: {value:"0"}, - birthRetain: {value:false}, + birthRetain: {value:"false"}, birthPayload: {value:""}, birthMsg: { value: {}}, closeTopic: {value:"", validate:validateMQTTPublishTopic}, closeQos: {value:"0"}, - closeRetain: {value:false}, + closeRetain: {value:"false"}, closePayload: {value:""}, closeMsg: { value: {}}, willTopic: {value:"", validate:validateMQTTPublishTopic}, willQos: {value:"0"}, - willRetain: {value:false}, + willRetain: {value:"false"}, willPayload: {value:""}, willMsg: { value: {}}, userProps: { value: ""}, From a81b1aa0cb769b3cc90a562f14c6eb79cbaa00ff Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sun, 18 Sep 2022 17:10:19 +0900 Subject: [PATCH 174/237] Support i18n in MQTT node property --- .../@node-red/nodes/core/network/10-mqtt.html | 8 ++++++-- .../@node-red/nodes/locales/en-US/messages.json | 4 +++- .../node_modules/@node-red/nodes/locales/ja/messages.json | 4 +++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/10-mqtt.html b/packages/node_modules/@node-red/nodes/core/network/10-mqtt.html index cf443f48b..6492060e2 100644 --- a/packages/node_modules/@node-red/nodes/core/network/10-mqtt.html +++ b/packages/node_modules/@node-red/nodes/core/network/10-mqtt.html @@ -421,7 +421,11 @@ From 8f27dae7ea20a05c657d672a1408cffce784ffcd Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Mon, 16 Jan 2023 00:56:39 +0900 Subject: [PATCH 219/237] Add Japanese translations for v3.0.3 --- .../node_modules/@node-red/runtime/locales/ja/runtime.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/runtime/locales/ja/runtime.json b/packages/node_modules/@node-red/runtime/locales/ja/runtime.json index bb5b0badc..8e4bceebe 100644 --- a/packages/node_modules/@node-red/runtime/locales/ja/runtime.json +++ b/packages/node_modules/@node-red/runtime/locales/ja/runtime.json @@ -20,6 +20,7 @@ "errors-help": "詳細は -v を指定して実行してください", "missing-modules": "不足しているノードモジュール:", "node-version-mismatch": "ノードモジュールはこのバージョンではロードできません。必要なバージョン: __version__ ", + "set-has-no-types": "セットに型がありません。 名前: '__name__', モジュール: '__module__', ファイル: '__file__'", "type-already-registered": "'__type__' はモジュール __module__ で登録済みです", "removing-modules": "設定からモジュールを削除します", "added-types": "追加したノード:", @@ -134,7 +135,8 @@ "flow": { "unknown-type": "不明なノード: __type__", "missing-types": "欠落したノード", - "error-loop": "メッセージの例外補足回数が最大値を超えました" + "error-loop": "メッセージの例外補足回数が最大値を超えました", + "non-message-returned": "ノードが __type__ 型のメッセージの送信を試みました" }, "index": { "unrecognised-id": "不明なID: __id__", From 10324d8260e5172332fc353f54b67336fede4006 Mon Sep 17 00:00:00 2001 From: Robin Schneider Date: Thu, 19 Jan 2023 10:28:46 +0100 Subject: [PATCH 220/237] Fix typos in settings.js (#4013) * chore: Remove trailing whitespace in settings.js * chore: Fix typos in settings.js * chore: Use consistent terminology in settings.js --- packages/node_modules/node-red/settings.js | 28 +++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/node_modules/node-red/settings.js b/packages/node_modules/node-red/settings.js index ec247a672..901e69789 100644 --- a/packages/node_modules/node-red/settings.js +++ b/packages/node_modules/node-red/settings.js @@ -181,7 +181,7 @@ module.exports = { /** Some nodes, such as HTTP In, can be used to listen for incoming http requests. * By default, these are served relative to '/'. The following property - * can be used to specifiy a different root path. If set to false, this is + * can be used to specify a different root path. If set to false, this is * disabled. */ //httpNodeRoot: '/red-nodes', @@ -219,17 +219,17 @@ module.exports = { /** When httpAdminRoot is used to move the UI to a different root path, the * following property can be used to identify a directory of static content * that should be served at http://localhost:1880/. - * When httpStaticRoot is set differently to httpAdminRoot, there is no need + * When httpStaticRoot is set differently to httpAdminRoot, there is no need * to move httpAdminRoot */ //httpStatic: '/home/nol/node-red-static/', //single static source /* OR multiple static sources can be created using an array of objects... */ //httpStatic: [ - // {path: '/home/nol/pics/', root: "/img/"}, - // {path: '/home/nol/reports/', root: "/doc/"}, + // {path: '/home/nol/pics/', root: "/img/"}, + // {path: '/home/nol/reports/', root: "/doc/"}, //], - /** + /** * All static routes will be appended to httpStaticRoot * e.g. if httpStatic = "/home/nol/docs" and httpStaticRoot = "/static/" * then "/home/nol/docs" will be served at "/static/" @@ -256,11 +256,11 @@ module.exports = { */ // lang: "de", - /** Configure diagnostics options + /** Configure diagnostics options * - enabled: When `enabled` is `true` (or unset), diagnostics data will - * be available at http://localhost:1880/diagnostics - * - ui: When `ui` is `true` (or unset), the action `show-system-info` will - * be available to logged in users of node-red editor + * be available at http://localhost:1880/diagnostics + * - ui: When `ui` is `true` (or unset), the action `show-system-info` will + * be available to logged in users of node-red editor */ diagnostics: { /** enable or disable diagnostics endpoint. Must be set to `false` to disable */ @@ -268,10 +268,10 @@ module.exports = { /** enable or disable diagnostics display in the node-red editor. Must be set to `false` to disable */ ui: true, }, - /** Configure runtimeState options - * - enabled: When `enabled` is `true` flows runtime can be Started/Stoped - * by POSTing to available at http://localhost:1880/flows/state - * - ui: When `ui` is `true`, the action `core:start-flows` and + /** Configure runtimeState options + * - enabled: When `enabled` is `true` flows runtime can be Started/Stopped + * by POSTing to available at http://localhost:1880/flows/state + * - ui: When `ui` is `true`, the action `core:start-flows` and * `core:stop-flows` will be available to logged in users of node-red editor * Also, the deploy menu (when set to default) will show a stop or start button */ @@ -519,7 +519,7 @@ module.exports = { */ //tlsConfigDisableLocalFiles: true, - /** The following property can be used to verify websocket connection attempts. + /** The following property can be used to verify WebSocket connection attempts. * This allows, for example, the HTTP request headers to be checked to ensure * they include valid authentication information. */ From 8d240ca797e19a837aa992437f875882722b1bf0 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 23 Jan 2023 17:44:03 +0000 Subject: [PATCH 221/237] Rename package var to avoid strict more error Fixes #4017 --- .../@node-red/registry/lib/localfilesystem.js | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/node_modules/@node-red/registry/lib/localfilesystem.js b/packages/node_modules/@node-red/registry/lib/localfilesystem.js index edb805572..0c231552f 100644 --- a/packages/node_modules/@node-red/registry/lib/localfilesystem.js +++ b/packages/node_modules/@node-red/registry/lib/localfilesystem.js @@ -106,8 +106,8 @@ function getLocalNodeFiles(dir, skipValidNodeRedModules) { // when loading local files, if the path is a valid node-red module // dont include it (will be picked up in scanTreeForNodesModules) if(skipValidNodeRedModules && files.indexOf("package.json") >= 0) { - const package = getPackageDetails(dir) - if(package.isNodeRedModule) { + const packageDetails = getPackageDetails(dir) + if(packageDetails.isNodeRedModule) { return {files: [], icons: []}; } } @@ -135,17 +135,17 @@ function getLocalNodeFiles(dir, skipValidNodeRedModules) { return {files: result, icons: icons} } -function scanDirForNodesModules(dir,moduleName,package) { +function scanDirForNodesModules(dir,moduleName,packageDetails) { let results = []; let scopeName; let files try { let isNodeRedModule = false - if(package) { - dir = path.join(package.moduleDir,'..') - files = [path.basename(package.moduleDir)] - moduleName = (package.package ? package.package.name : null) || moduleName - isNodeRedModule = package.isNodeRedModule + if(packageDetails) { + dir = path.join(packageDetails.moduleDir,'..') + files = [path.basename(packageDetails.moduleDir)] + moduleName = (packageDetails.package ? packageDetails.package.name : null) || moduleName + isNodeRedModule = packageDetails.isNodeRedModule } else { files = fs.readdirSync(dir); if (moduleName) { @@ -159,8 +159,8 @@ function scanDirForNodesModules(dir,moduleName,package) { // if we have found a package.json, this IS a node_module, lets see if it is a node-red node if (!isNodeRedModule && files.indexOf('package.json') > -1) { - package = getPackageDetails(dir) // get package details - if(package && package.isNodeRedModule) { + packageDetails = getPackageDetails(dir) // get package details + if(packageDetails && packageDetails.isNodeRedModule) { isNodeRedModule = true files = ['package.json'] // shortcut the file scan } @@ -179,8 +179,8 @@ function scanDirForNodesModules(dir,moduleName,package) { } else { if ((isNodeRedModule || (!moduleName || fn == moduleName)) && (isIncluded(fn) && !isExcluded(fn))) { try { - const moduleDir = isNodeRedModule ? package.moduleDir : path.join(dir,fn); - const pkg = package || getPackageDetails(moduleDir) + const moduleDir = isNodeRedModule ? packageDetails.moduleDir : path.join(dir,fn); + const pkg = packageDetails || getPackageDetails(moduleDir) if(pkg.error) { throw pkg.error } From 0346294c590b2c273bf2acf98de89177d0403658 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 23 Jan 2023 17:50:50 +0000 Subject: [PATCH 222/237] Rename package var --- .../storage/localfilesystem/projects/defaultFileSet.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/defaultFileSet.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/defaultFileSet.js index 04b95e3e0..186c6d781 100644 --- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/defaultFileSet.js +++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/defaultFileSet.js @@ -18,7 +18,7 @@ var i18n = require("@node-red/util").i18n; module.exports = { "package.json": function(project) { - var package = { + var packageDetails = { "name": project.name, "description": project.summary||i18n._("storage.localfilesystem.projects.summary"), "version": "0.0.1", @@ -30,11 +30,11 @@ module.exports = { }; if (project.files) { if (project.files.flow) { - package['node-red'].settings.flowFile = project.files.flow; - package['node-red'].settings.credentialsFile = project.files.credentials; + packageDetails['node-red'].settings.flowFile = project.files.flow; + packageDetails['node-red'].settings.credentialsFile = project.files.credentials; } } - return JSON.stringify(package,"",4); + return JSON.stringify(packageDetails,"",4); }, "README.md": function(project) { var content = project.name+"\n"+("=".repeat(project.name.length))+"\n\n"; From c7017ee84b47fc458f71f4ef5d7b27bfae398b36 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 23 Jan 2023 20:42:25 +0000 Subject: [PATCH 223/237] Allow Inject node to work with async context stores Fixes #4014 --- .../@node-red/nodes/core/common/20-inject.js | 77 ++++++++++++------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/common/20-inject.js b/packages/node_modules/@node-red/nodes/core/common/20-inject.js index 734bce765..3f2992cd5 100644 --- a/packages/node_modules/@node-red/nodes/core/common/20-inject.js +++ b/packages/node_modules/@node-red/nodes/core/common/20-inject.js @@ -95,45 +95,64 @@ module.exports = function(RED) { } this.on("input", function(msg, send, done) { - var errors = []; - var props = this.props; + const errors = []; + let props = this.props; if (msg.__user_inject_props__ && Array.isArray(msg.__user_inject_props__)) { props = msg.__user_inject_props__; } delete msg.__user_inject_props__; - props.forEach(p => { - var property = p.p; - var value = p.v ? p.v : ''; - var valueType = p.vt ? p.vt : 'str'; + props = [...props] + function evaluateProperty(doneEvaluating) { + if (props.length === 0) { + doneEvaluating() + return + } + const p = props.shift() + const property = p.p; + const value = p.v ? p.v : ''; + const valueType = p.vt ? p.vt : 'str'; - if (!property) return; - - if (valueType === "jsonata") { - if (p.v) { - try { - var exp = RED.util.prepareJSONataExpression(p.v, node); - var val = RED.util.evaluateJSONataExpression(exp, msg); - RED.util.setMessageProperty(msg, property, val, true); + if (property) { + if (valueType === "jsonata") { + if (p.v) { + try { + var exp = RED.util.prepareJSONataExpression(p.v, node); + var val = RED.util.evaluateJSONataExpression(exp, msg); + RED.util.setMessageProperty(msg, property, val, true); + } + catch (err) { + errors.push(err.message); + } } - catch (err) { - errors.push(err.message); + evaluateProperty(doneEvaluating) + } else { + try { + RED.util.evaluateNodeProperty(value, valueType, node, msg, (err, newValue) => { + if (err) { + errors.push(err.toString()) + } else { + RED.util.setMessageProperty(msg,property,newValue,true); + } + evaluateProperty(doneEvaluating) + }) + } catch (err) { + errors.push(err.toString()); + evaluateProperty(doneEvaluating) } } - return; + } else { + evaluateProperty(doneEvaluating) } - try { - RED.util.setMessageProperty(msg,property,RED.util.evaluateNodeProperty(value, valueType, this, msg),true); - } catch (err) { - errors.push(err.toString()); - } - }); - - if (errors.length) { - done(errors.join('; ')); - } else { - send(msg); - done(); } + + evaluateProperty(() => { + if (errors.length) { + done(errors.join('; ')); + } else { + send(msg); + done(); + } + }) }); } From ffff8aeb910010f31ff09bd3b07e2ddcb6045454 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Thu, 26 Jan 2023 02:11:34 +0900 Subject: [PATCH 224/237] Fix disabled menu in project feature --- packages/node_modules/@node-red/editor-client/src/js/red.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/red.js b/packages/node_modules/@node-red/editor-client/src/js/red.js index 7544434b7..497a93c45 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/red.js +++ b/packages/node_modules/@node-red/editor-client/src/js/red.js @@ -321,6 +321,8 @@ var RED = (function() { loader.end() RED.notify($("

    ").text(message)); RED.sidebar.info.refresh() + RED.menu.setDisabled('menu-item-projects-open',false); + RED.menu.setDisabled('menu-item-projects-settings',false); }); }); return; From fd42becbdcb6546351474c4966b189d3ced68c3c Mon Sep 17 00:00:00 2001 From: weibin Date: Mon, 30 Jan 2023 23:02:42 +0800 Subject: [PATCH 225/237] fix .red-ui-notification if flows stopped due to missing too much node types manage-project-dep button display none. --- .../@node-red/editor-client/src/sass/notifications.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/notifications.scss b/packages/node_modules/@node-red/editor-client/src/sass/notifications.scss index efae432b2..7d7544e2e 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/notifications.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/notifications.scss @@ -32,7 +32,8 @@ color: var(--red-ui-primary-text-color); border: 1px solid var(--red-ui-notification-border-default); border-left-width: 16px; - overflow: hidden; + overflow: scroll; + max-height: 80vh; .ui-dialog-buttonset { margin-top: 20px; margin-bottom: 10px; From 5bda221f9dcae1e715852fe6217086a226b75790 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Tue, 31 Jan 2023 00:36:18 +0900 Subject: [PATCH 226/237] Use main branch as default in project feature --- .../lib/storage/localfilesystem/projects/git/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js index 8aabcebe2..77b9ad2cd 100644 --- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js +++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js @@ -421,7 +421,10 @@ module.exports = { }); }, initRepo: function(cwd) { - return runGitCommand(["init"],cwd); + var args = ["init", "--initial-branch", "main"]; + return runGitCommand(args, cwd).catch(function () { + return runGitCommand(["init"], cwd); + }); }, setUpstream: function(cwd,remoteBranch) { var args = ["branch","--set-upstream-to",remoteBranch]; From 0f7a1a42e43a6fac1833e65c7de049978e9e2564 Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Wed, 1 Feb 2023 17:50:05 -0500 Subject: [PATCH 227/237] Fix border radius on Modules list header --- .../node_modules/@node-red/nodes/core/function/10-function.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/node_modules/@node-red/nodes/core/function/10-function.html b/packages/node_modules/@node-red/nodes/core/function/10-function.html index 43f8c7b39..e17f58aca 100644 --- a/packages/node_modules/@node-red/nodes/core/function/10-function.html +++ b/packages/node_modules/@node-red/nodes/core/function/10-function.html @@ -17,6 +17,8 @@ display: flex; background: var(--red-ui-tertiary-background); padding-right: 75px; + border-top-left-radius: 3px; + border-top-right-radius: 3px; } #node-input-libs-container-row .red-ui-editableList-header > div { flex-grow: 1; From f3d7016ab248c7a9f70a078f1e3cef87753c5bd9 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Fri, 3 Feb 2023 17:25:58 +0900 Subject: [PATCH 228/237] fix group position after undo --- .../@node-red/editor-client/src/js/ui/view.js | 85 +++++++++++++++++-- 1 file changed, 80 insertions(+), 5 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 03e4d3cd9..4131f5dbf 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -586,11 +586,28 @@ RED.view = (function() { var group = $(ui.helper).data("group"); if (group) { + var oldX = group.x; + var oldY = group.y; RED.group.addToGroup(group, nn); + var moveEvent = null; + if ((group.x !== oldX) || + (group.y !== oldY)) { + moveEvent = { + t: "move", + nodes: [{n: group, + ox: oldX, oy: oldY, + dx: group.x -oldX, + dy: group.y -oldY}], + dirty: true + }; + } historyEvent = { t: 'multi', events: [historyEvent], + }; + if (moveEvent) { + historyEvent.events.push(moveEvent) } historyEvent.events.push({ t: "addToGroup", @@ -1350,19 +1367,35 @@ RED.view = (function() { RED.editor.validateNode(nn); if (targetGroup) { + var oldX = targetGroup.x; + var oldY = targetGroup.y; RED.group.addToGroup(targetGroup, nn); + var moveEvent = null; + if ((targetGroup.x !== oldX) || + (targetGroup.y !== oldY)) { + moveEvent = { + t: "move", + nodes: [{n: targetGroup, + ox: oldX, oy: oldY, + dx: targetGroup.x -oldX, + dy: targetGroup.y -oldY}], + dirty: true + }; + } if (historyEvent.t !== "multi") { historyEvent = { t:'multi', events: [historyEvent] - } + }; } historyEvent.events.push({ t: "addToGroup", group: targetGroup, nodes: nn - }) - + }); + if (moveEvent) { + historyEvent.events.push(moveEvent); + } } if (spliceLink) { @@ -1698,6 +1731,7 @@ RED.view = (function() { // Check link splice or group-add if (movingSet.length() === 1 && movingSet.get(0).n.type !== "group") { + //}{//NIS node = movingSet.get(0); if (spliceActive) { if (!spliceTimer) { @@ -2057,11 +2091,25 @@ RED.view = (function() { if (mouse_mode == RED.state.MOVING_ACTIVE) { if (movingSet.length() > 0) { var addedToGroup = null; + var moveEvent = null; if (activeHoverGroup) { + var oldX = activeHoverGroup.x; + var oldY = activeHoverGroup.y; for (var j=0;j Date: Fri, 3 Feb 2023 22:23:09 +0900 Subject: [PATCH 229/237] Fix "EADDRINUSE" error --- packages/node_modules/node-red/red.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/node-red/red.js b/packages/node_modules/node-red/red.js index 4763fd5a1..2f5ab354c 100755 --- a/packages/node_modules/node-red/red.js +++ b/packages/node_modules/node-red/red.js @@ -458,7 +458,7 @@ httpsPromise.then(function(startupHttps) { RED.start().then(function() { if (settings.httpAdminRoot !== false || settings.httpNodeRoot !== false || settings.httpStatic) { server.on('error', function(err) { - if (err.errno === "EADDRINUSE") { + if (err.code === "EADDRINUSE") { RED.log.error(RED.log._("server.unable-to-listen", {listenpath:getListenPath()})); RED.log.error(RED.log._("server.port-in-use")); } else { From 16f8b78b3996445c39a78ee4c18fe43dd96f1141 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sat, 4 Feb 2023 11:16:28 +0900 Subject: [PATCH 230/237] Show scrollbar in notification dialog only when needed --- .../@node-red/editor-client/src/sass/notifications.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/notifications.scss b/packages/node_modules/@node-red/editor-client/src/sass/notifications.scss index 7d7544e2e..c0e87b7ba 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/notifications.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/notifications.scss @@ -32,7 +32,7 @@ color: var(--red-ui-primary-text-color); border: 1px solid var(--red-ui-notification-border-default); border-left-width: 16px; - overflow: scroll; + overflow: auto; max-height: 80vh; .ui-dialog-buttonset { margin-top: 20px; From ede3ac4282578334256333e487e6817598e8807d Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Sat, 4 Feb 2023 20:55:34 +0900 Subject: [PATCH 231/237] Add tooltip for show/hide button on info sidebar --- .../@node-red/editor-client/locales/en-US/editor.json | 2 ++ .../@node-red/editor-client/locales/ja/editor.json | 2 ++ .../@node-red/editor-client/src/js/ui/tab-info-outliner.js | 4 ++++ 3 files changed, 8 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index 0300a220f..e6beed2e1 100644 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -683,6 +683,8 @@ "empty": "empty", "globalConfig": "Global Configuration Nodes", "triggerAction": "Trigger action", + "showFlow": "Show", + "hideFlow": "Hide", "find": "Find in workspace" }, "help": { diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index b07b8e583..b5939cec0 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -683,6 +683,8 @@ "empty": "空", "globalConfig": "グローバル設定ノード", "triggerAction": "アクションを実行", + "showFlow": "表示", + "hideFlow": "非表示", "find": "ワークスペース内を検索" }, "help": { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info-outliner.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info-outliner.js index 32491f297..2dc18579f 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info-outliner.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info-outliner.js @@ -135,6 +135,10 @@ RED.sidebar.info.outliner = (function() { RED.workspaces.show(n.id, null, true); } }); + RED.popover.tooltip(toggleVisibleButton, function () { + var isHidden = !div.hasClass("red-ui-info-outline-item-hidden"); + return RED._("sidebar.info." + (isHidden ? "hideFlow" : "showFlow")); + }); } if (n.type !== 'subflow') { var toggleButton = $('').appendTo(controls).on("click",function(evt) { From 4fda59a5858b1a40aef5474a1d3f3b17d02aa830 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Fri, 10 Feb 2023 16:49:29 +0900 Subject: [PATCH 232/237] Add validator for complete node --- .../@node-red/nodes/core/common/24-complete.html | 11 ++++++++++- .../@node-red/nodes/locales/en-US/messages.json | 5 ++++- .../@node-red/nodes/locales/ja/messages.json | 5 ++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/common/24-complete.html b/packages/node_modules/@node-red/nodes/core/common/24-complete.html index b2d406bb5..2f68a0fcf 100644 --- a/packages/node_modules/@node-red/nodes/core/common/24-complete.html +++ b/packages/node_modules/@node-red/nodes/core/common/24-complete.html @@ -18,7 +18,16 @@ color:"#c0edc0", defaults: { name: {value:""}, - scope: {value:[], type:"*[]"}, + scope: { + value: [], + type: "*[]", + validate: function (v, opt) { + if (v.length > 0) { + return true; + } + return RED._("node-red:complete.errors.scopeUndefined"); + } + }, uncaught: {value:false} }, inputs:0, diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index 7fcd5eadc..a683b1552 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -119,7 +119,10 @@ } }, "complete": { - "completeNodes": "complete: __number__" + "completeNodes": "complete: __number__", + "errors": { + "scopeUndefined": "scope undefined" + } }, "debug": { "output": "Output", diff --git a/packages/node_modules/@node-red/nodes/locales/ja/messages.json b/packages/node_modules/@node-red/nodes/locales/ja/messages.json index b7fa20b02..cc93e92b1 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ja/messages.json @@ -119,7 +119,10 @@ } }, "complete": { - "completeNodes": "complete: __number__" + "completeNodes": "complete: __number__", + "errors": { + "scopeUndefined": "スコープが未定義" + } }, "debug": { "output": "対象", From 81331e68d2e4a64c24b22feb59e97e1ad8a2281f Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Fri, 10 Feb 2023 18:55:10 +0900 Subject: [PATCH 233/237] Add validator for link call node --- .../@node-red/nodes/core/common/60-link.html | 13 +++++++++++-- .../@node-red/nodes/core/common/60-link.js | 4 ++-- .../@node-red/nodes/locales/en-US/messages.json | 5 +++-- .../@node-red/nodes/locales/ja/messages.json | 5 +++-- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/common/60-link.html b/packages/node_modules/@node-red/nodes/core/common/60-link.html index 4b8c9a3d6..5c16929c3 100644 --- a/packages/node_modules/@node-red/nodes/core/common/60-link.html +++ b/packages/node_modules/@node-red/nodes/core/common/60-link.html @@ -1,4 +1,3 @@ -