@gregoriusrippenstein/node-red-contrib-introspection 0.9.20 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -114,5 +114,8 @@
114
114
  </script>
115
115
 
116
116
  <script type="text/html" data-help-name="GetFlows">
117
- <p>Retrieves the current flow file from the server. This uses the Node-RED API and is therefore storage-method independent. Return value is a JSON.</p>
117
+ <p>Retrieves the current flow file from this server. This uses the Node-RED API and is therefore storage-method independent. Payload becomes a Json string.</p>
118
+
119
+ If the response is to sent to another host, then insert a JSON node to convert the response of this node to a Javascript array which can be passed as payload
120
+ to the SendFlow node.
118
121
  </script>
@@ -7,9 +7,15 @@
7
7
  name: {
8
8
  value:"",
9
9
  },
10
+
10
11
  hostUrl: {
11
- value: ""
12
+ value:"hostUrl",
13
+ required:true
14
+ },
15
+ hostUrlType: {
16
+ value:"str"
12
17
  },
18
+
13
19
  flowVersion: {
14
20
  value: "v1"
15
21
  },
@@ -29,6 +35,7 @@
29
35
  value: "env",
30
36
  },
31
37
  },
38
+
32
39
  inputs:1,
33
40
  outputs:1,
34
41
 
@@ -39,6 +46,7 @@
39
46
  labelStyle: function() {
40
47
  return this.name?"node_label_italic":"";
41
48
  },
