@gregoriusrippenstein/node-red-contrib-nodedev 0.1.6 → 0.1.7

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.
package/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  ## Node-RED nodes for the development of Node-RED nodes in Node-RED using Node-RED nodes.
2
2
 
3
+
4
+ *** Work In Progress, not stable. ***
5
+
3
6
  *What?*
4
7
 
5
8
  The idea behind this collection of nodes to democratise the development of Node-RED nodes. Normally the development of your [own nodes](https://nodered.org/docs/creating-nodes/) will require the use of a third-party editor. Something like VSCode or Vim or Atom or god forbid, [Emacs](https://discourse.nodered.org/t/node-red-node-development-in-node-red/81525/2). This requirement makes creating nodes that much harder. So why not create nodes for Node-RED in Node-RED? After all, if you are using Node-RED, you probably understand how to use Node-RED.
@@ -97,21 +97,34 @@
97
97
 
98
98
  </script>
99
99
 
100
+ <script type="text/html" data-help-name="NodeFactory">
101
+ <p>Generate node templates providing starting points for node development</p>
102
+ The Node-Factory can be used to create example code for various types of Node-RED nodes.
103
+ </script>
104
+
100
105
  <script type="text/javascript">
101
106
  (function () {
102
107
 
103
- RED.comms.subscribe("nodedev:perform-autoimport-nodes", (event,data) => {
104
- if ( data.msg == "autoimport" ) {
105
- setTimeout(() => {
106
- RED.clipboard.import();
107
-
108
+ RED.comms.subscribe("nodedev:perform-autoimport-nodes", (event,data) => {
109
+ if ( data.msg == "notify") {
110
+ RED.notify(data.text, {
111
+ type: data.type,
112
+ id: data.id || "NodeFactory",
113
+ timeout: data.timeout || 2000
114
+ });
115
+ }
116
+
117
+ if ( data.msg == "autoimport" ) {
108
118
  setTimeout(() => {
109
- $('#red-ui-clipboard-dialog-import-text').val(
110
- data.payload
111
- ).trigger("paste");
112
- }, 300);
113
- },400)
114
- }
119
+ RED.clipboard.import();
120
+
121
+ setTimeout(() => {
122
+ $('#red-ui-clipboard-dialog-import-text').val(
123
+ data.payload
124
+ ).trigger("paste");
125
+ }, 300);
126
+ },400)
127
+ }
115
128
  });
116
129
 
117
130
  function doSubmission(node) {
@@ -5,9 +5,6 @@ module.exports = function (RED) {
5
5
  var path = require('path');
6
6
 
7
7
  var mustache = require("mustache");
8
- var tarStream = require('tar-stream');
9
- var streamx = require('streamx');
10
- var pakoGzip = require('pako');
11
8
 
12
9
  var spcRepDict = {
13
10
  "ocb2": "{{",
@@ -430,76 +427,12 @@ module.exports = function (RED) {
430
427
  } else {
431
428
  /* assume that payload is a buffer with a .tgz if not, error out */
432
429
  try {
433
- const extract = tarStream.extract()
434
-
435
- var allFiles = [];
436
-
437
- /*
438
- * there is no indication in a tar file of whether a file is binary or textual.
439
- * we can only make a guess by the extension of the filename.
440
- ***/
441
- var computeFormat = (filename) => {
442
- var ext = filename.split(".").at(-1);
443
-
444
- return {
445
- "html": "html",
446
- "js": "javascript",
447
- "md": "markdown",
448
- "json": "json",
449
- /* binary formats are encoded in base64 */
450
- "png": "base64",
451
- "tiff": "base64",
452
- "tif": "base64",
453
- "jpg": "base64",
454
- "jpeg": "base64",
455
- "bin": "base64",
456
- "bmp": "base64",
457
- "gif": "base64",
458
- }[ext.toLowerCase()] || "text";
459
- };
460
-
461
- extract.on('entry', function (header, stream, next) {
462
- // header is the tar header
463
- // stream is the content body (might be an empty stream)
464
- // call next when you are done with this entry
465
-
466
- var buffer = [];
467
-
468
- stream.on('data', function (data) {
469
- buffer.push(data)
470
- });
471
-
472
- stream.on('end', function () {
473
- var frmt = computeFormat(header.name.split("/").at(-1));
474
-
475
- allFiles.push({
476
- id: RED.util.generateId(),
477
- type: "PkgFile",
478
- name: header.name.split("/").at(-1),
479
- filename: header.name.replace(/^package\//, ''),
480
- template: Buffer.concat(buffer).toString(frmt == "base64" ? 'base64' : 'utf8'),
481
- syntax: "mustache",
482
- format: frmt,
483
- output: "str",
484
- x: 100,
485
- y: 50 * (allFiles.length + 1),
486
- wires: [
487
- []
488
- ]
489
- })
490
-
491
- next() // ready for next entry
492
- })
493
-
494
- stream.resume() // just auto drain the stream
495
- })
496
-
497
- extract.on('finish', function () {
498
- // all entries read, wire them together
499
- for (var idx = 0; idx < allFiles.length - 1; idx++) {
500
- allFiles[idx].wires = [[allFiles[idx + 1].id]];
501
- }
430
+ const onError = (err) => {
431
+ msg.error = err;
432
+ done("extraction error", msg)
433
+ }
502
434
 
435
+ const onFinish = (allFiles) => {
503
436
  msg.payload = JSON.stringify(allFiles);
504
437
  send(msg)
505
438
 
@@ -515,15 +448,9 @@ module.exports = function (RED) {
515
448
  );
516
449
 
517
450
  done()
518
- })
519
-
520
- extract.on('error', function (err) {
521
- msg.error = err;
522
- done("extraction error", msg)
523
- });
524
-
525
- var stream = streamx.Readable.from(Buffer.from(pakoGzip.inflate(new Uint8Array(msg.payload))))
526
- stream.pipe(extract);
451
+ }
452
+
453
+ require('./lib/tarhelpers.js').convertTarFile(RED, msg.payload, onFinish, onError)
527
454
  } catch (err) {
528
455
  msg.error = err
529
456
  done(err.message, msg)
@@ -0,0 +1,106 @@
1
+ module.exports = (function () {
2
+
3
+ var tarStream = require('tar-stream');
4
+ var streamx = require('streamx');
5
+ var pakoGzip = require('pako');
6
+
7
+ /*
8
+ * there is no indication in a tar file of whether a file is binary or textual.
9
+ * we can only make a guess by the extension of the filename.
10
+ ***/
11
+ var computeFormat = (filename) => {
12
+ var ext = filename.split(".").at(-1);
13
+
14
+ return {
15
+ "html": "html",
16
+ "js": "javascript",
17
+ "md": "markdown",
18
+ "json": "json",
19
+ "txt": "text",
20
+ "css": "css",
21
+ "yaml": "yaml",
22
+ "yml": "yaml",
23
+
24
+ /* binary formats are encoded in base64 */
25
+ "png": "base64",
26
+ "tiff": "base64",
27
+ "tif": "base64",
28
+ "jpg": "base64",
29
+ "jpeg": "base64",
30
+ "bin": "base64",
31
+ "bmp": "base64",
32
+ "gif": "base64",
33
+ "woff2": "base64",
34
+ "woff": "base64",
35
+ "ttf": "base64",
36
+ "mov": "base64",
37
+ "ico": "base64",
38
+ "eot": "base64",
39
+ }[ext.toLowerCase()] || "text";
40
+ }
41
+
42
+ var convertTarFile = (RED, tgzData, onFinish, onError) => {
43
+ const extract = tarStream.extract()
44
+
45
+ var allFiles = [];
46
+
47
+ extract.on('entry', function (header, stream, next) {
48
+ // header is the tar header
49
+ // stream is the content body (might be an empty stream)
50
+ // call next when you are done with this entry
51
+
52
+ var buffer = [];
53
+
54
+ stream.on('data', function (data) {
55
+ buffer.push(data)
56
+ });
57
+
58
+ stream.on('end', function () {
59
+ var frmt = computeFormat(header.name.split("/").at(-1));
60
+
61
+ allFiles.push({
62
+ id: RED.util.generateId(),
63
+ type: "PkgFile",
64
+ name: header.name.split("/").at(-1),
65
+ filename: header.name.replace(/^package\//, ''),
66
+ // @ts-ignore
67
+ template: Buffer.concat(buffer).toString(frmt == "base64" ? 'base64' : 'utf8'),
68
+ syntax: "plain", // not mustache templates, these are files.
69
+ format: frmt,
70
+ output: "str",
71
+ x: 250 * Math.floor(allFiles.length / 40),
72
+ y: 50 * (allFiles.length % 40),
73
+ wires: [
74
+ []
75
+ ]
76
+ })
77
+
78
+ next() // ready for next entry
79
+ })
80
+
81
+ stream.resume() // just auto drain the stream
82
+ })
83
+
84
+ extract.on('finish', function () {
85
+ // all entries read, wire them together
86
+ for (var idx = 0; idx < allFiles.length - 1; idx++) {
87
+ allFiles[idx].wires = [[allFiles[idx + 1].id]];
88
+ }
89
+
90
+ onFinish(allFiles)
91
+ })
92
+
93
+ extract.on('error', onError );
94
+
95
+ var stream = streamx.Readable.from(Buffer.from(pakoGzip.inflate(new Uint8Array(tgzData))))
96
+ stream.pipe(extract);
97
+ }
98
+
99
+
100
+ let exports = {
101
+ computeFormat: computeFormat,
102
+ convertTarFile: convertTarFile
103
+ }
104
+
105
+ return exports;
106
+ })();
@@ -0,0 +1,22 @@
1
+ <script type="text/javascript">
2
+ (function ($) {
3
+ RED.nodes.registerType('NodeFactorySidebarCfg', {
4
+ category: 'config',
5
+ hasUsers: false,
6
+ defaults: {
7
+ pkgname: { value: "node-red-dashboard" },
8
+ pkgversion: { value: "latest" }
9
+ },
10
+ paletteLabel: 'NodeFactorySidebarCfg',
11
+ label: function () {
12
+ return "NodeFactorySidebarCfg"
13
+ }
14
+ });
15
+ })(jQuery);
16
+ </script>
17
+
18
+ <!-- The html for the config node info panel (in right sidebar) -->
19
+ <script type="text/x-red" data-template-name="NodeFactorySidebarCfg">
20
+ <p>Generate nodes for an existing node package.</p>
21
+ Generate nodes for an existing node package.
22
+ </script>
@@ -0,0 +1,193 @@
1
+ module.exports = function (RED) {
2
+ let pacote = require('pacote');
3
+ let tarHelpers = require('./lib/tarhelpers.js');
4
+
5
+ function ConfigNodeFactorySidebarFunctionality(config) {
6
+ RED.nodes.createNode(this, config)
7
+ }
8
+
9
+ RED.nodes.registerType('NodeFactorySidebarCfg', ConfigNodeFactorySidebarFunctionality);
10
+
11
+ function createDevOpsNode(pkgname, pkgversion, x, y, nodeid) {
12
+ return {
13
+ "id": RED.util.generateId(),
14
+ "type": "NodeDevOps",
15
+ "name": "",
16
+ "pname": pkgname,
17
+ "pversion": pkgversion,
18
+ "pauthorname": "",
19
+ "pauthoremail": "",
20
+ "pdescription": "",
21
+ "noderedinstall": true,
22
+ "randompackagename": false,
23
+ "ignore_package_check": false,
24
+ "gitcommit": false,
25
+ "gitcheckforchange": false,
26
+ "githubowner": "",
27
+ "githubrepo": "",
28
+ "githubbranch": "main",
29
+ "githubauthor": "",
30
+ "githubauthoremail": "",
31
+ "githubmessage": "",
32
+ "npmpublish": false,
33
+ "npmunpublish": false,
34
+ "npmotp": "",
35
+ "x": x - 120,
36
+ "y": y - 50,
37
+ "wires": [
38
+ [nodeid]
39
+ ]
40
+ }
41
+ }
42
+
43
+ function createTarballNode(x,y,nodeid) {
44
+ return {
45
+ "id": RED.util.generateId(),
46
+ "type": "NpmTarBall",
47
+ "name": "",
48
+ "x": x+150,
49
+ "y": y+50,
50
+ "wires": [
51
+ [nodeid]
52
+ ]
53
+ };
54
+ }
55
+
56
+ function createNodeRedInstallNode(x,y) {
57
+ return {
58
+ "id": RED.util.generateId(),
59
+ "type": "NodeRedInstall",
60
+ "name": "",
61
+ "x": x+300,
62
+ "y": y+100,
63
+ "wires": [
64
+ []
65
+ ]
66
+ }
67
+ }
68
+
69
+ RED.httpAdmin.post("/NodeFactorySidebarCfg",
70
+ RED.auth.needsPermission("nodedev.write"),
71
+ (req, res) => {
72
+ try {
73
+ if (req.body) {
74
+ var msg = req.body;
75
+ if ( msg.pkgname && msg.pkgversion ) {
76
+ const onFinish = (allFiles) => {
77
+
78
+ /* these are no templates, these are files - no mustache intrepretation */
79
+ allFiles.forEach(function(element) {
80
+ element.syntax = "plain"
81
+ });
82
+
83
+ var lastNode = allFiles[allFiles.length - 1];
84
+
85
+ var nodeDevOp = createDevOpsNode(msg.pkgname, msg.pkgversion, allFiles[0].x, allFiles[0].y, allFiles[0].id)
86
+ var nrInstallNode = createNodeRedInstallNode(lastNode.x, lastNode.y)
87
+ var tarballNode = createTarballNode(lastNode.x, lastNode.y, nrInstallNode.id)
88
+
89
+ let pkgjson = allFiles.filter( a => {
90
+ return a.filename == "package.json"
91
+ })[0];
92
+
93
+ if ( pkgjson) {
94
+ let ctnt = JSON.parse(pkgjson.template);
95
+ nodeDevOp.pversion = ctnt.version
96
+ nodeDevOp.pdescription = ctnt.description || "Empty"
97
+ nodeDevOp.pauthorname = ctnt.author || "Empty"
98
+ nodeDevOp.pauthoremail = ctnt.author || "Empty"
99
+ }
100
+
101
+ lastNode.wires[0].push(tarballNode.id)
102
+
103
+ allFiles.push(nodeDevOp);
104
+ allFiles.push(tarballNode)
105
+ allFiles.push(nrInstallNode)
106
+
107
+ RED.comms.publish(
108
+ "nodedev:perform-autoimport-nodes",
109
+ RED.util.encodeObject({
110
+ msg: "autoimport",
111
+ payload: JSON.stringify(allFiles),
112
+ })
113
+ );
114
+ }
115
+
116
+ const onError = (err) => {
117
+ RED.comms.publish(
118
+ "nodedev:perform-autoimport-nodes",
119
+ RED.util.encodeObject({
120
+ msg: "notify",
121
+ text: "Failed to create nodes for " + msg.pkgname + "@" + msg.pkgversion + ": " + err,
122
+ type: "error",
123
+ })
124
+ );
125
+ }
126
+
127
+ pacote.manifest(
128
+ msg.pkgname + "@" + msg.pkgversion
129
+ ).then(manifest => {
130
+ RED.comms.publish(
131
+ "nodedev:perform-autoimport-nodes",
132
+ RED.util.encodeObject({
133
+ msg: "notify",
134
+ text: "Found url for " + msg.pkgname + "@" + msg.pkgversion,
135
+ type: "warning",
136
+ })
137
+ );
138
+
139
+ import('got').then((module) => {
140
+ module.got.get(manifest._resolved,
141
+ {
142
+ timeout: {
143
+ request: 25000,
144
+ response: 25000
145
+ },
146
+ responseType: 'buffer'
147
+ }).then(resp => {
148
+ try {
149
+ tarHelpers.convertTarFile(RED, resp.body, onFinish, onError);
150
+ } catch (err) {
151
+ onError(err)
152
+ }
153
+ }).catch(err => {
154
+ onError(err)
155
+ });
156
+ }).catch(err => {
157
+ onError(err)
158
+ });
159
+
160
+ }).catch(err => {
161
+ RED.comms.publish(
162
+ "nodedev:perform-autoimport-nodes",
163
+ RED.util.encodeObject({
164
+ msg: "notify",
165
+ text: "Failed to retrieve tgz file for " + msg.pkgname + "@" + msg.pkgversion + ": " + err,
166
+ type: "error",
167
+ })
168
+ );
169
+ })
170
+
171
+ res.sendStatus(200);
172
+
173
+ RED.comms.publish(
174
+ "nodedev:perform-autoimport-nodes",
175
+ RED.util.encodeObject({
176
+ msg: "notify",
177
+ text: "Retrieving " + msg.pkgname + "@" + msg.pkgversion,
178
+ type: "warning",
179
+ })
180
+ );
181
+ } else {
182
+ res.sendStatus(404);
183
+ }
184
+ } else {
185
+ res.sendStatus(405);
186
+ }
187
+ } catch (err) {
188
+ console.error("ERROR", err)
189
+ res.sendStatus(500);
190
+ }
191
+ }
192
+ );
193
+ }
package/package.json CHANGED
@@ -1,34 +1,44 @@
1
1
  {
2
2
  "name" : "@gregoriusrippenstein/node-red-contrib-nodedev",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "dependencies": {
5
5
  "pako": "latest",
6
6
  "tar-stream": "latest",
7
7
  "mustache": "latest",
8
8
  "libnpmpublish": "latest",
9
- "streamx": "latest"
9
+ "streamx": "latest",
10
+ "got": "latest",
11
+ "pacote": "latest"
10
12
  },
11
13
 
12
14
  "keywords": [
13
- "node-red", "node development", "introspection", "bootstrapping", "recursion"
15
+ "node-red",
16
+ "node development",
17
+ "introspection",
18
+ "bootstrapping",
19
+ "recursion"
14
20
  ],
15
21
 
16
22
  "homepage": "https://github.com/gorenje/node-red-contrib-nodedev#readme",
17
23
  "license": "Don't do evil.",
18
- "author": "Gerrit Riessen <does.anyone@spread-the.love> (https://spread-the.love)",
24
+ "author": "Gerrit Riessen <gerrit@openmindmap.org> (https://spread-the.love)",
19
25
  "engines": {
20
26
  "node": ">=8"
21
27
  },
22
28
 
23
29
  "node-red" : {
24
30
  "version": ">=3.0.0",
31
+ "plugins": {
32
+ "nodefactory_sidebar": "plugins/node-factory-sidebar.html"
33
+ },
25
34
  "nodes": {
26
35
  "nodedevops": "nodes/05-node-dev-ops.js",
27
36
  "nodefactory": "nodes/10-node-factory.js",
28
37
  "pkgfile": "nodes/20-pkg-file.js",
29
38
  "npmtarball": "nodes/30-npm-tarball.js",
30
39
  "noderedinstall": "nodes/40-node-red-install.js",
31
- "npmpublish": "nodes/50-npm-publish.js"
40
+ "npmpublish": "nodes/50-npm-publish.js",
41
+ "nodefactorycfg": "nodes/node-factory-sidebar-cfg.js"
32
42
  }
33
43
  },
34
44
 
@@ -0,0 +1,188 @@
1
+ <script type="text/javascript">
2
+ (function() {
3
+ var globalYourConfigNode = null;
4
+
5
+ function doSubmission(pkgname,pkgversion) {
6
+ ensureYourConfigNodeExists();
7
+
8
+ $.ajax({
9
+ url: "NodeFactorySidebarCfg",
10
+ type: "POST",
11
+ contentType: "application/json; charset=utf-8",
12
+
13
+ data: JSON.stringify({
14
+ pkgname: $("#node-input-nodefactorysidebar-package-name").val(),
15
+ pkgversion: $("#node-input-nodefactorysidebar-package-version").val()
16
+ }),
17
+
18
+ success: function (resp) {
19
+ RED.notify("Submission triggered", {
20
+ type: "warning",
21
+ id: "NodeFactorySidebar",
22
+ timeout: 2000
23
+ });
24
+ },
25
+
26
+ error: function (jqXHR, textStatus, errorThrown) {
27
+ RED.notify("Submission failed: " + textStatus, {
28
+ type: "error",
29
+ id: "NodeFactorySidebar",
30
+ timeout: 2000
31
+ });
32
+ console.log(textStatus,errorThrown )
33
+ }
34
+ });
35
+ }
36
+
37
+ function ensureYourConfigNodeExists() {
38
+ // This function makes sure there is 1 instance of your config node is available, and that the globalYourConfigNode variable refers to it.
39
+ // Explained in the next step of this tutorial... --> https://discourse.nodered.org/t/tutorial-create-a-sidebar-plugin-and-persist-the-data-in-a-config-node/82020
40
+
41
+ // If we had found it previously, check if it has been deleted by the user behind our back
42
+ if (globalYourConfigNode !== null) {
43
+ var configNode = RED.nodes.node(globalYourConfigNode.id);
44
+ if (configNode === null) { globalYourConfigNode = null; }
45
+ }
46
+
47
+ // If not found previously, let's go find it
48
+ if (globalYourConfigNode === null) {
49
+ var configNodes = [];
50
+ RED.nodes.eachConfig(function(configNode) {
51
+ if (configNode.type === 'NodeFactorySidebarCfg') {
52
+ configNodes.push(configNode);
53
+ }
54
+ });
55
+
56
+ // Make sure we only have 1 config node
57
+ while (configNodes.length > 1) {
58
+ var configNode = configNodes.pop();
59
+ RED.nodes.remove(configNode.id);
60
+ RED.nodes.dirty(true);
61
+ }
62
+
63
+ // When we found a config node, let's use that one
64
+ if (configNodes.length === 1) { globalYourConfigNode = configNodes[0]; }
65
+ }
66
+
67
+ // When it doesn't exist yet, create it if required
68
+ if (globalYourConfigNode === null) {
69
+ // Remark: since this config node is dynamically created (and only used in this sidebar which isn't another node), the config
70
+ // node is in fact "unused". But since we don't want it to appear "unused" in the "config nodes" panel, we need to set hasUsers
71
+ // to false (see https://github.com/node-red/node-red/blob/master/CHANGELOG.md#0161-maintenance-release).
72
+ // The hasUsers needs also to be specified in the RED.nodes.registerType statement!
73
+ let typ = RED.nodes.getType("NodeFactorySidebarCfg");
74
+
75
+ globalYourConfigNode = {
76
+ id: RED.nodes.id(), // on the server side, this is called RED.util.generateId()
77
+ _def: typ,
78
+ type: "NodeFactorySidebarCfg",
79
+ hasUsers: false,
80
+ users: [],
81
+ name: "NodeFactorySidebar",
82
+ label: function() { return this.name || "NodeFactorySidebar"},
83
+ /* values and data defined by this config node */
84
+ pkgname: typ.defaults.pkgname.value,
85
+ pkgversion: typ.defaults.pkgversion.value
86
+ }
87
+
88
+ // Add the new config node to the collection of Node-RED nodes
89
+ RED.nodes.add(globalYourConfigNode);
90
+
91
+ // Make sure the "Deploy" button becomes active
92
+ RED.nodes.dirty(true);
93
+ }
94
+ }
95
+
96
+ // Add your plugin as a new tabsheet in the right sidebar AFTER the flow editor is completely started
97
+ var initialiseConfigNodeOnce = () => {
98
+ RED.events.off('runtime-state', initialiseConfigNodeOnce);
99
+
100
+ // The html content of the sidebar has been specified below as a data-template, from where it can be loaded:
101
+ var content = $($('script[type="text/x-red"][data-template-name="NodeFactorySidebar"]').i18n().html());
102
+
103
+ // Add a "Your sidebar" tabsheet to the right sidebar panel, in which this sidebar panel can be displayed
104
+ // --> more details: https://nodered.org/docs/api/ui/sidebar/
105
+ RED.sidebar.addTab({
106
+ id: "NodeFactorySidebar",
107
+ label: "Node Factory", // short name for the tab
108
+ name: "Node Factory", // long name for the menu
109
+ content: content,
110
+ closeable: true,
111
+ // disableOnEdit: true,
112
+ enableOnEdit: true,
113
+ iconClass: "fa fa-industry" // your fontawesome icon
114
+ });
115
+
116
+ ensureYourConfigNodeExists();
117
+
118
+ var doSomething = (e) => {
119
+ if (e) { e.preventDefault(); }
120
+ doSubmission()
121
+ }
122
+
123
+ $('#node-input-nodefactorysidebar-generate-btn').on('click', doSomething );
124
+
125
+ $("#node-input-nodefactorysidebar-package-version").val(globalYourConfigNode.pkgversion);
126
+
127
+ $("#node-input-nodefactorysidebar-package-version").on("change", function() {
128
+ ensureYourConfigNodeExists();
129
+
130
+ let data = $(this).val();
131
+
132
+ if (globalYourConfigNode.pkgversion != data) {
133
+ globalYourConfigNode.pkgversion = data;
134
+ RED.nodes.dirty(true);
135
+ }
136
+ })
137
+ $("#node-input-nodefactorysidebar-package-name").val(globalYourConfigNode.pkgname);
138
+
139
+ $("#node-input-nodefactorysidebar-package-name").on("change", function() {
140
+ ensureYourConfigNodeExists();
141
+
142
+ let data = $(this).val();
143
+
144
+ if (globalYourConfigNode.pkgname != data) {
145
+ globalYourConfigNode.pkgname = data;
146
+ RED.nodes.dirty(true);
147
+ }
148
+ })
149
+
150
+ };
151
+
152
+ RED.events.on('runtime-state', initialiseConfigNodeOnce);
153
+ })();
154
+ </script>
155
+
156
+ <!-- The html for the right sidebar plugin screen -->
157
+ <script type="text/x-red" data-template-name="NodeFactorySidebar">
158
+ <div class="form-row" style="margin-left: 10px; margin-top: 30px;">
159
+ Enter package details here:
160
+ </div>
161
+
162
+ <div class="form-row" style="margin-left: 10px;">
163
+ <label for="node-input-nodefactorysidebar-package-name">
164
+ <i class="fa fa-tag"></i>
165
+ Name
166
+ </label>
167
+ <input type="text" id="node-input-nodefactorysidebar-package-name"
168
+ placeholder="node-red-dashboard">
169
+ </div>
170
+
171
+ <div class="form-row" style="margin-left: 10px;">
172
+ <label for="node-input-nodefactorysidebar-package-version">
173
+ <i class="fa fa-tag"></i>
174
+ Version
175
+ </label>
176
+ <input type="text" id="node-input-nodefactorysidebar-package-version"
177
+ placeholder="3.5.0">
178
+ </div>
179
+
180
+ <div class="form-row">
181
+ <div style="position: relative; height: 100%; margin: 15px;">
182
+ <button id="node-input-nodefactorysidebar-generate-btn"
183
+ class="red-ui-button"
184
+ style="width: 100%; margin-top: 30px">Generate Nodes</button>
185
+ </div>
186
+ </div>
187
+
188
+ </script>