@node-red/editor-client 4.0.0-beta.1 → 4.0.0-beta.2

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.
Files changed (139) hide show
  1. package/locales/de/editor.json +2 -0
  2. package/locales/en-US/editor.json +2 -0
  3. package/locales/fr/editor.json +8 -1
  4. package/locales/ja/editor.json +10 -3
  5. package/package.json +1 -1
  6. package/public/red/about +30 -0
  7. package/public/red/red.js +815 -162
  8. package/public/red/red.min.js +3 -3
  9. package/public/red/style.min.css +1 -1
  10. package/public/red/tours/3.1/welcome.js +8 -8
  11. package/public/red/tours/images/nr4-config-select.png +0 -0
  12. package/public/red/tours/images/nr4-multiplayer.png +0 -0
  13. package/public/red/tours/images/nr4-plugins.png +0 -0
  14. package/public/red/tours/welcome.js +105 -11
  15. package/public/types/node/assert/strict.d.ts +1 -1
  16. package/public/types/node/assert.d.ts +8 -9
  17. package/public/types/node/async_hooks.d.ts +9 -5
  18. package/public/types/node/buffer.d.ts +43 -18
  19. package/public/types/node/child_process.d.ts +8 -5
  20. package/public/types/node/cluster.d.ts +15 -19
  21. package/public/types/node/console.d.ts +2 -2
  22. package/public/types/node/crypto.d.ts +165 -70
  23. package/public/types/node/dgram.d.ts +4 -4
  24. package/public/types/node/diagnostics_channel.d.ts +8 -7
  25. package/public/types/node/dns/promises.d.ts +11 -9
  26. package/public/types/node/dns.d.ts +18 -13
  27. package/public/types/node/dom-events.d.ts +129 -0
  28. package/public/types/node/domain.d.ts +2 -2
  29. package/public/types/node/events.d.ts +49 -12
  30. package/public/types/node/fs/promises.d.ts +68 -24
  31. package/public/types/node/fs.d.ts +132 -59
  32. package/public/types/node/globals.d.ts +31 -17
  33. package/public/types/node/http.d.ts +138 -27
  34. package/public/types/node/http2.d.ts +38 -5
  35. package/public/types/node/https.d.ts +12 -3
  36. package/public/types/node/module.d.ts +1 -2
  37. package/public/types/node/net.d.ts +69 -28
  38. package/public/types/node/os.d.ts +16 -5
  39. package/public/types/node/path.d.ts +5 -5
  40. package/public/types/node/perf_hooks.d.ts +48 -9
  41. package/public/types/node/process.d.ts +18 -17
  42. package/public/types/node/querystring.d.ts +2 -2
  43. package/public/types/node/readline/promises.d.ts +146 -0
  44. package/public/types/node/readline.d.ts +141 -31
  45. package/public/types/node/stream/consumers.d.ts +2 -2
  46. package/public/types/node/stream/promises.d.ts +1 -1
  47. package/public/types/node/stream/web.d.ts +4 -66
  48. package/public/types/node/stream.d.ts +96 -118
  49. package/public/types/node/string_decoder.d.ts +2 -2
  50. package/public/types/node/test.d.ts +200 -16
  51. package/public/types/node/timers/promises.d.ts +1 -26
  52. package/public/types/node/timers.d.ts +2 -2
  53. package/public/types/node/tls.d.ts +21 -12
  54. package/public/types/node/trace_events.d.ts +12 -2
  55. package/public/types/node/ts4.8/assert/strict.d.ts +11 -0
  56. package/public/types/node/ts4.8/assert.d.ts +964 -0
  57. package/public/types/node/ts4.8/async_hooks.d.ts +504 -0
  58. package/public/types/node/ts4.8/buffer.d.ts +2262 -0
  59. package/public/types/node/ts4.8/child_process.d.ts +1372 -0
  60. package/public/types/node/ts4.8/cluster.d.ts +413 -0
  61. package/public/types/node/ts4.8/console.d.ts +415 -0
  62. package/public/types/node/ts4.8/crypto.d.ts +3967 -0
  63. package/public/types/node/ts4.8/dgram.d.ts +548 -0
  64. package/public/types/node/ts4.8/diagnostics_channel.d.ts +156 -0
  65. package/public/types/node/ts4.8/dns/promises.d.ts +373 -0
  66. package/public/types/node/ts4.8/dns.d.ts +662 -0
  67. package/public/types/node/ts4.8/dom-events.d.ts +129 -0
  68. package/public/types/node/ts4.8/domain.d.ts +173 -0
  69. package/public/types/node/ts4.8/events.d.ts +681 -0
  70. package/public/types/node/ts4.8/fs/promises.d.ts +1141 -0
  71. package/public/types/node/ts4.8/fs.d.ts +3875 -0
  72. package/public/types/node/ts4.8/globals.d.ts +297 -0
  73. package/public/types/node/ts4.8/http.d.ts +1617 -0
  74. package/public/types/node/ts4.8/http2.d.ts +2137 -0
  75. package/public/types/node/ts4.8/https.d.ts +544 -0
  76. package/public/types/node/ts4.8/module.d.ts +117 -0
  77. package/public/types/node/ts4.8/net.d.ts +872 -0
  78. package/public/types/node/ts4.8/os.d.ts +469 -0
  79. package/public/types/node/ts4.8/path.d.ts +194 -0
  80. package/public/types/node/ts4.8/perf_hooks.d.ts +628 -0
  81. package/public/types/node/ts4.8/process.d.ts +1485 -0
  82. package/public/types/node/ts4.8/querystring.d.ts +134 -0
  83. package/public/types/node/ts4.8/readline/promises.d.ts +146 -0
  84. package/public/types/node/ts4.8/readline.d.ts +656 -0
  85. package/public/types/node/ts4.8/stream/consumers.d.ts +15 -0
  86. package/public/types/node/ts4.8/stream/promises.d.ts +45 -0
  87. package/public/types/node/ts4.8/stream/web.d.ts +333 -0
  88. package/public/types/node/ts4.8/stream.d.ts +1343 -0
  89. package/public/types/node/ts4.8/string_decoder.d.ts +70 -0
  90. package/public/types/node/ts4.8/test.d.ts +377 -0
  91. package/public/types/node/ts4.8/timers/promises.d.ts +71 -0
  92. package/public/types/node/ts4.8/timers.d.ts +97 -0
  93. package/public/types/node/ts4.8/tls.d.ts +1031 -0
  94. package/public/types/node/ts4.8/trace_events.d.ts +174 -0
  95. package/public/types/node/ts4.8/tty.d.ts +209 -0
  96. package/public/types/node/ts4.8/url.d.ts +900 -0
  97. package/public/types/node/ts4.8/util.d.ts +1853 -0
  98. package/public/types/node/ts4.8/v8.d.ts +399 -0
  99. package/public/types/node/ts4.8/vm.d.ts +512 -0
  100. package/public/types/node/ts4.8/wasi.d.ts +161 -0
  101. package/public/types/node/ts4.8/worker_threads.d.ts +692 -0
  102. package/public/types/node/ts4.8/zlib.d.ts +520 -0
  103. package/public/types/node/tty.d.ts +5 -3
  104. package/public/types/node/url.d.ts +81 -39
  105. package/public/types/node/util.d.ts +269 -13
  106. package/public/types/node/v8.d.ts +22 -4
  107. package/public/types/node/vm.d.ts +7 -5
  108. package/public/types/node/wasi.d.ts +2 -2
  109. package/public/types/node/worker_threads.d.ts +51 -11
  110. package/public/types/node/zlib.d.ts +2 -2
  111. package/public/types/node-red/func.d.ts +26 -17
  112. package/public/vendor/ace/worker-jsonata.js +1 -1
  113. package/public/vendor/monaco/dist/{fa2cc0ab9f0bec2b3365.ttf → 0c718f5b7d2bce997c5f.ttf} +0 -0
  114. package/public/vendor/monaco/dist/css.worker.js +1 -1
  115. package/public/vendor/monaco/dist/css.worker.js.LICENSE.txt +1 -1
  116. package/public/vendor/monaco/dist/editor.js +1 -29
  117. package/public/vendor/monaco/dist/editor.js.LICENSE.txt +2 -2
  118. package/public/vendor/monaco/dist/editor.worker.js +1 -1
  119. package/public/vendor/monaco/dist/html.worker.js +1 -1
  120. package/public/vendor/monaco/dist/html.worker.js.LICENSE.txt +1 -1
  121. package/public/vendor/monaco/dist/json.worker.js +1 -1
  122. package/public/vendor/monaco/dist/json.worker.js.LICENSE.txt +1 -1
  123. package/public/vendor/monaco/dist/locale/cs.js +324 -106
  124. package/public/vendor/monaco/dist/locale/de.js +336 -118
  125. package/public/vendor/monaco/dist/locale/es.js +329 -111
  126. package/public/vendor/monaco/dist/locale/fr.js +334 -116
  127. package/public/vendor/monaco/dist/locale/it.js +327 -109
  128. package/public/vendor/monaco/dist/locale/ja.js +329 -111
  129. package/public/vendor/monaco/dist/locale/ko.js +330 -112
  130. package/public/vendor/monaco/dist/locale/pl.js +329 -111
  131. package/public/vendor/monaco/dist/locale/pt-br.js +329 -111
  132. package/public/vendor/monaco/dist/locale/qps-ploc.js +330 -112
  133. package/public/vendor/monaco/dist/locale/ru.js +331 -113
  134. package/public/vendor/monaco/dist/locale/tr.js +329 -111
  135. package/public/vendor/monaco/dist/locale/zh-hans.js +331 -113
  136. package/public/vendor/monaco/dist/locale/zh-hant.js +331 -113
  137. package/public/vendor/monaco/dist/ts.worker.js +2 -2
  138. package/public/vendor/vendor.js +1 -1
  139. package/public/vendor/monaco/dist/7064e66c3890a12c47b4.ttf +0 -0