49
+
42
50
  oneditprepare: function() {
43
51
  $("#node-input-apiUsername").typedInput({
44
52
  types:["env", "msg", "flow","global", "cred"],
@@ -46,10 +54,15 @@
46
54
  });
47
55
 
48
56
  $("#node-input-apiPassword").typedInput({
49
- types:["env", "msg", "flow","global", "env", "cred"],
57
+ types:["env", "msg", "flow","global", "cred"],
50
58
  typeField: "#node-input-apiPasswordType"
51
59
  });
52
60
 
61
+ $("#node-input-hostUrl").typedInput({
62
+ types:["str", "msg", "flow", "global", "env"],
63
+ typeField: "#node-input-hostUrlType"
64
+ });
65
+
53
66
  if ( $('#node-input-useAuthentication').is(":checked") ) {
54
67
  $('#useAuthentication-input-fields').show();
55
68
  } else {
@@ -75,7 +88,8 @@
75
88
 
76
89
  <div class="form-row">
77
90
  <label for="node-input-hostUrl"><i class="fa fa-tag"></i> Host</label>
78
- <input type="text" id="node-input-hostUrl" placeholder="Host URL">
91
+ <input type="text" id="node-input-hostUrl" placeholder="hostUrl">
92
+ <input type="hidden" id="node-input-hostUrlType" value="str">
79
93
  </div>
80
94
 
81
95
  <div class="form-row">
@@ -123,4 +137,6 @@
123
137
 
124
138
  <script type="text/html" data-help-name="SendFlow">
125
139
  <p>Send flow to another Node-RED instance.</p>
140
+
141
+ Payload is assumed to be a Javascript array containing flow data.
126
142
  </script>
@@ -11,29 +11,37 @@ module.exports = function(RED) {
11
11
 
12
12
  node.on("input", function(msg, send, done) {
13
13
  var sendFlow = (hdrs, got) => {
14
- got.post( (cfg.hostUrl || msg.hostUrl) + "/flows", {
15
- headers: {
16
- "Node-RED-API-Version": cfg.flowVersion,
17
- "Content-type": "application/json",
18
- "Node-RED-Deployment-Type": "full",
19
- ...hdrs
20
- },
21
- body: JSON.stringify(msg.payload)
22
- }).then( res => {
23
- send({
24
- ...msg,
25
- payload: res.body
26
- });
27
-
28
- node.status({fill:"green",shape:"dot",text:"Good"});
29
- setTimeout( function() {
30
- node.status({})
31
- }, 450);
32
-
33
- }).catch( err => {
34
- node.status({fill:"red",shape:"dot",text:"Failed"});
35
- node.error("error occurred", { ...msg, _err: err})
36
- });
14
+
15
+ RED.util.evaluateNodeProperty(cfg.hostUrl, cfg.hostUrlType, node, msg, (err, result) => {
16
+ if (err) {
17
+ node.status({ fill: "red", shape: "dot", text: "Failed" });
18
+ node.error("unable to obtain host url", { ...msg, _err: err })
19
+ } else {
20
+ got.post( result + "/flows", {
21
+ headers: {
22
+ "Node-RED-API-Version": cfg.flowVersion,
23
+ "Content-type": "application/json",
24
+ "Node-RED-Deployment-Type": "full",
25
+ ...hdrs
26
+ },
27
+ body: JSON.stringify(msg.payload)
28
+ }).then( res => {
29
+ send({
30
+ ...msg,
31
+ payload: res.body
32
+ });
33
+
34
+ node.status({fill:"green",shape:"dot",text:"Good"});
35
+ setTimeout( function() {
36
+ node.status({})
37
+ }, 450);
38
+
39
+ }).catch( err => {
40
+ node.status({fill:"red",shape:"dot",text:"Failed"});
41
+ node.error("posting data to host", { ...msg, _err: err})
42
+ });
43
+ }
44
+ })
37
45
  };
38
46
 
39
47
  /**
@@ -45,19 +53,17 @@ module.exports = function(RED) {
45
53
 
46
54
  node.status({fill:"blue",shape:"dot",text:"Requesting token"});
47
55
 
48
- RED.util.evaluateNodeProperty(cfg.apiUsername, cfg.apiUsernameType,
49
- node, msg, (err, result) => {
56
+ RED.util.evaluateNodeProperty(cfg.apiUsername, cfg.apiUsernameType, node, msg, (err, result) => {
50
57
  if (err) {
51
58
  node.status({fill:"red",shape:"dot",text:"Failed"});
52
- node.error("error occurred", { ...msg, _err: err})
59
+ node.error("unable to obtain api username", { ...msg, _err: err})
53
60
  } else {
54
61
  username = result;
55
62
 
56
- RED.util.evaluateNodeProperty(cfg.apiPassword, cfg.apiPasswordType,
57
- node, msg, (err, result) => {
63
+ RED.util.evaluateNodeProperty(cfg.apiPassword, cfg.apiPasswordType, node, msg, (err, result) => {
58
64
  if (err) {
59
65
  node.status({fill:"red",shape:"dot",text:"Failed"});
60
- node.error("error occurred", {...msg, _err: err})
66
+ node.error("unable to obtain api password", {...msg, _err: err})
61
67
  } else {
62
68
  password = result;
63
69
 
@@ -69,31 +75,39 @@ module.exports = function(RED) {
69
75
  "password": password
70
76
  }
71
77
 
72
- import('got').then( (module) => {
73
- module.got.post( (cfg.hostUrl || msg.hostUrl) + "/auth/token", {
74
- json: data
75
- }).then( res => {
76
- node.status({
77
- fill:"blue",
78
- shape:"dot",
79
- text:"Sending flow"
80
- });
78
+ RED.util.evaluateNodeProperty(cfg.hostUrl, cfg.hostUrlType, node, msg, (err, result) => {
79
+ if (err) {
80
+ node.status({ fill: "red", shape: "dot", text: "Failed" });
81
+ node.error("error occurred", { ...msg, _err: err })
82
+ } else {
83
+
84
+ import('got').then( (module) => {
85
+ module.got.post( result + "/auth/token", {
86
+ json: data
87
+ }).then( res => {
88
+ node.status({
89
+ fill:"blue",
90
+ shape:"dot",
91
+ text:"Sending flow"
92
+ });
81
93
 
82
- var access_token = JSON.parse(res.body).access_token;
94
+ var access_token = JSON.parse(res.body).access_token;
83
95
 
84
- sendFlow({
85
- "Authorization": "Bearer " + access_token
86
- }, module.got);
96
+ sendFlow({
97
+ "Authorization": "Bearer " + access_token
98
+ }, module.got);
87
99
 
88
- }).catch((err) => {
89
- node.status({fill:"red",shape:"dot",text:"Failed"});
90
- node.error( "error occurred", { ...msg, _err: err });
91
- });
92
- });
100
+ }).catch((err) => {
101
+ node.status({fill:"red",shape:"dot",text:"Failed"});
102
+ node.error( "unable to obtain api auth token", { ...msg, _err: err });
103
+ });
104
+ });
105
+ }
106
+ })
93
107
  }
94
108
  })
95
109
  }
96
- })
110
+ })
97
111
  } else {
98
112
  /*
99
113
  * Authentication free zone...
@@ -8,9 +8,15 @@
8
8
  name: {
9
9
  value:"",
10
10
  },
11
+
11
12
  hostUrl: {
12
- value: ""
13
+ value:"hostUrl",
14
+ required:true
15
+ },
16
+ hostUrlType: {
17
+ value:"str"
13
18
  },
19
+
14
20
  useAuthentication: {
15
21
  value:false
16
22
  },
@@ -30,6 +36,7 @@
30
36
  value: false
31
37
  }
32
38
  },
39
+
33
40
  inputs:1,
34
41
  outputs:1,
35
42
 
@@ -40,6 +47,7 @@
40
47
  labelStyle: function() {
41
48
  return this.name?"node_label_italic":"";
42
49
  },
50
+
43
51
  oneditprepare: function() {
44
52
  $("#node-input-apiUsername").typedInput({
45
53
  types:["env", "msg", "flow","global", "cred"],
@@ -51,6 +59,11 @@
51
59
  typeField: "#node-input-apiPasswordType"
52
60
  });
53
61
 
62
+ $("#node-input-hostUrl").typedInput({
63
+ types:["str", "msg", "flow", "global", "env"],
64
+ typeField: "#node-input-hostUrlType"
65
+ });
66
+
54
67
  if ( $('#node-input-useAuthentication').is(":checked") ) {
55
68
  $('#useAuthentication-input-fields').show();
56
69
  } else {
@@ -97,8 +110,9 @@
97
110
  -->
98
111
 
99
112
  <div class="form-row hostUrl-row">
100
- <label for="node-input-hostUrl"><i class="fa fa-tag"></i> Host</label>
101
- <input type="text" id="node-input-hostUrl" placeholder="Host URL">
113
+ <label for="node-input-hostUrl"><i class="fa fa-tag"></i> Host</label>
114
+ <input type="text" id="node-input-hostUrl" placeholder="hostUrl">
115
+ <input type="hidden" id="node-input-hostUrlType" value="str">
102
116
  </div>
103
117
 
104
118
  <div class="form-row">
@@ -134,4 +148,22 @@
134
148
 
135
149
  <script type="text/html" data-help-name="InstallPackage">
136
150
  <p>Install package on another Node-RED instance.</p>
151
+
152
+ <p>This takes two forms, it can either install a package by name and version (defaults to "latest") or it can
153
+ accept a buffer object containing a .tgz package file.</p>
154
+
155
+ <p></p>For the first case:
156
+ <pre>
157
+ msg.payload = {
158
+ module: "@gregoriusrippenstein/node-red-contrib-introspection",
159
+ version: "0.10.0"
160
+ }
161
+ </pre>
162
+
163
+ <p>To install a .tgz file, read it into a Buffer object and set the payload to:</p>
164
+ <pre>
165
+ msg.payload = {
166
+ data: new Buffer("ddd")
167
+ }
168
+ </pre>
137
169
  </script>
@@ -15,10 +15,17 @@ module.exports = function(RED) {
15
15
  return node.error("msg.payload missing or payload not hash", msg)
16
16
  }
17
17
 
18
+ /*
19
+ * This defines the helper function - installPackage - that is called either with
20
+ * an authentication token or without. That is determined below.
21
+ */
18
22
  var installPackage = (hdrs, got) => {
19
23
  let body = undefined
20
24
  let headers = {}
21
25
 
26
+ /*
27
+ * Check the Payload - either module name or .tgz buffer.
28
+ */
22
29
  if ( msg.payload.module ) {
23
30
  body = JSON.stringify({
24
31
  module: msg.payload.module,
@@ -40,27 +47,35 @@ module.exports = function(RED) {
40
47
  return node.error("msg.payload not well defined", msg)
41
48
  }
42
49
 
43
- got.post( (cfg.hostUrl || msg.hostUrl) + "/nodes", {
44
- headers: {
45
- ...headers,
46
- ...hdrs
47
- },
48
- body: body
49
- }).then( res => {
50
- send({
51
- ...msg,
52
- payload: JSON.parse(res.body)
53
- });
54
-
55
- node.status({fill:"green",shape:"dot",text:"Good"});
56
- setTimeout( function() {
57
- node.status({})
58
- }, 450);
59
-
60
- }).catch( err => {
61
- node.status({fill:"red",shape:"dot",text:"Failed"});
62
- node.error("error occurred", { ...msg, _err: err })
63
- });
50
+ /*
51
+ * Connect and send to host.
52
+ */
53
+ RED.util.evaluateNodeProperty(cfg.hostUrl, cfg.hostUrlType, node, msg, (err, result) => {
54
+ if (err) {
55
+ node.status({ fill: "red", shape: "dot", text: "Failed" });
56
+ node.error("unable to obtain host url", { ...msg, _err: err })
57
+ } else {
58
+ got.post( result + "/nodes", {
59
+ headers: {
60
+ ...headers,
61
+ ...hdrs
62
+ },
63
+ body: body
64
+ }).then( res => {
65
+ send({
66
+ ...msg,
67
+ payload: JSON.parse(res.body)
68
+ });
69
+
70
+ node.status({fill:"green",shape:"dot",text:"Good"});
71
+ setTimeout( function() { node.status({}) }, 450);
72
+
73
+ }).catch( err => {
74
+ node.status({fill:"red",shape:"dot",text:"Failed"});
75
+ node.error("unable to connect to host", { ...msg, _err: err })
76
+ });
77
+ }
78
+ })
64
79
  };
65
80
 
66
81
  /**
@@ -76,7 +91,7 @@ module.exports = function(RED) {
76
91
  node, msg, (err, result) => {
77
92
  if (err) {
78
93
  node.status({fill:"red",shape:"dot",text:"Failed"});
79
- node.error("error occurred", { ...msg, _err: err})
94
+ node.error("unable to obtain api username", { ...msg, _err: err})
80
95
  } else {
81
96
  username = result;
82
97
 
@@ -84,7 +99,7 @@ module.exports = function(RED) {
84
99
  node, msg, (err, result) => {
85
100
  if (err) {
86
101
  node.status({fill:"red",shape:"dot",text:"Failed"});
87
- node.error("error occurred", { ...msg, _err: err} )
102
+ node.error("unable to obtain api password", { ...msg, _err: err} )
88
103
  } else {
89
104
  password = result;
90
105
 
@@ -96,27 +111,34 @@ module.exports = function(RED) {
96
111
  "password": password
97
112
  }
98
113
 
99
- import('got').then( (module) => {
100
- module.got.post( (cfg.hostUrl || msg.hostUrl) + "/auth/token", {
101
- json: data
102
- }).then( res => {
103
- node.status({
104
- fill:"blue",
105
- shape:"dot",
106
- text:"Sending flow"
114
+ RED.util.evaluateNodeProperty(cfg.hostUrl, cfg.hostUrlType, node, msg, (err, result) => {
115
+ if (err) {
116
+ node.status({ fill: "red", shape: "dot", text: "Failed" });
117
+ node.error("unable to obtain api host url", { ...msg, _err: err })
118
+ } else {
119
+ import('got').then( (module) => {
120
+ module.got.post( result + "/auth/token", {
121
+ json: data
122
+ }).then( res => {
123
+ node.status({
124
+ fill:"blue",
125
+ shape:"dot",
126
+ text:"Installing packages"
127
+ });
128
+
129
+ var access_token = JSON.parse(res.body).access_token;
130
+
131
+ installPackage({
132
+ "Authorization": "Bearer " + access_token
133
+ }, module.got);
134
+
135
+ }).catch((err) => {
136
+ node.status({fill:"red",shape:"dot",text:"Failed"});
137
+ node.error( "error occured", { ...msg, _err: err } );
138
+ });
107
139
  });
108
-
109
- var access_token = JSON.parse(res.body).access_token;
110
-
111
- installPackage({
112
- "Authorization": "Bearer " + access_token
113
- }, module.got);
114
-
115
- }).catch((err) => {
116
- node.status({fill:"red",shape:"dot",text:"Failed"});
117
- node.error( "error occured", { ...msg, _err: err } );
118
- });
119
- });
140
+ }
141
+ })
120
142
  }
121
143
  })
122
144
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gregoriusrippenstein/node-red-contrib-introspection",
3
- "version": "0.9.20",
3
+ "version": "0.10.1",
4
4
  "dependencies": {
5
5
  "got": "^13",
6
6
  "uglify-js": "^3.17.4",
@@ -6,7 +6,7 @@
6
6
 
7
7
  function setupTreelist(){var e=collectOrphans();if(0==e.length){RED.notify("No Orphans Found",{type:"success",timeout:2e3});try{$("#node-input-orphan-target-container-div").treeList("empty")}catch(e){}}else{try{$("#node-input-orphan-target-container-div").treeList("empty")}catch(e){$("#node-input-orphan-target-container-div").css({width:"100%",height:"calc(100%)"}).treeList({multi:!1}).on("treelistitemmouseover",function(e,t){}).on("treelistitemmouseout",function(e,t){}).on("treelistselect",function(e,t){t.node&&(RED.workspaces.show(t.node.z,!1,!1,!0),RED.view.reveal(t.node.id,!0),RED.view.redraw())}).on("treelistconfirm",function(e,t){var n;t.node&&(n=t.node.id,setTimeout(()=>{var e=RED.nodes.node(n);e&&(RED.view.reveal(e.id),RED.view.select(e.id),RED.editor.edit(e)),n==RED.workspaces.active()&&RED.workspaces.edit()},50))}),$("#node-input-orphan-target-filter").show();var n=$("#node-input-orphan-target-filter").searchBox({style:"compact",delay:300,change:function(){var e,t=$(this).val().trim().toLowerCase();""===t?($("#node-input-orphan-target-container-div").treeList("filter",null),n.searchBox("count","")):(e=$("#node-input-orphan-target-container-div").treeList("filter",function(e){return-1<e.label.toLowerCase().indexOf(t)||-1<e.node.type.toLowerCase().indexOf(t)}),n.searchBox("count",e+" / "+$("#node-input-orphan-target-container-div").treeList("data").length))}})}$("#node-input-orphan-target-container-div").treeList("data",e.sort((e,t)=>e.node.z>t.node.z))}}function collectOrphans(){let t=new Set;var n=[],i=(RED.nodes.eachLink(e=>{t.add(e.source),t.add(e.target)}),RED.nodes.eachNode(e=>{(!t.has(e)||"link out"==e.type&&"link"==e.mode&&0==e.links.reduce((e,t)=>e||!!RED.nodes.node(t),!1))&&n.push(e)}),[]),r={};return n.forEach(function(e){var t=RED.nodes.getType(e.type);if(t){var n=t.label,n=("function"==typeof n?n.call(e):n)||"",o=e.type;if(0===o.indexOf("subflow:"))return}t&&n||(n=e.type),r[e.id]={node:e,label:n,sublabel:o,selected:!1,checkbox:!1},i.push(r[e.id])}),i}
8
8
 
9
- function nr_intro_generate_svg_4_0(r,t){return e=>{try{handleSvgObject($($("#red-ui-workspace-chart").find("svg")[0]),e,t)}catch(t){var n="Error Generating SVG: "+JSON.stringify(t);r.notify(n,{type:"error"}),e('<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg width="1000" height="1000" viewBox="0 0 1000 1000" pointer-events="all" style="cursor: crosshair; touch-action: none;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><style>.small { font: bold 20px sans-serif; fill: red;}</style><text x="10" y="30" class="small">'+n+"</text></svg>")}}}function nr_intro_generate_svg_3_1(r,t){return e=>{try{handleSvgObject($($("#red-ui-workspace-chart").find("svg")[0]),e,t)}catch(t){var n="Error Generating SVG: "+JSON.stringify(t);r.notify(n,{type:"error"}),e('<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg width="1000" height="1000" viewBox="0 0 1000 1000" pointer-events="all" style="cursor: crosshair; touch-action: none;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><style>.small { font: bold 20px sans-serif; fill: red;}</style><text x="10" y="30" class="small">'+n+"</text></svg>")}}}function nr_intro_generate_svg_3_0(r,t){return e=>{try{handleSvgObject($($("svg")[0]),e,t)}catch(t){var n="Error Generating SVG: "+JSON.stringify(t);r.notify(n,{type:"error"}),e('<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg width="1000" height="1000" viewBox="0 0 1000 1000" pointer-events="all" style="cursor: crosshair; touch-action: none;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><style>.small { font: bold 20px sans-serif; fill: red;}</style><text x="10" y="30" class="small">'+n+"</text></svg>")}}}function handleSvgObject(a,t,e){var n=a.clone(),r=(n.find("foreignObject").remove(),n.find("svg.__screenshot").remove(),'width="'+a.attr("width")+'" height="'+a.attr("height")+'"'),s=RED.settings.version.split("."),i=parseInt(s[0]),s=parseInt(s[1]);if(3<=i&&1<=s||4<=i){var o=$($($(a).children("g")[0]).children("g")[0]).children("g"),l={x:8e3,y:8e3,w:-1,h:-1};for(let t=1;t<o.length;t++){var g=o[t].getBBox();0==g.width&&0==g.height||(l.x=Math.min(g.x,l.x),l.y=Math.min(g.y,l.y),l.w=Math.max(g.width,l.w),l.h=Math.max(g.height,l.h))}r+=` viewBox='${l.x} ${l.y} ${l.w} ${l.h}'`}function c(t,e){for(var n=0;n<t.length;n++){var s=t.item(n),i=e[n];["stroke-width","fill-opacity","stroke-opacity","opacity","stroke-dasharray"].forEach(function(t){s.setAttribute(t,$(i).attr(t)||$(i).css(t))}),["fill","stroke"].forEach(function(t){var e,n,r=(e=$(i).attr(t)||$(i).css(t))&&null!==e&&"none"!=e?(n=e.match(/^#(.)(.)(.)$/))?"#"+n[1]+n[1]+n[2]+n[2]+n[3]+n[3]:(n=e.match(/^#......$/))?e:null===(n=e.match(/^rgb\(([0-9]+),\s+([0-9]+),\s+([0-9]+)/))?(r=e.match(/^rgba\(([0-9]+),\s+([0-9]+),\s+([0-9]+),\s+([0-9]+)/))?{clr:"#"+("0"+parseInt(r[1],10).toString(16)).slice(-2)+("0"+parseInt(r[2],10).toString(16)).slice(-2)+("0"+parseInt(r[3],10).toString(16)).slice(-2),opa:r[4]}:(console.log("Screenshot node: returned unknown color: "+e),e):"#"+("0"+parseInt(n[1],10).toString(16)).slice(-2)+("0"+parseInt(n[2],10).toString(16)).slice(-2)+("0"+parseInt(n[3],10).toString(16)).slice(-2):"none";"object"==typeof r?(s.setAttribute(t+"-opacity",r.opa),s.setAttribute(t,r.clr)):s.setAttribute(t,r)}),$(i).hasClass("hide")&&("g"==s.tagName&&s.setAttribute("opacity","0"),s.setAttribute("visibility","hidden"))}}var s='<?xml version="1.0" standalone="no"?>\r\n<svg '+r+' pointer-events="all" style="cursor: crosshair; touch-action: none;" xmlns="http://www.w3.org/2000/svg" class="__screenshot" xmlns:xlink="http://www.w3.org/1999/xlink">\r\n',i=n.html(),h=(new DOMParser).parseFromString(s+i+"\r\n</svg>","image/svg+xml"),v=t=>t,f=(e.rmidsandclasses&&(v=e=>(["g","rect","line","path","circle","image","text"].forEach(t=>{$(e.getElementsByTagName(t)).each((t,e)=>{e.setAttribute("class",""),e.setAttribute("id","")})}),e)),["g","rect","line","path","circle","image"].forEach(function(t){c(h.getElementsByTagName(t),a.find(t))}),["text"].forEach(function(t){c(h.getElementsByTagName(t),a.find(t));for(var e=h.getElementsByTagName(t),n=a.find(t),r=0;r<e.length;r++){var s=e.item(r),i=n[r];["font-family","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-weight","text-anchor","dominant-baseline"].forEach(function(t){s.setAttribute(t,$(i).attr(t)||$(i).css(t))})}}),h.getElementsByTagName("image")),w={},m=(n,r,s)=>{var i=n.getAttribute("xlink:href")||n.getAttribute("href"),a=i.substr(-4,4).toLowerCase(),o={".jpg":"jpeg",jpeg:"jpeg",".png":"png",".svg":"svg+xml"};if(w[i])return n.setAttribute("xlink:href","data:image/"+o[a]+";base64,"+w[i]),s(r-1);switch(a){case".jpg":case"jpeg":case".png":var l=new XMLHttpRequest;l.open("GET",i,!0),l.responseType="arraybuffer";l.onerror=function(t){s(r-1)},l.onload=function(t){var e=l.response;e&&(e=(t=>{for(var e="",n=new Uint8Array(t),r=n.byteLength,s=0;s<r;s++)e+=String.fromCharCode(n[s]);return window.btoa(e)})(e),w[i]=e,n.setAttribute("xlink:href","data:image/"+o[a]+";base64,"+e)),s(r-1)},l.send(null);break;case".svg":$.get(i,function(t){var e=new XMLSerializer,e=btoa(e.serializeToString(t));w[i]=e,n.setAttribute("xlink:href","data:image/svg+xml;base64,"+e),s(r-1)})}},d=e=>{if(e<0)t((new XMLSerializer).serializeToString(v(h)));else try{m(f.item(e),e,d)}catch(t){d(e-1)}};0<f.length?m(f.item(f.length-1),f.length-1,d):t((new XMLSerializer).serializeToString(v(h)))}function generatorFunctionForVersion(t,e){var n,r=t.settings.version.split("."),s=r[0],r=r[1];if("3"==s){if("0"==r)return nr_intro_generate_svg_3_0(t,e);if("1"==r)return nr_intro_generate_svg_3_1(t,e)}return"4"==s&&"0"==r?nr_intro_generate_svg_4_0(t,e):(n=t,t=>{var e="Node-RED version ("+n.settings.version+") not supported";n.notify(e,{type:"error"}),t&&t('<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg width="1000" height="1000" viewBox="0 0 1000 1000" pointer-events="all" style="cursor: crosshair; touch-action: none;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><style>.small { font: bold 20px sans-serif; fill: red;}</style><text x="10" y="30" class="small">'+e+"</text></svg>")})}function addPanZoom(){var t=d3.select("#node-input-screenshot-svgcontainer svg"),e=(t.html("<g>"+t.html()+"</g>"),setTimeout(()=>{var t=$("#node-input-screenshot-svgcontainer svg"),e=$(t).attr("viewBox").split(" ");t.animate({height:parseInt(e[3]),width:parseInt(e[2])},800,"swing")},100),t.select("g")),n=d3.behavior.zoom().scaleExtent([.1,200]).on("zoom",function(t){e.attr({transform:"translate("+n.translate()+") scale("+n.scale()+")"})});t.call(n)}
9
+ function nr_intro_generate_svg_4_0(r,t){return e=>{try{handleSvgObject($($("#red-ui-workspace-chart").find("svg")[0]),e,t)}catch(t){var n="Error Generating SVG: "+JSON.stringify(t);r.notify(n,{type:"error"}),e('<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg width="1000" height="1000" viewBox="0 0 1000 1000" pointer-events="all" style="cursor: crosshair; touch-action: none;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><style>.small { font: bold 20px sans-serif; fill: red;}</style><text x="10" y="30" class="small">'+n+"</text></svg>")}}}function nr_intro_generate_svg_3_1(r,t){return e=>{try{handleSvgObject($($("#red-ui-workspace-chart").find("svg")[0]),e,t)}catch(t){var n="Error Generating SVG: "+JSON.stringify(t);r.notify(n,{type:"error"}),e('<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg width="1000" height="1000" viewBox="0 0 1000 1000" pointer-events="all" style="cursor: crosshair; touch-action: none;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><style>.small { font: bold 20px sans-serif; fill: red;}</style><text x="10" y="30" class="small">'+n+"</text></svg>")}}}function nr_intro_generate_svg_3_0(r,t){return e=>{try{handleSvgObject($($("svg")[0]),e,t)}catch(t){var n="Error Generating SVG: "+JSON.stringify(t);r.notify(n,{type:"error"}),e('<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg width="1000" height="1000" viewBox="0 0 1000 1000" pointer-events="all" style="cursor: crosshair; touch-action: none;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><style>.small { font: bold 20px sans-serif; fill: red;}</style><text x="10" y="30" class="small">'+n+"</text></svg>")}}}function handleSvgObject(o,t,e){var n=o.clone(),r=(e.removeforeignobjects&&n.find("foreignObject").remove(),n.find("svg.__screenshot").remove(),'width="'+o.attr("width")+'" height="'+o.attr("height")+'"'),s=RED.settings.version.split("."),i=parseInt(s[0]),s=parseInt(s[1]);if(3<=i&&1<=s||4<=i){var a=$($($(o).children("g")[0]).children("g")[0]).children("g"),l={x:8e3,y:8e3,w:-1,h:-1};for(let t=1;t<a.length;t++){var g=a[t].getBBox();0==g.width&&0==g.height||(l.x=Math.min(g.x,l.x),l.y=Math.min(g.y,l.y),l.w=Math.max(g.width,l.w),l.h=Math.max(g.height,l.h))}r+=` viewBox='${l.x} ${l.y} ${l.w} ${l.h}'`}function c(t,e){for(var n=0;n<t.length;n++){var s=t.item(n),i=e[n];["stroke-width","fill-opacity","stroke-opacity","opacity","stroke-dasharray"].forEach(function(t){s.setAttribute(t,$(i).attr(t)||$(i).css(t))}),["fill","stroke"].forEach(function(t){var e,n,r=(e=$(i).attr(t)||$(i).css(t))&&null!==e&&"none"!=e?(n=e.match(/^#(.)(.)(.)$/))?"#"+n[1]+n[1]+n[2]+n[2]+n[3]+n[3]:(n=e.match(/^#......$/))?e:null===(n=e.match(/^rgb\(([0-9]+),\s+([0-9]+),\s+([0-9]+)/))?(r=e.match(/^rgba\(([0-9]+),\s+([0-9]+),\s+([0-9]+),\s+([0-9]+)/))?{clr:"#"+("0"+parseInt(r[1],10).toString(16)).slice(-2)+("0"+parseInt(r[2],10).toString(16)).slice(-2)+("0"+parseInt(r[3],10).toString(16)).slice(-2),opa:r[4]}:(console.log("Screenshot node: returned unknown color: "+e),e):"#"+("0"+parseInt(n[1],10).toString(16)).slice(-2)+("0"+parseInt(n[2],10).toString(16)).slice(-2)+("0"+parseInt(n[3],10).toString(16)).slice(-2):"none";"object"==typeof r?(s.setAttribute(t+"-opacity",r.opa),s.setAttribute(t,r.clr)):s.setAttribute(t,r)}),$(i).hasClass("hide")&&("g"==s.tagName&&s.setAttribute("opacity","0"),s.setAttribute("visibility","hidden"))}}var s='<?xml version="1.0" standalone="no"?>\r\n<svg '+r+' pointer-events="all" style="cursor: crosshair; touch-action: none;" xmlns="http://www.w3.org/2000/svg" class="__screenshot" xmlns:xlink="http://www.w3.org/1999/xlink">\r\n',i=n.html(),h=(new DOMParser).parseFromString(s+i+"\r\n</svg>","image/svg+xml"),v=t=>t,f=(e.rmidsandclasses&&(v=e=>(["g","rect","line","path","circle","image","text"].forEach(t=>{$(e.getElementsByTagName(t)).each((t,e)=>{e.setAttribute("class",""),e.setAttribute("id","")})}),e)),["g","rect","line","path","circle","image"].forEach(function(t){c(h.getElementsByTagName(t),o.find(t))}),["text"].forEach(function(t){c(h.getElementsByTagName(t),o.find(t));for(var e=h.getElementsByTagName(t),n=o.find(t),r=0;r<e.length;r++){var s=e.item(r),i=n[r];["font-family","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-weight","text-anchor","dominant-baseline"].forEach(function(t){s.setAttribute(t,$(i).attr(t)||$(i).css(t))})}}),h.getElementsByTagName("image")),w={},m=(n,r,s)=>{var i=n.getAttribute("xlink:href")||n.getAttribute("href"),o=i.substr(-4,4).toLowerCase(),a={".jpg":"jpeg",jpeg:"jpeg",".png":"png",".svg":"svg+xml"};if(w[i])return n.setAttribute("xlink:href","data:image/"+a[o]+";base64,"+w[i]),s(r-1);switch(o){case".jpg":case"jpeg":case".png":var l=new XMLHttpRequest;l.open("GET",i,!0),l.responseType="arraybuffer";l.onerror=function(t){s(r-1)},l.onload=function(t){var e=l.response;e&&(e=(t=>{for(var e="",n=new Uint8Array(t),r=n.byteLength,s=0;s<r;s++)e+=String.fromCharCode(n[s]);return window.btoa(e)})(e),w[i]=e,n.setAttribute("xlink:href","data:image/"+a[o]+";base64,"+e)),s(r-1)},l.send(null);break;case".svg":$.get(i,function(t){var e=new XMLSerializer,e=btoa(e.serializeToString(t));w[i]=e,n.setAttribute("xlink:href","data:image/svg+xml;base64,"+e),s(r-1)});break;default:console.log("SVG Capture ignoring file prefix: "+o),s(r-1)}},d=e=>{if(e<0)t((new XMLSerializer).serializeToString(v(h)));else try{m(f.item(e),e,d)}catch(t){d(e-1)}};0<f.length?m(f.item(f.length-1),f.length-1,d):t((new XMLSerializer).serializeToString(v(h)))}function generatorFunctionForVersion(t,e){var n,r=t.settings.version.split("."),s=r[0],r=r[1];if("3"==s){if("0"==r)return nr_intro_generate_svg_3_0(t,e);if("1"==r)return nr_intro_generate_svg_3_1(t,e)}return"4"==s&&"0"==r?nr_intro_generate_svg_4_0(t,e):(n=t,t=>{var e="Node-RED version ("+n.settings.version+") not supported";n.notify(e,{type:"error"}),t&&t('<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg width="1000" height="1000" viewBox="0 0 1000 1000" pointer-events="all" style="cursor: crosshair; touch-action: none;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><style>.small { font: bold 20px sans-serif; fill: red;}</style><text x="10" y="30" class="small">'+e+"</text></svg>")})}function addPanZoom(){var t=d3.select("#node-input-screenshot-svgcontainer svg"),e=(t.html("<g>"+t.html()+"</g>"),setTimeout(()=>{var t=$("#node-input-screenshot-svgcontainer svg"),e=$(t).attr("viewBox").split(" ");t.animate({height:parseInt(e[3]),width:parseInt(e[2])},800,"swing")},100),t.select("g")),n=d3.behavior.zoom().scaleExtent([.1,200]).on("zoom",function(t){e.attr({transform:"translate("+n.translate()+") scale("+n.scale()+")"})});t.call(n)}
10
10
 
11
11
  function setupTreelistInfoness(){var e=collectUndocumentedNodes();if(0==e.length){RED.notify("All nodes documented",{type:"success",timeout:2e3});try{$("#node-input-orphan-target-container-div").treeList("empty")}catch(e){}}else{try{$("#node-input-orphan-target-container-div").treeList("empty")}catch(e){$("#node-input-orphan-target-container-div").css({width:"100%",height:"calc(100%)"}).treeList({multi:!1}).on("treelistitemmouseover",function(e,t){}).on("treelistitemmouseout",function(e,t){}).on("treelistselect",function(e,t){t.node&&(RED.workspaces.show(t.node.z,!1,!1,!0),RED.view.reveal(t.node.id,!0),RED.view.redraw())}).on("treelistconfirm",function(e,t){var i;t.node&&(i=t.node.id,setTimeout(()=>{var e=RED.nodes.node(i);e&&(RED.view.reveal(e.id),RED.view.select(e.id),RED.editor.edit(e,"editor-tab-description")),i==RED.workspaces.active()&&RED.workspaces.edit()},50))}),$("#node-input-orphan-target-filter").show();var i=$("#node-input-orphan-target-filter").searchBox({style:"compact",delay:300,change:function(){var e,t=$(this).val().trim().toLowerCase();""===t?($("#node-input-orphan-target-container-div").treeList("filter",null),i.searchBox("count","")):(e=$("#node-input-orphan-target-container-div").treeList("filter",function(e){return-1<e.label.toLowerCase().indexOf(t)||-1<e.node.type.toLowerCase().indexOf(t)}),i.searchBox("count",e+" / "+$("#node-input-orphan-target-container-div").treeList("data").length))}})}$("#node-input-orphan-target-container-div").treeList("data",e.sort((e,t)=>e.node.z>t.node.z))}}function collectUndocumentedNodes(){let t=[];RED.nodes.eachNode(e=>{if($("#"+e.id).find(".red-ui-info-available-indicator").remove(),e.info&&e.info.trim()&&$("#"+e.id)[0]){var i=document.createElementNS("http://www.w3.org/2000/svg","g"),n=(i.setAttribute("class","red-ui-info-available-indicator"),i.setAttribute("transform","translate(20,10)"),i.setAttribute("id","infoclk-"+e.id),document.createElementNS("http://www.w3.org/2000/svg","g")),o=(n.setAttribute("class","tip"),n.setAttribute("fill","lightyellow"),document.createElementNS("http://www.w3.org/2000/svg","rect")),o=(o.setAttribute("width","30"),o.setAttribute("height","20"),o.setAttribute("x",$("#"+e.id)[0].getBBox().width-60),o.setAttribute("y","-15"),o.setAttribute("rx","2"),o.setAttribute("style","cursor: pointer;"),n.append(o),document.createElementNS("http://www.w3.org/2000/svg","text"));o.setAttribute("x",$("#"+e.id)[0].getBBox().width-55),o.setAttribute("y","-6"),o.appendChild(document.createTextNode("docs")),n.append(o),i.append(n),$(i).insertBefore($("#"+e.id).find(".red-ui-flow-node"));let t=e.id;$("#infoclk-"+e.id).on("click",e=>{e&&e.preventDefault();e=RED.nodes.node(t);RED.editor.edit(e,"editor-tab-description"),RED.sidebar.show("info")})}else e.z==RED.workspaces.active()&&["link in","link out","link call"].indexOf(e.type)<0&&t.push(e)});var o=[],r={};return t.forEach(function(e){var t=RED.nodes.getType(e.type);if(t){var i,n=t.label,n=("function"==typeof n?n.call(e):n)||"";if(0===(i=e.type).indexOf("subflow:"))return}t&&n||(n=e.type),r[e.id]={node:e,label:n,sublabel:i,selected:!1,checkbox:!1},o.push(r[e.id])}),o}
12
12
 
@@ -114,6 +114,7 @@
114
114
  $('#node-screenshot-capture-btn').on("click", function (e) {
115
115
  if ( e ) { e.preventDefault() }
116
116
 
117
+ $('#node-screenshot-capture-btn').prop('disabled',true)
117
118
  $('#node-input-screenshot-svgcontainer').html( "" );
118
119
 
119
120
  RED.notify(
@@ -122,15 +123,22 @@
122
123
  }
123
124
  );
124
125
 
126
+ /*
127
+ foreignObjects can be used to embed audio and videos into flow SVGs however
128
+ they can't be exported since they cause an XML parsing error - unfortunately.
129
+ */
125
130
  let opts = {
126
- rmidsandclasses: $('#node-input-screenshot-remove-classes-and-ids').is('checked')
131
+ rmidsandclasses: $('#node-input-screenshot-remove-classes-and-ids').is('checked'),
132
+ removeforeignobjects: true // $('#node-input-screenshot-remove-foreign-objects').is(":checked")
127
133
  }
134
+
128
135
  generatorFunctionForVersion(RED, opts)( (svgdata) => {
129
136
  $('#node-input-screenshot-svgcontainer').html(svgdata);
130
137
  globalRefToSvgData = svgdata;
131
138
  setTimeout(() => {
132
139
  addPanZoom()
133
140
  },150)
141
+ $('#node-screenshot-capture-btn').prop('disabled',false)
134
142
  });
135
143
  })
136
144
 
@@ -372,21 +380,18 @@
372
380
  <div class="form-row" style="margin-left: 10px; margin-top: 30px;">
373
381
  <button type="button" id="node-screenshot-capture-btn"
374
382
  class="red-ui-button red-ui-button-large"><i class="fa fa-camera"></i> Capture</button>
383
+ </div>
375
384
 
376
- <label for="node-input-screenshot-remove-classes-and-ids" class="ml-30" style="width: 200px">
385
+ <div class="form-row" style="margin-left: 10px;">
386
+ <label for="node-input-screenshot-remove-classes-and-ids" style="width: 200px">
377
387
  <span>Remove Classes and Ids?</span>
378
388
  </label>
379
389
  <input type="checkbox" checked="checked" id="node-input-screenshot-remove-classes-and-ids"
380
- style="display:inline-block; width:15px; vertical-align:baseline;">
381
- </div>
382
-
383
- <div class="form-row" style="margin-left: 10px; margin-top: 30px;">
384
- Step 1: Capture a screen shot, Step 2: download it, copy it or post off.
390
+ style="display:inline-block; width:15px; vertical-align:baseline;">
385
391
  </div>
386
-
387
-
392
+
388
393
  <div class="form-row"
389
- style="min-height: 300px; height: 450px; overflow: hidden; margin-left: 10px; margin-right: 15px; border: 1px rgb(196, 196, 196) solid; border-radius: 5px;">
394
+ style="min-height: 300px; height: 65vh; overflow: hidden; margin-left: 10px; margin-right: 15px; border: 1px rgb(196, 196, 196) solid; border-radius: 5px;">
390
395
  <div id="node-input-screenshot-svgcontainer"></div>
391
396
  </div>
392
397