From 68331fc40c4390f9b9f5bec29baecec8ea5ab083 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Wed, 8 Jun 2022 21:56:17 +0100 Subject: [PATCH 01/45] 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 02/45] 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 03/45] 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 04/45] 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 05/45] 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 01d9affe61efbf35fdeb304985dfc5061d24299e Mon Sep 17 00:00:00 2001 From: cow0w Date: Fri, 17 Jun 2022 22:18:14 +0300 Subject: [PATCH 06/45] 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 6c2297c3659ddbd5cc784ba07e97680d4816eaea Mon Sep 17 00:00:00 2001 From: Dennis Neufeld Date: Sat, 18 Jun 2022 19:41:46 +0200 Subject: [PATCH 07/45] 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 08/45] 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 3fbbfce17cd9ef011b3dcd37a9376f08821963a9 Mon Sep 17 00:00:00 2001 From: Dennis Neufeld Date: Mon, 20 Jun 2022 13:28:10 +0200 Subject: [PATCH 09/45] 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 10/45] 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 10835968fb3f714d971ea78bc48be41378bc7f9f Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Tue, 21 Jun 2022 21:47:57 +0900 Subject: [PATCH 11/45] 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^ 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 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 12/45] 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 13/45] 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 14/45] 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 15/45] 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 d2e84925f7738dcceda6465627e4945d20b23ffc Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 22 Jun 2022 21:43:25 +0100 Subject: [PATCH 16/45] 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 17/45] 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 18/45] 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 19/45] 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 20/45] =?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 21/45] 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 22/45] 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 23/45] 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 24/45] 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 25/45] 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 26/45] 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 27/45] 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 28/45] 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 29/45] 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 30/45] 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 31/45] 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 32/45] 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 33/45] 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 34/45] 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 35/45] 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 36/45] 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 37/45] 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 38/45] 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 39/45] 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 40/45] 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 41/45] 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 42/45] 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 43/45] 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 44/45] 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 45/45] 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",