package/public/red/red.js CHANGED
@@ -116,6 +116,7 @@ var RED = (function() {
116
116
  cache: false,
117
117
  url: 'plugins',
118
118
  success: function(data) {
119
+ RED.plugins.setPluginList(data);
119
120
  loader.reportProgress(RED._("event.loadPlugins"), 13)
120
121
  RED.i18n.loadPluginCatalogs(function() {
121
122
  loadPlugins(function() {
@@ -625,6 +626,41 @@ var RED = (function() {
625
626
  RED.view.redrawStatus(node);
626
627
  }
627
628
  });
629
+ RED.comms.subscribe("notification/plugin/#",function(topic,msg) {
630
+ if (topic == "notification/plugin/added") {
631
+ RED.settings.refreshSettings(function(err, data) {
632
+ let addedPlugins = [];
633
+ msg.forEach(function(m) {
634
+ let id = m.id;
635
+ RED.plugins.addPlugin(m);
636
+
637
+ m.plugins.forEach((p) => {
638
+ addedPlugins.push(p.id);
639
+ })
640
+
641
+ RED.i18n.loadNodeCatalog(id, function() {
642
+ var lang = localStorage.getItem("editor-language")||RED.i18n.detectLanguage();
643
+ $.ajax({
644
+ headers: {
645
+ "Accept":"text/html",
646
+ "Accept-Language": lang
647
+ },
648
+ cache: false,
649
+ url: 'plugins/'+id,
650
+ success: function(data) {
651
+ appendPluginConfig(data);
652
+ }
653
+ });
654
+ });
655
+ });
656
+ if (addedPlugins.length) {
657
+ let pluginList = "<ul><li>"+addedPlugins.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>";
658
+ // ToDo: Adapt notification (node -> plugin)
659
+ RED.notify(RED._("palette.event.nodeAdded", {count:addedPlugins.length})+pluginList,"success");
660
+ }
661
+ })
662
+ }
663
+ });
628
664
 
629
665
  let pendingNodeRemovedNotifications = []
630
666
  let pendingNodeRemovedTimeout
@@ -894,6 +930,10 @@ var RED = (function() {
894
930
 
895
931
  RED.nodes.init();
896
932
  RED.runtime.init()
933
+
934
+ if (RED.settings.theme("multiplayer.enabled",false)) {
935
+ RED.multiplayer.init()
936
+ }
897
937
  RED.comms.connect();
898
938
 
899
939
  $("#red-ui-main-container").show();
@@ -1812,6 +1852,7 @@ RED.user = (function() {
1812
1852
  }
1813
1853
 
1814
1854
  function logout() {
1855
+ RED.events.emit('logout')
1815
1856
  var tokens = RED.settings.get("auth-tokens");
1816
1857
  var token = tokens?tokens.access_token:"";
1817
1858
  $.ajax({
@@ -1850,6 +1891,7 @@ RED.user = (function() {
1850
1891
  });
1851
1892
  }
1852
1893
  });
1894
+ $('<i class="fa fa-user"></i>').appendTo("#red-ui-header-button-user");
1853
1895
  } else {
1854
1896
  RED.menu.addItem("red-ui-header-button-user",{
1855
1897
  id:"usermenu-item-username",
@@ -1862,6 +1904,15 @@ RED.user = (function() {
1862
1904
  RED.user.logout();
1863
1905
  }
1864
1906
  });
1907
+ const userMenu = $("#red-ui-header-button-user")
1908
+ userMenu.empty()
1909
+ if (RED.settings.user.image) {
1910
+ $('<span class="user-profile"></span>').css({
1911
+ backgroundImage: "url("+RED.settings.user.image+")",
1912
+ }).appendTo(userMenu);
1913
+ } else {
1914
+ $('<i class="fa fa-user"></i>').appendTo(userMenu);
1915
+ }
1865
1916
  }
1866
1917
 
1867
1918
  }
@@ -1872,14 +1923,6 @@ RED.user = (function() {
1872
1923
 
1873
1924
  var userMenu = $('<li><a id="red-ui-header-button-user" class="button hide" href="#"></a></li>')
1874
1925
  .prependTo(".red-ui-header-toolbar");
1875
- if (RED.settings.user.image) {
1876
- $('<span class="user-profile"></span>').css({
1877
- backgroundImage: "url("+RED.settings.user.image+")",
1878
- }).appendTo(userMenu.find("a"));
1879
- } else {
1880
- $('<i class="fa fa-user"></i>').appendTo(userMenu.find("a"));
1881
- }
1882
-
1883
1926
  RED.menu.init({id:"red-ui-header-button-user",
1884
1927
  options: []
1885
1928
  });
@@ -1979,6 +2022,15 @@ RED.comms = (function() {
1979
2022
  var reconnectAttempts = 0;
1980
2023
  var active = false;
1981
2024
 
2025
+ RED.events.on('login', function(username) {
2026
+ // User has logged in
2027
+ // Need to upgrade the connection to be authenticated
2028
+ if (ws && ws.readyState == 1) {
2029
+ const auth_tokens = RED.settings.get("auth-tokens");
2030
+ ws.send(JSON.stringify({auth:auth_tokens.access_token}))
2031
+ }
2032
+ })
2033
+
1982
2034
  function connectWS() {
1983
2035
  active = true;
1984
2036
  var wspath;
@@ -2009,6 +2061,7 @@ RED.comms = (function() {
2009
2061
  ws.send(JSON.stringify({subscribe:t}));
2010
2062
  }
2011
2063
  }
2064
+ emit('connect')
2012
2065
  }
2013
2066
 
2014
2067
  ws = new WebSocket(wspath);
@@ -2133,10 +2186,54 @@ RED.comms = (function() {
2133
2186
  }
2134
2187
  }
2135
2188
 
2189
+ function send(topic, msg) {
2190
+ if (ws && ws.readyState == 1) {
2191
+ ws.send(JSON.stringify({
2192
+ topic,
2193
+ data: msg
2194
+ }))
2195
+ }
2196
+ }
2197
+
2198
+ const eventHandlers = {};
2199
+ function on(evt,func) {
2200
+ eventHandlers[evt] = eventHandlers[evt]||[];
2201
+ eventHandlers[evt].push(func);
2202
+ }
2203
+ function off(evt,func) {
2204
+ const handler = eventHandlers[evt];
2205
+ if (handler) {
2206
+ for (let i=0;i<handler.length;i++) {
2207
+ if (handler[i] === func) {
2208
+ handler.splice(i,1);
2209
+ return;
2210
+ }
2211
+ }
2212
+ }
2213
+ }
2214
+ function emit() {
2215
+ const evt = arguments[0]
2216
+ const args = Array.prototype.slice.call(arguments,1);
2217
+ if (eventHandlers[evt]) {
2218
+ let cpyHandlers = [...eventHandlers[evt]];
2219
+ for (let i=0;i<cpyHandlers.length;i++) {
2220
+ try {
2221
+ cpyHandlers[i].apply(null, args);
2222
+ } catch(err) {
2223
+ console.warn("RED.comms.emit error: ["+evt+"] "+(err.toString()));
2224
+ console.warn(err);
2225
+ }
2226
+ }
2227
+ }
2228
+ }
2229
+
2136
2230
  return {
2137
2231
  connect: connectWS,
2138
2232
  subscribe: subscribe,
2139
- unsubscribe:unsubscribe
2233
+ unsubscribe:unsubscribe,
2234
+ on,
2235
+ off,
2236
+ send
2140
2237
  }
2141
2238
  })();
2142
2239
  ;RED.runtime = (function() {
@@ -2175,6 +2272,223 @@ RED.comms = (function() {
2175
2272
  }
2176
2273
  }
2177
2274
  })()
2275
+ ;RED.multiplayer = (function () {
2276
+
2277
+ // sessionId - used to identify sessions across websocket reconnects
2278
+ let sessionId
2279
+
2280
+ let headerWidget
2281
+ // Map of session id to { session:'', user:{}, location:{}}
2282
+ let connections = {}
2283
+ // Map of username to { user:{}, connections:[] }
2284
+ let users = {}
2285
+
2286
+ function addUserConnection (connection) {
2287
+ if (connections[connection.session]) {
2288
+ // This is an existing connection that has been authenticated
2289
+ const existingConnection = connections[connection.session]
2290
+ if (existingConnection.user.username !== connection.user.username) {
2291
+ removeUserButton(users[existingConnection.user.username])
2292
+ }
2293
+ }
2294
+ connections[connection.session] = connection
2295
+ const user = users[connection.user.username] = users[connection.user.username] || {
2296
+ user: connection.user,
2297
+ connections: []
2298
+ }
2299
+ connection.location = connection.location || {}
2300
+ user.connections.push(connection)
2301
+
2302
+ if (connection.user.username === RED.settings.user?.username ||
2303
+ connection.session === sessionId
2304
+ ) {
2305
+ // This is the current user - do not add a extra button for them
2306
+ } else {
2307
+ if (user.connections.length === 1) {
2308
+ if (user.button) {
2309
+ clearTimeout(user.inactiveTimeout)
2310
+ clearTimeout(user.removeTimeout)
2311
+ user.button.removeClass('inactive')
2312
+ } else {
2313
+ addUserButton(user)
2314
+ }
2315
+ }
2316
+ }
2317
+ }
2318
+
2319
+ function removeUserConnection (session, isDisconnected) {
2320
+ const connection = connections[session]
2321
+ delete connections[session]
2322
+ const user = users[connection.user.username]
2323
+ const i = user.connections.indexOf(connection)
2324
+ user.connections.splice(i, 1)
2325
+ if (isDisconnected) {
2326
+ removeUserButton(user)
2327
+ } else {
2328
+ if (user.connections.length === 0) {
2329
+ // Give the user 5s to reconnect before marking inactive
2330
+ user.inactiveTimeout = setTimeout(() => {
2331
+ user.button.addClass('inactive')
2332
+ // Give the user further 20 seconds to reconnect before removing them
2333
+ // from the user toolbar entirely
2334
+ user.removeTimeout = setTimeout(() => {
2335
+ removeUserButton(user)
2336
+ }, 20000)
2337
+ }, 5000)
2338
+ }
2339
+ }
2340
+ }
2341
+
2342
+ function addUserButton (user) {
2343
+ user.button = $('<li class="red-ui-multiplayer-user"><button type="button" class="red-ui-multiplayer-user-icon" href="#"></button></li>')
2344
+ .attr('data-username', user.user.username)
2345
+ .prependTo("#red-ui-multiplayer-user-list");
2346
+ var button = user.button.find("button")
2347
+ button.on('click', function () {
2348
+ RED.popover.create({
2349
+ target:button,
2350
+ trigger: 'modal',
2351
+ interactive: true,
2352
+ width: "250px",
2353
+ direction: 'bottom',
2354
+ content: () => {
2355
+ const content = $('<div>')
2356
+ $('<div style="text-align: center">').text(user.user.username).appendTo(content)
2357
+
2358
+ const location = user.connections[0].location
2359
+ if (location.workspace) {
2360
+ const ws = RED.nodes.workspace(location.workspace) || RED.nodes.subflow(location.workspace)
2361
+ if (ws) {
2362
+ $('<div>').text(`${ws.type}: ${ws.label||ws.name||ws.id}`).appendTo(content)
2363
+ } else {
2364
+ $('<div>').text(`tab: unknown`).appendTo(content)
2365
+ }
2366
+ }
2367
+ if (location.node) {
2368
+ const node = RED.nodes.node(location.node)
2369
+ if (node) {
2370
+ $('<div>').text(`node: ${node.id}`).appendTo(content)
2371
+ } else {
2372
+ $('<div>').text(`node: unknown`).appendTo(content)
2373
+ }
2374
+ }
2375
+ return content
2376
+ },
2377
+ }).open()
2378
+ })
2379
+ if (!user.user.image) {
2380
+ $('<i class="fa fa-user"></i>').appendTo(button);
2381
+ } else {
2382
+ $('<span class="user-profile"></span>').css({
2383
+ backgroundImage: "url("+user.user.image+")",
2384
+ }).appendTo(button);
2385
+ }
2386
+ }
2387
+
2388
+ function getLocation () {
2389
+ const location = {
2390
+ workspace: RED.workspaces.active()
2391
+ }
2392
+ const editStack = RED.editor.getEditStack()
2393
+ for (let i = editStack.length - 1; i >= 0; i--) {
2394
+ if (editStack[i].id) {
2395
+ location.node = editStack[i].id
2396
+ break
2397
+ }
2398
+ }
2399
+ return location
2400
+ }
2401
+ function updateLocation () {
2402
+ const location = getLocation()
2403
+ if (location.workspace !== 0) {
2404
+ log('send', 'multiplayer/location', location)
2405
+ RED.comms.send('multiplayer/location', location)
2406
+ }
2407
+ }
2408
+
2409
+ function removeUserButton (user) {
2410
+ user.button.remove()
2411
+ delete user.button
2412
+ }
2413
+
2414
+ function updateUserLocation (data) {
2415
+ connections[data.session].location = data
2416
+ delete data.session
2417
+ }
2418
+ return {
2419
+ init: function () {
2420
+
2421
+
2422
+ sessionId = RED.settings.getLocal('multiplayer:sessionId')
2423
+ if (!sessionId) {
2424
+ sessionId = RED.nodes.id()
2425
+ RED.settings.setLocal('multiplayer:sessionId', sessionId)
2426
+ }
2427
+
2428
+ headerWidget = $('<li><ul id="red-ui-multiplayer-user-list"></ul></li>').prependTo('.red-ui-header-toolbar')
2429
+
2430
+ RED.comms.on('connect', () => {
2431
+ const location = getLocation()
2432
+ const connectInfo = {
2433
+ session: sessionId
2434
+ }
2435
+ if (location.workspace !== 0) {
2436
+ connectInfo.location = location
2437
+ }
2438
+ RED.comms.send('multiplayer/connect', connectInfo)
2439
+ })
2440
+ RED.comms.subscribe('multiplayer/#', (topic, msg) => {
2441
+ log('recv', topic, msg)
2442
+ if (topic === 'multiplayer/init') {
2443
+ // We have just reconnected, runtime has sent state to
2444
+ // initialise the world
2445
+ connections = {}
2446
+ users = {}
2447
+ $('#red-ui-multiplayer-user-list').empty()
2448
+
2449
+ msg.forEach(connection => {
2450
+ addUserConnection(connection)
2451
+ })
2452
+ } else if (topic === 'multiplayer/connection-added') {
2453
+ addUserConnection(msg)
2454
+ } else if (topic === 'multiplayer/connection-removed') {
2455
+ removeUserConnection(msg.session, msg.disconnected)
2456
+ } else if (topic === 'multiplayer/location') {
2457
+ updateUserLocation(msg)
2458
+ }
2459
+ })
2460
+
2461
+ RED.events.on('workspace:change', (event) => {
2462
+ updateLocation()
2463
+ })
2464
+ RED.events.on('editor:open', () => {
2465
+ updateLocation()
2466
+ })
2467
+ RED.events.on('editor:close', () => {
2468
+ updateLocation()
2469
+ })
2470
+ RED.events.on('editor:change', () => {
2471
+ updateLocation()
2472
+ })
2473
+ RED.events.on('login', () => {
2474
+ updateLocation()
2475
+ })
2476
+ RED.events.on('logout', () => {
2477
+ const disconnectInfo = {
2478
+ session: sessionId
2479
+ }
2480
+ RED.comms.send('multiplayer/disconnect', disconnectInfo)
2481
+ RED.settings.removeLocal('multiplayer:sessionId')
2482
+ })
2483
+ }
2484
+ }
2485
+
2486
+ function log() {
2487
+ if (RED.multiplayer.DEBUG) {
2488
+ console.log('[multiplayer]', ...arguments)
2489
+ }
2490
+ }
2491
+ })();
2178
2492
  ;/**
2179
2493
  * Copyright JS Foundation and other contributors, http://js.foundation
2180
2494
  *
@@ -3672,6 +3986,7 @@ RED.state = {
3672
3986
  ;RED.plugins = (function() {
3673
3987
  var plugins = {};
3674
3988
  var pluginsByType = {};
3989
+ var moduleList = {};
3675
3990
 
3676
3991
  function registerPlugin(id,definition) {
3677
3992
  plugins[id] = definition;
@@ -3709,10 +4024,44 @@ RED.state = {
3709
4024
  function getPluginsByType(type) {
3710
4025
  return pluginsByType[type] || [];
3711
4026
  }
4027
+
4028
+ function setPluginList(list) {
4029
+ for(let i=0;i<list.length;i++) {
4030
+ let p = list[i];
4031
+ addPlugin(p);
4032
+ }
4033
+ }
4034
+
4035
+ function addPlugin(p) {
4036
+
4037
+ moduleList[p.module] = moduleList[p.module] || {
4038
+ name:p.module,
4039
+ version:p.version,
4040
+ local:p.local,
4041
+ sets:{},
4042
+ plugin: true,
4043
+ id: p.id
4044
+ };
4045
+ if (p.pending_version) {
4046
+ moduleList[p.module].pending_version = p.pending_version;
4047
+ }
4048
+ moduleList[p.module].sets[p.name] = p;
4049
+
4050
+ RED.events.emit("registry:plugin-module-added",p.module);
4051
+ }
4052
+
4053
+ function getModule(module) {
4054
+ return moduleList[module];
4055
+ }
4056
+
3712
4057
  return {
3713
4058
  registerPlugin: registerPlugin,
3714
4059
  getPlugin: getPlugin,
3715
- getPluginsByType: getPluginsByType
4060
+ getPluginsByType: getPluginsByType,
4061
+
4062
+ setPluginList: setPluginList,
4063
+ addPlugin: addPlugin,
4064
+ getModule: getModule
3716
4065
  }
3717
4066
  })();
3718
4067
  ;/**
@@ -3866,6 +4215,8 @@ RED.nodes = (function() {
3866
4215
  },
3867
4216
  removeNodeSet: function(id) {
3868
4217
  var ns = nodeSets[id];
4218
+ if (!ns) { return {} }
4219
+
3869
4220
  for (var j=0;j<ns.types.length;j++) {
3870
4221
  delete typeToId[ns.types[j]];
3871
4222
  }
@@ -4289,12 +4640,16 @@ RED.nodes = (function() {
4289
4640
  * @param {String} z tab id
4290
4641
  */
4291
4642
  checkTabState: function (z) {
4292
- const ws = workspaces[z]
4643
+ const ws = workspaces[z] || subflows[z]
4293
4644
  if (ws) {
4294
4645
  const contentsChanged = tabDirtyMap[z].size > 0 || tabDeletedNodesMap[z].size > 0
4295
4646
  if (Boolean(ws.contentsChanged) !== contentsChanged) {
4296
4647
  ws.contentsChanged = contentsChanged
4297
- RED.events.emit("flows:change", ws);
4648
+ if (ws.type === 'tab') {
4649
+ RED.events.emit("flows:change", ws);
4650
+ } else {
4651
+ RED.events.emit("subflows:change", ws);
4652
+ }
4298
4653
  }
4299
4654
  }
4300
4655
  }
@@ -4767,7 +5122,22 @@ RED.nodes = (function() {
4767
5122
  RED.nodes.registerType("subflow:"+sf.id, {
4768
5123
  defaults:{
4769
5124
  name:{value:""},
4770
- env:{value:[]}
5125
+ env:{value:[], validate: function(value) {
5126
+ const errors = []
5127
+ if (value) {
5128
+ value.forEach(env => {
5129
+ const r = RED.utils.validateTypedProperty(env.value, env.type)
5130
+ if (r !== true) {
5131
+ errors.push(env.name+': '+r)
5132
+ }
5133
+ })
5134
+ }
5135
+ if (errors.length === 0) {
5136
+ return true
5137
+ } else {
5138
+ return errors
5139
+ }
5140
+ }}
4771
5141
  },
4772
5142
  icon: function() { return sf.icon||"subflow.svg" },
4773
5143
  category: sf.category || "subflows",
@@ -12309,7 +12679,7 @@ RED.popover = (function() {
12309
12679
  closePopup(true);
12310
12680
  });
12311
12681
  }
12312
- if (trigger === 'hover' && options.interactive) {
12682
+ if (/*trigger === 'hover' && */options.interactive) {
12313
12683
  div.on('mouseenter', function(e) {
12314
12684
  clearTimeout(timer);
12315
12685
  active = true;
@@ -19920,10 +20290,6 @@ RED.keyboard = (function() {
19920
20290
  }
19921
20291
 
19922
20292
  function init(done) {
19923
- if (!RED.user.hasPermission("settings.write")) {
19924
- RED.notify(RED._("user.errors.settings"),"error");
19925
- return;
19926
- }
19927
20293
  RED.userSettings.add({
19928
20294
  id:'envvar',
19929
20295
  title: RED._("env-var.environment"),
@@ -20447,6 +20813,11 @@ RED.workspaces = (function() {
20447
20813
  createWorkspaceTabs();
20448
20814
  RED.events.on("sidebar:resize",workspace_tabs.resize);
20449
20815
 
20816
+ RED.events.on("workspace:clear", () => {
20817
+ // Reset the index used to generate new flow names
20818
+ workspaceIndex = 0
20819
+ })
20820
+
20450
20821
  RED.actions.add("core:show-next-tab",function() {
20451
20822
  var oldActive = activeWorkspace;
20452
20823
  workspace_tabs.nextTab();
@@ -20613,6 +20984,9 @@ RED.workspaces = (function() {
20613
20984
  RED.events.on("flows:change", (ws) => {
20614
20985
  $("#red-ui-tab-"+(ws.id.replace(".","-"))).toggleClass('red-ui-workspace-changed',!!(ws.contentsChanged || ws.changed || ws.added));
20615
20986
  })
20987
+ RED.events.on("subflows:change", (ws) => {
20988
+ $("#red-ui-tab-"+(ws.id.replace(".","-"))).toggleClass('red-ui-workspace-changed',!!(ws.contentsChanged || ws.changed || ws.added));
20989
+ })
20616
20990
 
20617
20991
  hideWorkspace();
20618
20992
  }
@@ -29427,6 +29801,10 @@ RED.palette = (function() {
29427
29801
  var categoryContainers = {};
29428
29802
  var sidebarControls;
29429
29803
 
29804
+ let paletteState = { filter: "", collapsed: [] };
29805
+
29806
+ let filterRefreshTimeout
29807
+
29430
29808
  function createCategory(originalCategory,rootCategory,category,ns) {
29431
29809
  if ($("#red-ui-palette-base-category-"+rootCategory).length === 0) {
29432
29810
  createCategoryContainer(originalCategory,rootCategory, ns+":palette.label."+rootCategory);
@@ -29452,20 +29830,57 @@ RED.palette = (function() {
29452
29830
  catDiv.data('label',label);
29453
29831
  categoryContainers[category] = {
29454
29832
  container: catDiv,
29455
- close: function() {
29833
+ hide: function (instant) {
29834
+ if (instant) {
29835
+ catDiv.hide()
29836
+ } else {
29837
+ catDiv.slideUp()
29838
+ }
29839
+ },
29840
+ show: function () {
29841
+ catDiv.show()
29842
+ },
29843
+ isOpen: function () {
29844
+ return !!catDiv.hasClass("red-ui-palette-open")
29845
+ },
29846
+ getNodeCount: function (visibleOnly) {
29847
+ const nodes = catDiv.find(".red-ui-palette-node")
29848
+ if (visibleOnly) {
29849
+ return nodes.filter(function() { return $(this).css('display') !== 'none'}).length
29850
+ } else {
29851
+ return nodes.length
29852
+ }
29853
+ },
29854
+ close: function(instant, skipSaveState) {
29456
29855
  catDiv.removeClass("red-ui-palette-open");
29457
29856
  catDiv.addClass("red-ui-palette-closed");
29458
- $("#red-ui-palette-base-category-"+category).slideUp();
29857
+ if (instant) {
29858
+ $("#red-ui-palette-base-category-"+category).hide();
29859
+ } else {
29860
+ $("#red-ui-palette-base-category-"+category).slideUp();
29861
+ }
29459
29862
  $("#red-ui-palette-header-"+category+" i").removeClass("expanded");
29863
+ if (!skipSaveState) {
29864
+ if (!paletteState.collapsed.includes(category)) {
29865
+ paletteState.collapsed.push(category);
29866
+ savePaletteState();
29867
+ }
29868
+ }
29460
29869
  },
29461
- open: function() {
29870
+ open: function(skipSaveState) {
29462
29871
  catDiv.addClass("red-ui-palette-open");
29463
29872
  catDiv.removeClass("red-ui-palette-closed");
29464
29873
  $("#red-ui-palette-base-category-"+category).slideDown();
29465
29874
  $("#red-ui-palette-header-"+category+" i").addClass("expanded");
29875
+ if (!skipSaveState) {
29876
+ if (paletteState.collapsed.includes(category)) {
29877
+ paletteState.collapsed.splice(paletteState.collapsed.indexOf(category), 1);
29878
+ savePaletteState();
29879
+ }
29880
+ }
29466
29881
  },
29467
29882
  toggle: function() {
29468
- if (catDiv.hasClass("red-ui-palette-open")) {
29883
+ if (categoryContainers[category].isOpen()) {
29469
29884
  categoryContainers[category].close();
29470
29885
  } else {
29471
29886
  categoryContainers[category].open();
@@ -29807,8 +30222,16 @@ RED.palette = (function() {
29807
30222
 
29808
30223
  var categoryNode = $("#red-ui-palette-container-"+rootCategory);
29809
30224
  if (categoryNode.find(".red-ui-palette-node").length === 1) {
29810
- categoryContainers[rootCategory].open();
30225
+ if (!paletteState?.collapsed?.includes(rootCategory)) {
30226
+ categoryContainers[rootCategory].open();
30227
+ } else {
30228
+ categoryContainers[rootCategory].close(true);
30229
+ }
29811
30230
  }
30231
+ clearTimeout(filterRefreshTimeout)
30232
+ filterRefreshTimeout = setTimeout(() => {
30233
+ refreshFilter()
30234
+ }, 200)
29812
30235
 
29813
30236
  }
29814
30237
  }
@@ -29908,7 +30331,8 @@ RED.palette = (function() {
29908
30331
  paletteNode.css("backgroundColor", sf.color);
29909
30332
  }
29910
30333
 
29911
- function filterChange(val) {
30334
+ function refreshFilter() {
30335
+ const val = $("#red-ui-palette-search input").val()
29912
30336
  var re = new RegExp(val.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'),'i');
29913
30337
  $("#red-ui-palette-container .red-ui-palette-node").each(function(i,el) {
29914
30338
  var currentLabel = $(el).attr("data-palette-label");
@@ -29920,16 +30344,26 @@ RED.palette = (function() {
29920
30344
  }
29921
30345
  });
29922
30346
 
29923
- for (var category in categoryContainers) {
30347
+ for (let category in categoryContainers) {
29924
30348
  if (categoryContainers.hasOwnProperty(category)) {
29925
- if (categoryContainers[category].container
29926
- .find(".red-ui-palette-node")
29927
- .filter(function() { return $(this).css('display') !== 'none'}).length === 0) {
29928
- categoryContainers[category].close();
29929
- categoryContainers[category].container.slideUp();
30349
+ const categorySection = categoryContainers[category]
30350
+ if (categorySection.getNodeCount(true) === 0) {
30351
+ categorySection.hide()
29930
30352
  } else {
29931
- categoryContainers[category].open();
29932
- categoryContainers[category].container.show();
30353
+ categorySection.show()
30354
+ if (val) {
30355
+ // There is a filter being applied and it has matched
30356
+ // something in this category - show the contents
30357
+ categorySection.open(true)
30358
+ } else {
30359
+ // No filter. Only show the category if it isn't in lastState
30360
+ if (!paletteState.collapsed.includes(category)) {
30361
+ categorySection.open(true)
30362
+ } else if (categorySection.isOpen()) {
30363
+ // This section should be collapsed but isn't - so make it so
30364
+ categorySection.close(true, true)
30365
+ }
30366
+ }
29933
30367
  }
29934
30368
  }
29935
30369
  }
@@ -29945,6 +30379,9 @@ RED.palette = (function() {
29945
30379
 
29946
30380
  $("#red-ui-palette > .red-ui-palette-spinner").show();
29947
30381
 
30382
+ RED.events.on('logout', function () {
30383
+ RED.settings.removeLocal('palette-state')
30384
+ })
29948
30385
 
29949
30386
  RED.events.on('registry:node-type-added', function(nodeType) {
29950
30387
  var def = RED.nodes.getType(nodeType);
@@ -29988,14 +30425,14 @@ RED.palette = (function() {
29988
30425
 
29989
30426
  RED.events.on("subflows:change",refreshSubflow);
29990
30427
 
29991
-
29992
-
29993
30428
  $("#red-ui-palette-search input").searchBox({
29994
30429
  delay: 100,
29995
30430
  change: function() {
29996
- filterChange($(this).val());
30431
+ refreshFilter();
30432
+ paletteState.filter = $(this).val();
30433
+ savePaletteState();
29997
30434
  }
29998
- })
30435
+ });
29999
30436
 
30000
30437
  sidebarControls = $('<div class="red-ui-sidebar-control-left"><i class="fa fa-chevron-left"></i></div>').appendTo($("#red-ui-palette"));
30001
30438
  RED.popover.tooltip(sidebarControls,RED._("keyboard.togglePalette"),"core:toggle-palette");
@@ -30061,7 +30498,23 @@ RED.palette = (function() {
30061
30498
  togglePalette(state);
30062
30499
  }
30063
30500
  });
30501
+
30502
+ try {
30503
+ paletteState = JSON.parse(RED.settings.getLocal("palette-state") || '{"filter":"", "collapsed": []}');
30504
+ if (paletteState.filter) {
30505
+ // Apply the category filter
30506
+ $("#red-ui-palette-search input").searchBox("value", paletteState.filter);
30507
+ }
30508
+ } catch (error) {
30509
+ console.error("Unexpected error loading palette state from localStorage: ", error);
30510
+ }
30511
+ setTimeout(() => {
30512
+ // Lazily tidy up any categories that haven't been reloaded
30513
+ paletteState.collapsed = paletteState.collapsed.filter(category => !!categoryContainers[category])
30514
+ savePaletteState()
30515
+ }, 10000)
30064
30516
  }
30517
+
30065
30518
  function togglePalette(state) {
30066
30519
  if (!state) {
30067
30520
  $("#red-ui-main-container").addClass("red-ui-palette-closed");
@@ -30081,6 +30534,15 @@ RED.palette = (function() {
30081
30534
  })
30082
30535
  return categories;
30083
30536
  }
30537
+
30538
+ function savePaletteState() {
30539
+ try {
30540
+ RED.settings.setLocal("palette-state", JSON.stringify(paletteState));
30541
+ } catch (error) {
30542
+ console.error("Unexpected error saving palette state to localStorage: ", error);
30543
+ }
30544
+ }
30545
+
30084
30546
  return {
30085
30547
  init: init,
30086
30548
  add:addNodeType,
@@ -31570,8 +32032,10 @@ RED.sidebar.help = (function() {
31570
32032
 
31571
32033
  function refreshSubflow(sf) {
31572
32034
  var item = treeList.treeList('get',"node-type:subflow:"+sf.id);
31573
- item.subflowLabel = sf._def.label().toLowerCase();
31574
- item.treeList.replaceElement(getNodeLabel({_def:sf._def,type:sf._def.label()}));
32035
+ if (item) {
32036
+ item.subflowLabel = sf._def.label().toLowerCase();
32037
+ item.treeList.replaceElement(getNodeLabel({_def:sf._def,type:sf._def.label()}));
32038
+ }
31575
32039
  }
31576
32040
 
31577
32041
  function hideTOC() {
@@ -32938,86 +33402,106 @@ RED.palette.editor = (function() {
32938
33402
  var moduleInfo = nodeEntries[module].info;
32939
33403
  var nodeEntry = nodeEntries[module].elements;
32940
33404
  if (nodeEntry) {
32941
- var activeTypeCount = 0;
32942
- var typeCount = 0;
32943
- var errorCount = 0;
32944
- nodeEntry.errorList.empty();
32945
- nodeEntries[module].totalUseCount = 0;
32946
- nodeEntries[module].setUseCount = {};
32947
-
32948
- for (var setName in moduleInfo.sets) {
32949
- if (moduleInfo.sets.hasOwnProperty(setName)) {
32950
- var inUseCount = 0;
32951
- var set = moduleInfo.sets[setName];
32952
- var setElements = nodeEntry.sets[setName];
32953
- if (set.err) {
32954
- errorCount++;
32955
- var errMessage = set.err;
32956
- if (set.err.message) {
32957
- errMessage = set.err.message;
32958
- } else if (set.err.code) {
32959
- errMessage = set.err.code;
32960
- }
32961
- $("<li>").text(errMessage).appendTo(nodeEntry.errorList);
32962
- }
32963
- if (set.enabled) {
32964
- activeTypeCount += set.types.length;
32965
- }
32966
- typeCount += set.types.length;
32967
- for (var i=0;i<moduleInfo.sets[setName].types.length;i++) {
32968
- var t = moduleInfo.sets[setName].types[i];
32969
- inUseCount += (typesInUse[t]||0);
32970
- var swatch = setElements.swatches[t];
32971
- if (set.enabled) {
32972
- var def = RED.nodes.getType(t);
32973
- if (def && def.color) {
32974
- swatch.css({background:RED.utils.getNodeColor(t,def)});
32975
- swatch.css({border: "1px solid "+getContrastingBorder(swatch.css('backgroundColor'))})
32976
- }
33405
+ if (moduleInfo.plugin) {
33406
+ nodeEntry.enableButton.hide();
33407
+ nodeEntry.removeButton.show();
33408
+
33409
+ let pluginCount = 0;
33410
+ for (let setName in moduleInfo.sets) {
33411
+ if (moduleInfo.sets.hasOwnProperty(setName)) {
33412
+ let set = moduleInfo.sets[setName];
33413
+ if (set.plugins) {
33414
+ pluginCount += set.plugins.length;
32977
33415
  }
32978
33416
  }
32979
- nodeEntries[module].setUseCount[setName] = inUseCount;
32980
- nodeEntries[module].totalUseCount += inUseCount;
33417
+ }
33418
+
33419
+ nodeEntry.setCount.text(RED._('palette.editor.pluginCount',{count:pluginCount,label:pluginCount}));
32981
33420
 
32982
- if (inUseCount > 0) {
32983
- setElements.enableButton.text(RED._('palette.editor.inuse'));
32984
- setElements.enableButton.addClass('disabled');
32985
- } else {
32986
- setElements.enableButton.removeClass('disabled');
33421
+ } else {
33422
+ var activeTypeCount = 0;
33423
+ var typeCount = 0;
33424
+ var errorCount = 0;
33425
+ nodeEntry.errorList.empty();
33426
+ nodeEntries[module].totalUseCount = 0;
33427
+ nodeEntries[module].setUseCount = {};
33428
+
33429
+ for (var setName in moduleInfo.sets) {
33430
+ if (moduleInfo.sets.hasOwnProperty(setName)) {
33431
+ var inUseCount = 0;
33432
+ const set = moduleInfo.sets[setName];
33433
+ const setElements = nodeEntry.sets[setName]
33434
+
33435
+ if (set.err) {
33436
+ errorCount++;
33437
+ var errMessage = set.err;
33438
+ if (set.err.message) {
33439
+ errMessage = set.err.message;
33440
+ } else if (set.err.code) {
33441
+ errMessage = set.err.code;
33442
+ }
33443
+ $("<li>").text(errMessage).appendTo(nodeEntry.errorList);
33444
+ }
32987
33445
  if (set.enabled) {
32988
- setElements.enableButton.text(RED._('palette.editor.disable'));
32989
- } else {
32990
- setElements.enableButton.text(RED._('palette.editor.enable'));
33446
+ activeTypeCount += set.types.length;
33447
+ }
33448
+ typeCount += set.types.length;
33449
+ for (var i=0;i<moduleInfo.sets[setName].types.length;i++) {
33450
+ var t = moduleInfo.sets[setName].types[i];
33451
+ inUseCount += (typesInUse[t]||0);
33452
+ if (setElements && set.enabled) {
33453
+ var def = RED.nodes.getType(t);
33454
+ if (def && def.color) {
33455
+ setElements.swatches[t].css({background:RED.utils.getNodeColor(t,def)});
33456
+ setElements.swatches[t].css({border: "1px solid "+getContrastingBorder(setElements.swatches[t].css('backgroundColor'))})
33457
+ }
33458
+ }
33459
+ }
33460
+ nodeEntries[module].setUseCount[setName] = inUseCount;
33461
+ nodeEntries[module].totalUseCount += inUseCount;
33462
+
33463
+ if (setElements) {
33464
+ if (inUseCount > 0) {
33465
+ setElements.enableButton.text(RED._('palette.editor.inuse'));
33466
+ setElements.enableButton.addClass('disabled');
33467
+ } else {
33468
+ setElements.enableButton.removeClass('disabled');
33469
+ if (set.enabled) {
33470
+ setElements.enableButton.text(RED._('palette.editor.disable'));
33471
+ } else {
33472
+ setElements.enableButton.text(RED._('palette.editor.enable'));
33473
+ }
33474
+ }
33475
+ setElements.setRow.toggleClass("red-ui-palette-module-set-disabled",!set.enabled);
32991
33476
  }
32992
33477
  }
32993
- setElements.setRow.toggleClass("red-ui-palette-module-set-disabled",!set.enabled);
32994
33478
  }
32995
- }
32996
33479
 
32997
- if (errorCount === 0) {
32998
- nodeEntry.errorRow.hide()
32999
- } else {
33000
- nodeEntry.errorRow.show();
33001
- }
33480
+ if (errorCount === 0) {
33481
+ nodeEntry.errorRow.hide()
33482
+ } else {
33483
+ nodeEntry.errorRow.show();
33484
+ }
33002
33485
 
33003
- var nodeCount = (activeTypeCount === typeCount)?typeCount:activeTypeCount+" / "+typeCount;
33004
- nodeEntry.setCount.text(RED._('palette.editor.nodeCount',{count:typeCount,label:nodeCount}));
33486
+ var nodeCount = (activeTypeCount === typeCount)?typeCount:activeTypeCount+" / "+typeCount;
33487
+ nodeEntry.setCount.text(RED._('palette.editor.nodeCount',{count:typeCount,label:nodeCount}));
33005
33488
 
33006
- if (nodeEntries[module].totalUseCount > 0) {
33007
- nodeEntry.enableButton.text(RED._('palette.editor.inuse'));
33008
- nodeEntry.enableButton.addClass('disabled');
33009
- nodeEntry.removeButton.hide();
33010
- } else {
33011
- nodeEntry.enableButton.removeClass('disabled');
33012
- if (moduleInfo.local) {
33013
- nodeEntry.removeButton.css('display', 'inline-block');
33014
- }
33015
- if (activeTypeCount === 0) {
33016
- nodeEntry.enableButton.text(RED._('palette.editor.enableall'));
33489
+ if (nodeEntries[module].totalUseCount > 0) {
33490
+ nodeEntry.enableButton.text(RED._('palette.editor.inuse'));
33491
+ nodeEntry.enableButton.addClass('disabled');
33492
+ nodeEntry.removeButton.hide();
33017
33493
  } else {
33018
- nodeEntry.enableButton.text(RED._('palette.editor.disableall'));
33494
+ nodeEntry.enableButton.removeClass('disabled');
33495
+ if (moduleInfo.local) {
33496
+ nodeEntry.removeButton.css('display', 'inline-block');
33497
+ }
33498
+ if (activeTypeCount === 0) {
33499
+ nodeEntry.enableButton.text(RED._('palette.editor.enableall'));
33500
+ } else {
33501
+ nodeEntry.enableButton.text(RED._('palette.editor.disableall'));
33502
+ }
33503
+ nodeEntry.container.toggleClass("disabled",(activeTypeCount === 0));
33019
33504
  }
33020
- nodeEntry.container.toggleClass("disabled",(activeTypeCount === 0));
33021
33505
  }
33022
33506
  }
33023
33507
  if (moduleInfo.pending_version) {
@@ -33368,6 +33852,33 @@ RED.palette.editor = (function() {
33368
33852
  }
33369
33853
  }
33370
33854
  })
33855
+
33856
+ RED.events.on("registry:plugin-module-added", function(module) {
33857
+
33858
+ if (!nodeEntries.hasOwnProperty(module)) {
33859
+ nodeEntries[module] = {info:RED.plugins.getModule(module)};
33860
+ var index = [module];
33861
+ for (var s in nodeEntries[module].info.sets) {
33862
+ if (nodeEntries[module].info.sets.hasOwnProperty(s)) {
33863
+ index.push(s);
33864
+ index = index.concat(nodeEntries[module].info.sets[s].types)
33865
+ }
33866
+ }
33867
+ nodeEntries[module].index = index.join(",").toLowerCase();
33868
+ nodeList.editableList('addItem', nodeEntries[module]);
33869
+ } else {
33870
+ _refreshNodeModule(module);
33871
+ }
33872
+
33873
+ for (var i=0;i<filteredList.length;i++) {
33874
+ if (filteredList[i].info.id === module) {
33875
+ var installButton = filteredList[i].elements.installButton;
33876
+ installButton.addClass('disabled');
33877
+ installButton.text(RED._('palette.editor.installed'));
33878
+ break;
33879
+ }
33880
+ }
33881
+ });
33371
33882
  }
33372
33883
 
33373
33884
  var settingsPane;
@@ -33494,6 +34005,7 @@ RED.palette.editor = (function() {
33494
34005
  errorRow: errorRow,
33495
34006
  errorList: errorList,
33496
34007
  setCount: setCount,
34008
+ setButton: setButton,
33497
34009
  container: container,
33498
34010
  shade: shade,
33499
34011
  versionSpan: versionSpan,
@@ -33504,49 +34016,88 @@ RED.palette.editor = (function() {
33504
34016
  if (container.hasClass('expanded')) {
33505
34017
  container.removeClass('expanded');
33506
34018
  contentRow.slideUp();
34019
+ setTimeout(() => {
34020
+ contentRow.empty()
34021
+ }, 200)
34022
+ object.elements.sets = {}
33507
34023
  } else {
33508
34024
  container.addClass('expanded');
34025
+ populateSetList()
33509
34026
  contentRow.slideDown();
33510
34027
  }
33511
34028
  })
33512
-
33513
- var setList = Object.keys(entry.sets)
33514
- setList.sort(function(A,B) {
33515
- return A.toLowerCase().localeCompare(B.toLowerCase());
33516
- });
33517
- setList.forEach(function(setName) {
33518
- var set = entry.sets[setName];
33519
- var setRow = $('<div>',{class:"red-ui-palette-module-set"}).appendTo(contentRow);
33520
- var buttonGroup = $('<div>',{class:"red-ui-palette-module-set-button-group"}).appendTo(setRow);
33521
- var typeSwatches = {};
33522
- set.types.forEach(function(t) {
33523
- var typeDiv = $('<div>',{class:"red-ui-palette-module-type"}).appendTo(setRow);
33524
- typeSwatches[t] = $('<span>',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv);
33525
- $('<span>',{class:"red-ui-palette-module-type-node"}).text(t).appendTo(typeDiv);
33526
- })
33527
- var enableButton = $('<a href="#" class="red-ui-button red-ui-button-small"></a>').appendTo(buttonGroup);
33528
- enableButton.on("click", function(evt) {
33529
- evt.preventDefault();
33530
- if (object.setUseCount[setName] === 0) {
33531
- var currentSet = RED.nodes.registry.getNodeSet(set.id);
33532
- shade.show();
33533
- var newState = !currentSet.enabled
33534
- changeNodeState(set.id,newState,shade,function(xhr){
33535
- if (xhr) {
33536
- if (xhr.responseJSON) {
33537
- RED.notify(RED._('palette.editor.errors.'+(newState?'enable':'disable')+'Failed',{module: id,message:xhr.responseJSON.message}));
34029
+ const populateSetList = function () {
34030
+ var setList = Object.keys(entry.sets)
34031
+ setList.sort(function(A,B) {
34032
+ return A.toLowerCase().localeCompare(B.toLowerCase());
34033
+ });
34034
+ setList.forEach(function(setName) {
34035
+ var set = entry.sets[setName];
34036
+ var setRow = $('<div>',{class:"red-ui-palette-module-set"}).appendTo(contentRow);
34037
+ var buttonGroup = $('<div>',{class:"red-ui-palette-module-set-button-group"}).appendTo(setRow);
34038
+ var typeSwatches = {};
34039
+ let enableButton;
34040
+ if (set.types) {
34041
+ set.types.forEach(function(t) {
34042
+ var typeDiv = $('<div>',{class:"red-ui-palette-module-type"}).appendTo(setRow);
34043
+ typeSwatches[t] = $('<span>',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv);
34044
+ if (set.enabled) {
34045
+ var def = RED.nodes.getType(t);
34046
+ if (def && def.color) {
34047
+ typeSwatches[t].css({background:RED.utils.getNodeColor(t,def)});
34048
+ typeSwatches[t].css({border: "1px solid "+getContrastingBorder(typeSwatches[t].css('backgroundColor'))})
33538
34049
  }
33539
34050
  }
33540
- });
34051
+ $('<span>',{class:"red-ui-palette-module-type-node"}).text(t).appendTo(typeDiv);
34052
+ })
34053
+ enableButton = $('<a href="#" class="red-ui-button red-ui-button-small"></a>').appendTo(buttonGroup);
34054
+ enableButton.on("click", function(evt) {
34055
+ evt.preventDefault();
34056
+ if (object.setUseCount[setName] === 0) {
34057
+ var currentSet = RED.nodes.registry.getNodeSet(set.id);
34058
+ shade.show();
34059
+ var newState = !currentSet.enabled
34060
+ changeNodeState(set.id,newState,shade,function(xhr){
34061
+ if (xhr) {
34062
+ if (xhr.responseJSON) {
34063
+ RED.notify(RED._('palette.editor.errors.'+(newState?'enable':'disable')+'Failed',{module: id,message:xhr.responseJSON.message}));
34064
+ }
34065
+ }
34066
+ });
34067
+ }
34068
+ })
34069
+
34070
+ if (object.setUseCount[setName] > 0) {
34071
+ enableButton.text(RED._('palette.editor.inuse'));
34072
+ enableButton.addClass('disabled');
34073
+ } else {
34074
+ enableButton.removeClass('disabled');
34075
+ if (set.enabled) {
34076
+ enableButton.text(RED._('palette.editor.disable'));
34077
+ } else {
34078
+ enableButton.text(RED._('palette.editor.enable'));
34079
+ }
34080
+ }
34081
+ setRow.toggleClass("red-ui-palette-module-set-disabled",!set.enabled);
34082
+
34083
+
34084
+ }
34085
+ if (set.plugins) {
34086
+ set.plugins.forEach(function(p) {
34087
+ var typeDiv = $('<div>',{class:"red-ui-palette-module-type"}).appendTo(setRow);
34088
+ // typeSwatches[p.id] = $('<span>',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv);
34089
+ $('<span><i class="fa fa-puzzle-piece" aria-hidden="true"></i> </span>',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv);
34090
+ $('<span>',{class:"red-ui-palette-module-type-node"}).text(p.id).appendTo(typeDiv);
34091
+ })
33541
34092
  }
33542
- })
33543
34093
 
33544
- object.elements.sets[set.name] = {
33545
- setRow: setRow,
33546
- enableButton: enableButton,
33547
- swatches: typeSwatches
33548
- };
33549
- });
34094
+ object.elements.sets[set.name] = {
34095
+ setRow: setRow,
34096
+ enableButton: enableButton,
34097
+ swatches: typeSwatches
34098
+ };
34099
+ });
34100
+ }
33550
34101
  enableButton.on("click", function(evt) {
33551
34102
  evt.preventDefault();
33552
34103
  if (object.totalUseCount === 0) {
@@ -33916,7 +34467,55 @@ RED.palette.editor = (function() {
33916
34467
  }
33917
34468
  }
33918
34469
  ]
33919
- }); }
34470
+ });
34471
+ }
34472
+ } else {
34473
+ // dedicated list management for plugins
34474
+ if (entry.plugin) {
34475
+
34476
+ let e = nodeEntries[entry.name];
34477
+ if (e) {
34478
+ nodeList.editableList('removeItem', e);
34479
+ delete nodeEntries[entry.name];
34480
+ }
34481
+
34482
+ // We assume that a plugin that implements onremove
34483
+ // cleans the editor accordingly of its left-overs.
34484
+ let found_onremove = true;
34485
+
34486
+ let keys = Object.keys(entry.sets);
34487
+ keys.forEach((key) => {
34488
+ let set = entry.sets[key];
34489
+ for (let i=0; i<set.plugins?.length; i++) {
34490
+ let plgn = RED.plugins.getPlugin(set.plugins[i].id);
34491
+ if (plgn && plgn.onremove && typeof plgn.onremove === 'function') {
34492
+ plgn.onremove();
34493
+ } else {
34494
+ if (plgn && plgn.onadd && typeof plgn.onadd === 'function') {
34495
+ // if there's no 'onadd', there shouldn't be any left-overs
34496
+ found_onremove = false;
34497
+ }
34498
+ }
34499
+ }
34500
+ });
34501
+
34502
+ if (!found_onremove) {
34503
+ let removeNotify = RED.notify("Removed plugin " + entry.name + ". Please reload the editor to clear left-overs.",{
34504
+ modal: true,
34505
+ fixed: true,
34506
+ type: 'warning',
34507
+ buttons: [
34508
+ {
34509
+ text: "Understood",
34510
+ class:"primary",
34511
+ click: function(e) {
34512
+ removeNotify.close();
34513
+ }
34514
+ }
34515
+ ]
34516
+ });
34517
+ }
34518
+ }
33920
34519
  }
33921
34520
  })
33922
34521
  notification.close();
@@ -34346,8 +34945,9 @@ RED.editor = (function() {
34346
34945
  nodeValue = node[property]
34347
34946
  }
34348
34947
 
34349
- const buttonId = `${prefix}-lookup-${property}`
34350
- const selectId = prefix + '-' + property
34948
+ const addBtnId = `${prefix}-btn-${property}-add`;
34949
+ const editBtnId = `${prefix}-btn-${property}-edit`;
34950
+ const selectId = prefix + '-' + property;
34351
34951
  const input = $(`#${selectId}`);
34352
34952
  if (input.length === 0) {
34353
34953
  return;
@@ -34370,40 +34970,68 @@ RED.editor = (function() {
34370
34970
  select.css({
34371
34971
  'flex-grow': 1
34372
34972
  });
34973
+
34373
34974
  updateConfigNodeSelect(property, type, nodeValue, prefix, filter);
34374
- const disableButton = function(disabled) {
34375
- btn.prop( "disabled", !!disabled)
34376
- btn.toggleClass("disabled", !!disabled)
34377
- }
34975
+
34378
34976
  // create the edit button
34379
- const btn = $('<a id="' + buttonId + '" class="red-ui-button"><i class="fa fa-pencil"></i></a>')
34977
+ const editButton = $('<a id="' + editBtnId + '" class="red-ui-button"><i class="fa fa-pencil"></i></a>')
34978
+ .css({ "margin-left": "10px" })
34979
+ .appendTo(outerWrap);
34980
+
34981
+ RED.popover.tooltip(editButton, RED._('editor.editConfig', { type }));
34982
+
34983
+ // create the add button
34984
+ const addButton = $('<a id="' + addBtnId + '" class="red-ui-button"><i class="fa fa-plus"></i></a>')
34380
34985
  .css({ "margin-left": "10px" })
34381
34986
  .appendTo(outerWrap);
34987
+ RED.popover.tooltip(addButton, RED._('editor.addNewConfig', { type }));
34988
+
34989
+ const disableButton = function(button, disabled) {
34990
+ $(button).prop("disabled", !!disabled)
34991
+ $(button).toggleClass("disabled", !!disabled)
34992
+ };
34382
34993
 
34383
34994
  // add the click handler
34384
- btn.on("click", function (e) {
34995
+ addButton.on("click", function (e) {
34996
+ if (addButton.prop("disabled")) { return }
34997
+ showEditConfigNodeDialog(property, type, "_ADD_", prefix, node);
34998
+ e.preventDefault();
34999
+ });
35000
+ editButton.on("click", function (e) {
34385
35001
  const selectedOpt = select.find(":selected")
34386
35002
  if (selectedOpt.data('env')) { return } // don't show the dialog for env vars items (MVP. Future enhancement: lookup the env, if present, show the associated edit dialog)
34387
- if (btn.prop("disabled")) { return }
35003
+ if (editButton.prop("disabled")) { return }
34388
35004
  showEditConfigNodeDialog(property, type, selectedOpt.val(), prefix, node);
34389
35005
  e.preventDefault();
34390
35006
  });
34391
35007
 
34392
35008
  // dont permit the user to click the button if the selected option is an env var
34393
35009
  select.on("change", function () {
34394
- const selectedOpt = select.find(":selected")
35010
+ const selectedOpt = select.find(":selected");
35011
+ const optionsLength = select.find("option").length;
34395
35012
  if (selectedOpt?.data('env')) {
34396
- disableButton(true)
35013
+ disableButton(addButton, true);
35014
+ disableButton(editButton, true);
35015
+ // disable the edit button if no options available
35016
+ } else if (optionsLength === 1 && selectedOpt.val() === "_ADD_") {
35017
+ disableButton(addButton, false);
35018
+ disableButton(editButton, true);
35019
+ } else if (selectedOpt.val() === "") {
35020
+ disableButton(addButton, false);
35021
+ disableButton(editButton, true);
34397
35022
  } else {
34398
- disableButton(false)
35023
+ disableButton(addButton, false);
35024
+ disableButton(editButton, false);
34399
35025
  }
34400
35026
  });
35027
+
34401
35028
  var label = "";
34402
35029
  var configNode = RED.nodes.node(nodeValue);
34403
35030
 
34404
35031
  if (configNode) {
34405
35032
  label = RED.utils.getNodeLabel(configNode, configNode.id);
34406
35033
  }
35034
+
34407
35035
  input.val(label);
34408
35036
  }
34409
35037
 
@@ -34897,7 +35525,12 @@ RED.editor = (function() {
34897
35525
  }
34898
35526
  }
34899
35527
 
34900
- select.append('<option value="_ADD_"'+(value===""?" selected":"")+'>'+RED._("editor.addNewType", {type:label})+'</option>');
35528
+ if (!configNodes.length) {
35529
+ select.append('<option value="_ADD_" selected>' + RED._("editor.addNewType", { type: label }) + '</option>');
35530
+ } else {
35531
+ select.append('<option value="">' + RED._("editor.inputs.none") + '</option>');
35532
+ }
35533
+
34901
35534
  window.setTimeout(function() { select.trigger("change");},50);
34902
35535
  }
34903
35536
  }
@@ -41007,7 +41640,7 @@ RED.editor.codeEditor.monaco = (function() {
41007
41640
  createMonacoCompletionItem("set (flow context)", 'flow.set("${1:name}", ${1:value});','Set a value in flow context',range),
41008
41641
  createMonacoCompletionItem("get (global context)", 'global.get("${1:name}");','Get a value from global context',range),
41009
41642
  createMonacoCompletionItem("set (global context)", 'global.set("${1:name}", ${1:value});','Set a value in global context',range),
41010
- createMonacoCompletionItem("get (env)", 'env.get("${1|NR_NODE_ID,NR_NODE_NAME,NR_NODE_PATH,NR_GROUP_ID,NR_GROUP_NAME,NR_FLOW_ID,NR_FLOW_NAME|}");','Get env variable value',range),
41643
+ createMonacoCompletionItem("get (env)", 'env.get("${1|NR_NODE_ID,NR_NODE_NAME,NR_NODE_PATH,NR_GROUP_ID,NR_GROUP_NAME,NR_FLOW_ID,NR_FLOW_NAME,NR_SUBFLOW_NAME,NR_SUBFLOW_ID,NR_SUBFLOW_PATH|}");','Get env variable value',range),
41011
41644
  createMonacoCompletionItem("cloneMessage (RED.util)", 'RED.util.cloneMessage(${1:msg});',
41012
41645
  ["```typescript",
41013
41646
  "RED.util.cloneMessage<T extends registry.NodeMessage>(msg: T): T",
@@ -42262,6 +42895,7 @@ RED.eventLog = (function() {
42262
42895
  setTimeout(function() {
42263
42896
  oldTray.tray.detach();
42264
42897
  showTray(options);
42898
+ RED.events.emit('editor:change')
42265
42899
  },250)
42266
42900
  } else {
42267
42901
  if (stack.length > 0) {
@@ -42331,6 +42965,7 @@ RED.eventLog = (function() {
42331
42965
  RED.view.focus();
42332
42966
  } else {
42333
42967
  stack[stack.length-1].tray.css("z-index", "auto");
42968
+ RED.events.emit('editor:change')
42334
42969
  }
42335
42970
  },250)
42336
42971
  }
@@ -45684,10 +46319,16 @@ RED.search = (function() {
45684
46319
  onselect: 'core:split-wire-with-link-nodes',
45685
46320
  disabled: !canEdit || !hasLinks
45686
46321
  },
45687
- null,
45688
- { onselect: 'core:show-import-dialog', label: RED._('common.label.import')},
45689
- { onselect: 'core:show-examples-import-dialog', label: RED._('menu.label.importExample') }
46322
+ null
45690
46323
  )
46324
+ if (RED.settings.theme("menu.menu-item-import-library", true)) {
46325
+ insertOptions.push(
46326
+ { onselect: 'core:show-import-dialog', label: RED._('common.label.import')},
46327
+ { onselect: 'core:show-examples-import-dialog', label: RED._('menu.label.importExample') }
46328
+ )
46329
+ }
46330
+
46331
+
45691
46332
  if (hasSelection && canEdit) {
45692
46333
  const nodeOptions = []
45693
46334
  if (!hasMultipleSelection && !isGroup) {
@@ -45760,8 +46401,14 @@ RED.search = (function() {
45760
46401
  { onselect: 'core:paste-from-internal-clipboard', label: RED._("keyboard.pasteNode"), disabled: !canEdit || !RED.view.clipboard() },
45761
46402
  { onselect: 'core:delete-selection', label: RED._('keyboard.deleteSelected'), disabled: !canEdit || !canDelete },
45762
46403
  { onselect: 'core:delete-selection-and-reconnect', label: RED._('keyboard.deleteReconnect'), disabled: !canEdit || !canDelete },
45763
- { onselect: 'core:show-export-dialog', label: RED._("menu.label.export") },
45764
- { onselect: 'core:select-all-nodes', label: RED._("keyboard.selectAll") },
46404
+ )
46405
+ if (RED.settings.theme("menu.menu-item-export-library", true)) {
46406
+ menuItems.push(
46407
+ { onselect: 'core:show-export-dialog', label: RED._("menu.label.export") }
46408
+ )
46409
+ }
46410
+ menuItems.push(
46411
+ { onselect: 'core:select-all-nodes', label: RED._("keyboard.selectAll") }
45765
46412
  )
45766
46413
  }
45767
46414
 
@@ -47772,14 +48419,20 @@ RED.subflow = (function() {
47772
48419
  var nodePropValue = nodeProp;
47773
48420
  if (prop.ui && prop.ui.type === "cred") {
47774
48421
  nodePropType = "cred";
48422
+ } else if (prop.ui && prop.ui.type === "conf-types") {
48423
+ nodePropType = prop.value.type
47775
48424
  } else {
47776
48425
  switch(typeof nodeProp) {
47777
48426
  case "string": nodePropType = "str"; break;
47778
48427
  case "number": nodePropType = "num"; break;
47779
48428
  case "boolean": nodePropType = "bool"; nodePropValue = nodeProp?"true":"false"; break;
47780
48429
  default:
47781
- nodePropType = nodeProp.type;
47782
- nodePropValue = nodeProp.value;
48430
+ if (nodeProp) {
48431
+ nodePropType = nodeProp.type;
48432
+ nodePropValue = nodeProp.value;
48433
+ } else {
48434
+ nodePropType = 'str'
48435
+ }
47783
48436
  }
47784
48437
  }
47785
48438
  var item = {