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

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 (143) hide show
  1. package/locales/de/editor.json +2 -0
  2. package/locales/en-US/editor.json +9 -1
  3. package/locales/fr/editor.json +17 -3
  4. package/locales/ja/editor.json +18 -4
  5. package/package.json +1 -1
  6. package/public/red/about +70 -0
  7. package/public/red/red.js +1602 -425
  8. package/public/red/red.min.js +3 -3
  9. package/public/red/style.min.css +2 -2
  10. package/public/red/tours/3.1/welcome.js +8 -8
  11. package/public/red/tours/images/nr4-background-deploy.png +0 -0
  12. package/public/red/tours/images/nr4-config-select.png +0 -0
  13. package/public/red/tours/images/nr4-diff-update.png +0 -0
  14. package/public/red/tours/images/nr4-multiplayer-location.png +0 -0
  15. package/public/red/tours/images/nr4-multiplayer.png +0 -0
  16. package/public/red/tours/images/nr4-plugins.png +0 -0
  17. package/public/red/tours/welcome.js +166 -11
  18. package/public/types/node/assert/strict.d.ts +1 -1
  19. package/public/types/node/assert.d.ts +8 -9
  20. package/public/types/node/async_hooks.d.ts +9 -5
  21. package/public/types/node/buffer.d.ts +43 -18
  22. package/public/types/node/child_process.d.ts +8 -5
  23. package/public/types/node/cluster.d.ts +15 -19
  24. package/public/types/node/console.d.ts +2 -2
  25. package/public/types/node/crypto.d.ts +165 -70
  26. package/public/types/node/dgram.d.ts +4 -4
  27. package/public/types/node/diagnostics_channel.d.ts +8 -7
  28. package/public/types/node/dns/promises.d.ts +11 -9
  29. package/public/types/node/dns.d.ts +18 -13
  30. package/public/types/node/dom-events.d.ts +129 -0
  31. package/public/types/node/domain.d.ts +2 -2
  32. package/public/types/node/events.d.ts +49 -12
  33. package/public/types/node/fs/promises.d.ts +68 -24
  34. package/public/types/node/fs.d.ts +132 -59
  35. package/public/types/node/globals.d.ts +31 -17
  36. package/public/types/node/http.d.ts +138 -27
  37. package/public/types/node/http2.d.ts +38 -5
  38. package/public/types/node/https.d.ts +12 -3
  39. package/public/types/node/module.d.ts +1 -2
  40. package/public/types/node/net.d.ts +69 -28
  41. package/public/types/node/os.d.ts +16 -5
  42. package/public/types/node/path.d.ts +5 -5
  43. package/public/types/node/perf_hooks.d.ts +48 -9
  44. package/public/types/node/process.d.ts +18 -17
  45. package/public/types/node/querystring.d.ts +2 -2
  46. package/public/types/node/readline/promises.d.ts +146 -0
  47. package/public/types/node/readline.d.ts +141 -31
  48. package/public/types/node/stream/consumers.d.ts +2 -2
  49. package/public/types/node/stream/promises.d.ts +1 -1
  50. package/public/types/node/stream/web.d.ts +4 -66
  51. package/public/types/node/stream.d.ts +96 -118
  52. package/public/types/node/string_decoder.d.ts +2 -2
  53. package/public/types/node/test.d.ts +200 -16
  54. package/public/types/node/timers/promises.d.ts +1 -26
  55. package/public/types/node/timers.d.ts +2 -2
  56. package/public/types/node/tls.d.ts +21 -12
  57. package/public/types/node/trace_events.d.ts +12 -2
  58. package/public/types/node/ts4.8/assert/strict.d.ts +11 -0
  59. package/public/types/node/ts4.8/assert.d.ts +964 -0
  60. package/public/types/node/ts4.8/async_hooks.d.ts +504 -0
  61. package/public/types/node/ts4.8/buffer.d.ts +2262 -0
  62. package/public/types/node/ts4.8/child_process.d.ts +1372 -0
  63. package/public/types/node/ts4.8/cluster.d.ts +413 -0
  64. package/public/types/node/ts4.8/console.d.ts +415 -0
  65. package/public/types/node/ts4.8/crypto.d.ts +3967 -0
  66. package/public/types/node/ts4.8/dgram.d.ts +548 -0
  67. package/public/types/node/ts4.8/diagnostics_channel.d.ts +156 -0
  68. package/public/types/node/ts4.8/dns/promises.d.ts +373 -0
  69. package/public/types/node/ts4.8/dns.d.ts +662 -0
  70. package/public/types/node/ts4.8/dom-events.d.ts +129 -0
  71. package/public/types/node/ts4.8/domain.d.ts +173 -0
  72. package/public/types/node/ts4.8/events.d.ts +681 -0
  73. package/public/types/node/ts4.8/fs/promises.d.ts +1141 -0
  74. package/public/types/node/ts4.8/fs.d.ts +3875 -0
  75. package/public/types/node/ts4.8/globals.d.ts +297 -0
  76. package/public/types/node/ts4.8/http.d.ts +1617 -0
  77. package/public/types/node/ts4.8/http2.d.ts +2137 -0
  78. package/public/types/node/ts4.8/https.d.ts +544 -0
  79. package/public/types/node/ts4.8/module.d.ts +117 -0
  80. package/public/types/node/ts4.8/net.d.ts +872 -0
  81. package/public/types/node/ts4.8/os.d.ts +469 -0
  82. package/public/types/node/ts4.8/path.d.ts +194 -0
  83. package/public/types/node/ts4.8/perf_hooks.d.ts +628 -0
  84. package/public/types/node/ts4.8/process.d.ts +1485 -0
  85. package/public/types/node/ts4.8/querystring.d.ts +134 -0
  86. package/public/types/node/ts4.8/readline/promises.d.ts +146 -0
  87. package/public/types/node/ts4.8/readline.d.ts +656 -0
  88. package/public/types/node/ts4.8/stream/consumers.d.ts +15 -0
  89. package/public/types/node/ts4.8/stream/promises.d.ts +45 -0
  90. package/public/types/node/ts4.8/stream/web.d.ts +333 -0
  91. package/public/types/node/ts4.8/stream.d.ts +1343 -0
  92. package/public/types/node/ts4.8/string_decoder.d.ts +70 -0
  93. package/public/types/node/ts4.8/test.d.ts +377 -0
  94. package/public/types/node/ts4.8/timers/promises.d.ts +71 -0
  95. package/public/types/node/ts4.8/timers.d.ts +97 -0
  96. package/public/types/node/ts4.8/tls.d.ts +1031 -0
  97. package/public/types/node/ts4.8/trace_events.d.ts +174 -0
  98. package/public/types/node/ts4.8/tty.d.ts +209 -0
  99. package/public/types/node/ts4.8/url.d.ts +900 -0
  100. package/public/types/node/ts4.8/util.d.ts +1853 -0
  101. package/public/types/node/ts4.8/v8.d.ts +399 -0
  102. package/public/types/node/ts4.8/vm.d.ts +512 -0
  103. package/public/types/node/ts4.8/wasi.d.ts +161 -0
  104. package/public/types/node/ts4.8/worker_threads.d.ts +692 -0
  105. package/public/types/node/ts4.8/zlib.d.ts +520 -0
  106. package/public/types/node/tty.d.ts +5 -3
  107. package/public/types/node/url.d.ts +81 -39
  108. package/public/types/node/util.d.ts +269 -13
  109. package/public/types/node/v8.d.ts +22 -4
  110. package/public/types/node/vm.d.ts +7 -5
  111. package/public/types/node/wasi.d.ts +2 -2
  112. package/public/types/node/worker_threads.d.ts +51 -11
  113. package/public/types/node/zlib.d.ts +2 -2
  114. package/public/types/node-red/func.d.ts +26 -17
  115. package/public/types/node-red/util.d.ts +1 -1
  116. package/public/vendor/ace/worker-jsonata.js +1 -1
  117. package/public/vendor/monaco/dist/{fa2cc0ab9f0bec2b3365.ttf → 0c718f5b7d2bce997c5f.ttf} +0 -0
  118. package/public/vendor/monaco/dist/css.worker.js +1 -1
  119. package/public/vendor/monaco/dist/css.worker.js.LICENSE.txt +1 -1
  120. package/public/vendor/monaco/dist/editor.js +1 -29
  121. package/public/vendor/monaco/dist/editor.js.LICENSE.txt +2 -2
  122. package/public/vendor/monaco/dist/editor.worker.js +1 -1
  123. package/public/vendor/monaco/dist/html.worker.js +1 -1
  124. package/public/vendor/monaco/dist/html.worker.js.LICENSE.txt +1 -1
  125. package/public/vendor/monaco/dist/json.worker.js +1 -1
  126. package/public/vendor/monaco/dist/json.worker.js.LICENSE.txt +1 -1
  127. package/public/vendor/monaco/dist/locale/cs.js +324 -106
  128. package/public/vendor/monaco/dist/locale/de.js +336 -118
  129. package/public/vendor/monaco/dist/locale/es.js +329 -111
  130. package/public/vendor/monaco/dist/locale/fr.js +334 -116
  131. package/public/vendor/monaco/dist/locale/it.js +327 -109
  132. package/public/vendor/monaco/dist/locale/ja.js +329 -111
  133. package/public/vendor/monaco/dist/locale/ko.js +330 -112
  134. package/public/vendor/monaco/dist/locale/pl.js +329 -111
  135. package/public/vendor/monaco/dist/locale/pt-br.js +329 -111
  136. package/public/vendor/monaco/dist/locale/qps-ploc.js +330 -112
  137. package/public/vendor/monaco/dist/locale/ru.js +331 -113
  138. package/public/vendor/monaco/dist/locale/tr.js +329 -111
  139. package/public/vendor/monaco/dist/locale/zh-hans.js +331 -113
  140. package/public/vendor/monaco/dist/locale/zh-hant.js +331 -113
  141. package/public/vendor/monaco/dist/ts.worker.js +2 -2
  142. package/public/vendor/vendor.js +1 -1
  143. 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() {
@@ -388,6 +389,7 @@ var RED = (function() {
388
389
  RED.workspaces.show(workspaces[0]);
389
390
  }
390
391
  }
392
+ RED.events.emit('flows:loaded')
391
393
  } catch(err) {
392
394
  console.warn(err);
393
395
  RED.notify(
@@ -625,6 +627,41 @@ var RED = (function() {
625
627
  RED.view.redrawStatus(node);
626
628
  }
627
629
  });
630
+ RED.comms.subscribe("notification/plugin/#",function(topic,msg) {
631
+ if (topic == "notification/plugin/added") {
632
+ RED.settings.refreshSettings(function(err, data) {
633
+ let addedPlugins = [];
634
+ msg.forEach(function(m) {
635
+ let id = m.id;
636
+ RED.plugins.addPlugin(m);
637
+
638
+ m.plugins.forEach((p) => {
639
+ addedPlugins.push(p.id);
640
+ })
641
+
642
+ RED.i18n.loadNodeCatalog(id, function() {
643
+ var lang = localStorage.getItem("editor-language")||RED.i18n.detectLanguage();
644
+ $.ajax({
645
+ headers: {
646
+ "Accept":"text/html",
647
+ "Accept-Language": lang
648
+ },
649
+ cache: false,
650
+ url: 'plugins/'+id,
651
+ success: function(data) {
652
+ appendPluginConfig(data);
653
+ }
654
+ });
655
+ });
656
+ });
657
+ if (addedPlugins.length) {
658
+ let pluginList = "<ul><li>"+addedPlugins.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>";
659
+ // ToDo: Adapt notification (node -> plugin)
660
+ RED.notify(RED._("palette.event.nodeAdded", {count:addedPlugins.length})+pluginList,"success");
661
+ }
662
+ })
663
+ }
664
+ });
628
665
 
629
666
  let pendingNodeRemovedNotifications = []
630
667
  let pendingNodeRemovedTimeout
@@ -894,6 +931,10 @@ var RED = (function() {
894
931
 
895
932
  RED.nodes.init();
896
933
  RED.runtime.init()
934
+
935
+ if (RED.settings.theme("multiplayer.enabled",false)) {
936
+ RED.multiplayer.init()
937
+ }
897
938
  RED.comms.connect();
898
939
 
899
940
  $("#red-ui-main-container").show();
@@ -1812,6 +1853,7 @@ RED.user = (function() {
1812
1853
  }
1813
1854
 
1814
1855
  function logout() {
1856
+ RED.events.emit('logout')
1815
1857
  var tokens = RED.settings.get("auth-tokens");
1816
1858
  var token = tokens?tokens.access_token:"";
1817
1859
  $.ajax({
@@ -1836,6 +1878,8 @@ RED.user = (function() {
1836
1878
 
1837
1879
  function updateUserMenu() {
1838
1880
  $("#red-ui-header-button-user-submenu li").remove();
1881
+ const userMenu = $("#red-ui-header-button-user")
1882
+ userMenu.empty()
1839
1883
  if (RED.settings.user.anonymous) {
1840
1884
  RED.menu.addItem("red-ui-header-button-user",{
1841
1885
  id:"usermenu-item-login",
@@ -1863,7 +1907,8 @@ RED.user = (function() {
1863
1907
  }
1864
1908
  });
1865
1909
  }
1866
-
1910
+ const userIcon = generateUserIcon(RED.settings.user)
1911
+ userIcon.appendTo(userMenu);
1867
1912
  }
1868
1913
 
1869
1914
  function init() {
@@ -1872,14 +1917,6 @@ RED.user = (function() {
1872
1917
 
1873
1918
  var userMenu = $('<li><a id="red-ui-header-button-user" class="button hide" href="#"></a></li>')
1874
1919
  .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
1920
  RED.menu.init({id:"red-ui-header-button-user",
1884
1921
  options: []
1885
1922
  });
@@ -1942,12 +1979,30 @@ RED.user = (function() {
1942
1979
  return false;
1943
1980
  }
1944
1981
 
1982
+ function generateUserIcon(user) {
1983
+ const userIcon = $('<span class="red-ui-user-profile"></span>')
1984
+ if (user.image) {
1985
+ userIcon.addClass('has_profile_image')
1986
+ userIcon.css({
1987
+ backgroundImage: "url("+user.image+")",
1988
+ })
1989
+ } else if (user.anonymous) {
1990
+ $('<i class="fa fa-user"></i>').appendTo(userIcon);
1991
+ } else {
1992
+ $('<span>').text(user.username.substring(0,2)).appendTo(userIcon);
1993
+ }
1994
+ if (user.profileColor !== undefined) {
1995
+ userIcon.addClass('red-ui-user-profile-color-' + user.profileColor)
1996
+ }
1997
+ return userIcon
1998
+ }
1945
1999
 
1946
2000
  return {
1947
2001
  init: init,
1948
2002
  login: login,
1949
2003
  logout: logout,
1950
- hasPermission: hasPermission
2004
+ hasPermission: hasPermission,
2005
+ generateUserIcon
1951
2006
  }
1952
2007
 
1953
2008
  })();
@@ -1979,6 +2034,15 @@ RED.comms = (function() {
1979
2034
  var reconnectAttempts = 0;
1980
2035
  var active = false;
1981
2036
 
2037
+ RED.events.on('login', function(username) {
2038
+ // User has logged in
2039
+ // Need to upgrade the connection to be authenticated
2040
+ if (ws && ws.readyState == 1) {
2041
+ const auth_tokens = RED.settings.get("auth-tokens");
2042
+ ws.send(JSON.stringify({auth:auth_tokens.access_token}))
2043
+ }
2044
+ })
2045
+
1982
2046
  function connectWS() {
1983
2047
  active = true;
1984
2048
  var wspath;
@@ -2009,6 +2073,7 @@ RED.comms = (function() {
2009
2073
  ws.send(JSON.stringify({subscribe:t}));
2010
2074
  }
2011
2075
  }
2076
+ emit('connect')
2012
2077
  }
2013
2078
 
2014
2079
  ws = new WebSocket(wspath);
@@ -2133,10 +2198,54 @@ RED.comms = (function() {
2133
2198
  }
2134
2199
  }
2135
2200
 
2201
+ function send(topic, msg) {
2202
+ if (ws && ws.readyState == 1) {
2203
+ ws.send(JSON.stringify({
2204
+ topic,
2205
+ data: msg
2206
+ }))
2207
+ }
2208
+ }
2209
+
2210
+ const eventHandlers = {};
2211
+ function on(evt,func) {
2212
+ eventHandlers[evt] = eventHandlers[evt]||[];
2213
+ eventHandlers[evt].push(func);
2214
+ }
2215
+ function off(evt,func) {
2216
+ const handler = eventHandlers[evt];
2217
+ if (handler) {
2218
+ for (let i=0;i<handler.length;i++) {
2219
+ if (handler[i] === func) {
2220
+ handler.splice(i,1);
2221
+ return;
2222
+ }
2223
+ }
2224
+ }
2225
+ }
2226
+ function emit() {
2227
+ const evt = arguments[0]
2228
+ const args = Array.prototype.slice.call(arguments,1);
2229
+ if (eventHandlers[evt]) {
2230
+ let cpyHandlers = [...eventHandlers[evt]];
2231
+ for (let i=0;i<cpyHandlers.length;i++) {
2232
+ try {
2233
+ cpyHandlers[i].apply(null, args);
2234
+ } catch(err) {
2235
+ console.warn("RED.comms.emit error: ["+evt+"] "+(err.toString()));
2236
+ console.warn(err);
2237
+ }
2238
+ }
2239
+ }
2240
+ }
2241
+
2136
2242
  return {
2137
2243
  connect: connectWS,
2138
2244
  subscribe: subscribe,
2139
- unsubscribe:unsubscribe
2245
+ unsubscribe:unsubscribe,
2246
+ on,
2247
+ off,
2248
+ send
2140
2249
  }
2141
2250
  })();
2142
2251
  ;RED.runtime = (function() {
@@ -2175,6 +2284,496 @@ RED.comms = (function() {
2175
2284
  }
2176
2285
  }
2177
2286
  })()
2287
+ ;RED.multiplayer = (function () {
2288
+
2289
+ // activeSessionId - used to identify sessions across websocket reconnects
2290
+ let activeSessionId
2291
+
2292
+ let headerWidget
2293
+ // Map of session id to { session:'', user:{}, location:{}}
2294
+ let sessions = {}
2295
+ // Map of username to { user:{}, sessions:[] }
2296
+ let users = {}
2297
+
2298
+ function addUserSession (session) {
2299
+ if (sessions[session.session]) {
2300
+ // This is an existing connection that has been authenticated
2301
+ const existingSession = sessions[session.session]
2302
+ if (existingSession.user.username !== session.user.username) {
2303
+ removeUserHeaderButton(users[existingSession.user.username])
2304
+ }
2305
+ }
2306
+ sessions[session.session] = session
2307
+ const user = users[session.user.username] = users[session.user.username] || {
2308
+ user: session.user,
2309
+ sessions: []
2310
+ }
2311
+ if (session.user.profileColor === undefined) {
2312
+ session.user.profileColor = (1 + Math.floor(Math.random() * 5))
2313
+ }
2314
+ session.location = session.location || {}
2315
+ user.sessions.push(session)
2316
+
2317
+ if (session.session === activeSessionId) {
2318
+ // This is the current user session - do not add a extra button for them
2319
+ } else {
2320
+ if (user.sessions.length === 1) {
2321
+ if (user.button) {
2322
+ clearTimeout(user.inactiveTimeout)
2323
+ clearTimeout(user.removeTimeout)
2324
+ user.button.removeClass('inactive')
2325
+ } else {
2326
+ addUserHeaderButton(user)
2327
+ }
2328
+ }
2329
+ sessions[session.session].location = session.location
2330
+ updateUserLocation(session.session)
2331
+ }
2332
+ }
2333
+
2334
+ function removeUserSession (sessionId, isDisconnected) {
2335
+ removeUserLocation(sessionId)
2336
+ const session = sessions[sessionId]
2337
+ delete sessions[sessionId]
2338
+ const user = users[session.user.username]
2339
+ const i = user.sessions.indexOf(session)
2340
+ user.sessions.splice(i, 1)
2341
+ if (isDisconnected) {
2342
+ removeUserHeaderButton(user)
2343
+ } else {
2344
+ if (user.sessions.length === 0) {
2345
+ // Give the user 5s to reconnect before marking inactive
2346
+ user.inactiveTimeout = setTimeout(() => {
2347
+ user.button.addClass('inactive')
2348
+ // Give the user further 20 seconds to reconnect before removing them
2349
+ // from the user toolbar entirely
2350
+ user.removeTimeout = setTimeout(() => {
2351
+ removeUserHeaderButton(user)
2352
+ }, 20000)
2353
+ }, 5000)
2354
+ }
2355
+ }
2356
+ }
2357
+
2358
+ function addUserHeaderButton (user) {
2359
+ user.button = $('<li class="red-ui-multiplayer-user"><button type="button" class="red-ui-multiplayer-user-icon"></button></li>')
2360
+ .attr('data-username', user.user.username)
2361
+ .prependTo("#red-ui-multiplayer-user-list");
2362
+ var button = user.button.find("button")
2363
+ RED.popover.tooltip(button, user.user.username)
2364
+ button.on('click', function () {
2365
+ const location = user.sessions[0].location
2366
+ revealUser(location)
2367
+ })
2368
+
2369
+ const userProfile = RED.user.generateUserIcon(user.user)
2370
+ userProfile.appendTo(button)
2371
+ }
2372
+
2373
+ function removeUserHeaderButton (user) {
2374
+ user.button.remove()
2375
+ delete user.button
2376
+ }
2377
+
2378
+ function getLocation () {
2379
+ const location = {
2380
+ workspace: RED.workspaces.active()
2381
+ }
2382
+ const editStack = RED.editor.getEditStack()
2383
+ for (let i = editStack.length - 1; i >= 0; i--) {
2384
+ if (editStack[i].id) {
2385
+ location.node = editStack[i].id
2386
+ break
2387
+ }
2388
+ }
2389
+ return location
2390
+ }
2391
+ function publishLocation () {
2392
+ const location = getLocation()
2393
+ if (location.workspace !== 0) {
2394
+ log('send', 'multiplayer/location', location)
2395
+ RED.comms.send('multiplayer/location', location)
2396
+ }
2397
+ }
2398
+
2399
+ function revealUser(location, skipWorkspace) {
2400
+ if (location.node) {
2401
+ // Need to check if this is a known node, so we can fall back to revealing
2402
+ // the workspace instead
2403
+ const node = RED.nodes.node(location.node)
2404
+ if (node) {
2405
+ RED.view.reveal(location.node)
2406
+ } else if (!skipWorkspace && location.workspace) {
2407
+ RED.view.reveal(location.workspace)
2408
+ }
2409
+ } else if (!skipWorkspace && location.workspace) {
2410
+ RED.view.reveal(location.workspace)
2411
+ }
2412
+ }
2413
+
2414
+ const workspaceTrays = {}
2415
+ function getWorkspaceTray(workspaceId) {
2416
+ // console.log('get tray for',workspaceId)
2417
+ if (!workspaceTrays[workspaceId]) {
2418
+ const tray = $('<div class="red-ui-multiplayer-users-tray"></div>')
2419
+ const users = []
2420
+ const userIcons = {}
2421
+
2422
+ const userCountIcon = $(`<div class="red-ui-multiplayer-user-location"><span class="red-ui-user-profile red-ui-multiplayer-user-count"><span></span></span></div>`)
2423
+ const userCountSpan = userCountIcon.find('span span')
2424
+ userCountIcon.hide()
2425
+ userCountSpan.text('')
2426
+ userCountIcon.appendTo(tray)
2427
+ const userCountTooltip = RED.popover.tooltip(userCountIcon, function () {
2428
+ const content = $('<div>')
2429
+ users.forEach(sessionId => {
2430
+ $('<div>').append($('<a href="#">').text(sessions[sessionId].user.username).on('click', function (evt) {
2431
+ evt.preventDefault()
2432
+ revealUser(sessions[sessionId].location, true)
2433
+ userCountTooltip.close()
2434
+ })).appendTo(content)
2435
+ })
2436
+ return content
2437
+ },
2438
+ null,
2439
+ true
2440
+ )
2441
+
2442
+ const updateUserCount = function () {
2443
+ const maxShown = 2
2444
+ const children = tray.children()
2445
+ children.each(function (index, element) {
2446
+ const i = users.length - index
2447
+ if (i > maxShown) {
2448
+ $(this).hide()
2449
+ } else if (i >= 0) {
2450
+ $(this).show()
2451
+ }
2452
+ })
2453
+ if (users.length < maxShown + 1) {
2454
+ userCountIcon.hide()
2455
+ } else {
2456
+ userCountSpan.text('+'+(users.length - maxShown))
2457
+ userCountIcon.show()
2458
+ }
2459
+ }
2460
+ workspaceTrays[workspaceId] = {
2461
+ attached: false,
2462
+ tray,
2463
+ users,
2464
+ userIcons,
2465
+ addUser: function (sessionId) {
2466
+ if (users.indexOf(sessionId) === -1) {
2467
+ // console.log(`addUser ws:${workspaceId} session:${sessionId}`)
2468
+ users.push(sessionId)
2469
+ const userLocationId = `red-ui-multiplayer-user-location-${sessionId}`
2470
+ const userLocationIcon = $(`<div class="red-ui-multiplayer-user-location" id="${userLocationId}"></div>`)
2471
+ RED.user.generateUserIcon(sessions[sessionId].user).appendTo(userLocationIcon)
2472
+ userLocationIcon.prependTo(tray)
2473
+ RED.popover.tooltip(userLocationIcon, sessions[sessionId].user.username)
2474
+ userIcons[sessionId] = userLocationIcon
2475
+ updateUserCount()
2476
+ }
2477
+ },
2478
+ removeUser: function (sessionId) {
2479
+ // console.log(`removeUser ws:${workspaceId} session:${sessionId}`)
2480
+ const userLocationId = `red-ui-multiplayer-user-location-${sessionId}`
2481
+ const index = users.indexOf(sessionId)
2482
+ if (index > -1) {
2483
+ users.splice(index, 1)
2484
+ userIcons[sessionId].remove()
2485
+ delete userIcons[sessionId]
2486
+ }
2487
+ updateUserCount()
2488
+ },
2489
+ updateUserCount
2490
+ }
2491
+ }
2492
+ const trayDef = workspaceTrays[workspaceId]
2493
+ if (!trayDef.attached) {
2494
+ const workspaceTab = $(`#red-ui-tab-${workspaceId}`)
2495
+ if (workspaceTab.length > 0) {
2496
+ trayDef.attached = true
2497
+ trayDef.tray.appendTo(workspaceTab)
2498
+ trayDef.users.forEach(sessionId => {
2499
+ trayDef.userIcons[sessionId].on('click', function (evt) {
2500
+ revealUser(sessions[sessionId].location, true)
2501
+ })
2502
+ })
2503
+ }
2504
+ }
2505
+ return workspaceTrays[workspaceId]
2506
+ }
2507
+ function attachWorkspaceTrays () {
2508
+ let viewTouched = false
2509
+ for (let sessionId of Object.keys(sessions)) {
2510
+ const location = sessions[sessionId].location
2511
+ if (location) {
2512
+ if (location.workspace) {
2513
+ getWorkspaceTray(location.workspace).updateUserCount()
2514
+ }
2515
+ if (location.node) {
2516
+ addUserToNode(sessionId, location.node)
2517
+ viewTouched = true
2518
+ }
2519
+ }
2520
+ }
2521
+ if (viewTouched) {
2522
+ RED.view.redraw()
2523
+ }
2524
+ }
2525
+
2526
+ function addUserToNode(sessionId, nodeId) {
2527
+ const node = RED.nodes.node(nodeId)
2528
+ if (node) {
2529
+ if (!node._multiplayer) {
2530
+ node._multiplayer = {
2531
+ users: [sessionId]
2532
+ }
2533
+ node._multiplayer_refresh = true
2534
+ } else {
2535
+ if (node._multiplayer.users.indexOf(sessionId) === -1) {
2536
+ node._multiplayer.users.push(sessionId)
2537
+ node._multiplayer_refresh = true
2538
+ }
2539
+ }
2540
+ }
2541
+ }
2542
+ function removeUserFromNode(sessionId, nodeId) {
2543
+ const node = RED.nodes.node(nodeId)
2544
+ if (node && node._multiplayer) {
2545
+ const i = node._multiplayer.users.indexOf(sessionId)
2546
+ if (i > -1) {
2547
+ node._multiplayer.users.splice(i, 1)
2548
+ }
2549
+ if (node._multiplayer.users.length === 0) {
2550
+ delete node._multiplayer
2551
+ } else {
2552
+ node._multiplayer_refresh = true
2553
+ }
2554
+ }
2555
+
2556
+ }
2557
+
2558
+ function removeUserLocation (sessionId) {
2559
+ updateUserLocation(sessionId, {})
2560
+ }
2561
+ function updateUserLocation (sessionId, location) {
2562
+ let viewTouched = false
2563
+ const oldLocation = sessions[sessionId].location
2564
+ if (location) {
2565
+ if (oldLocation.workspace !== location.workspace) {
2566
+ // console.log('removing', sessionId, oldLocation.workspace)
2567
+ workspaceTrays[oldLocation.workspace]?.removeUser(sessionId)
2568
+ }
2569
+ if (oldLocation.node !== location.node) {
2570
+ removeUserFromNode(sessionId, oldLocation.node)
2571
+ viewTouched = true
2572
+ }
2573
+ sessions[sessionId].location = location
2574
+ } else {
2575
+ location = sessions[sessionId].location
2576
+ }
2577
+ // console.log(`updateUserLocation sessionId:${sessionId} oldWS:${oldLocation?.workspace} newWS:${location.workspace}`)
2578
+ if (location.workspace) {
2579
+ getWorkspaceTray(location.workspace).addUser(sessionId)
2580
+ }
2581
+ if (location.node) {
2582
+ addUserToNode(sessionId, location.node)
2583
+ viewTouched = true
2584
+ }
2585
+ if (viewTouched) {
2586
+ RED.view.redraw()
2587
+ }
2588
+ }
2589
+
2590
+ // function refreshUserLocations () {
2591
+ // for (const session of Object.keys(sessions)) {
2592
+ // if (session !== activeSessionId) {
2593
+ // updateUserLocation(session)
2594
+ // }
2595
+ // }
2596
+ // }
2597
+
2598
+ return {
2599
+ init: function () {
2600
+
2601
+ function createAnnotationUser(user) {
2602
+
2603
+ const group = document.createElementNS("http://www.w3.org/2000/svg","g");
2604
+ const badge = document.createElementNS("http://www.w3.org/2000/svg","circle");
2605
+ const radius = 20
2606
+ badge.setAttribute("cx",radius/2);
2607
+ badge.setAttribute("cy",radius/2);
2608
+ badge.setAttribute("r",radius/2);
2609
+ badge.setAttribute("class", "red-ui-multiplayer-annotation-background")
2610
+ group.appendChild(badge)
2611
+ if (user && user.profileColor !== undefined) {
2612
+ badge.setAttribute("class", "red-ui-multiplayer-annotation-background red-ui-user-profile-color-" + user.profileColor)
2613
+ }
2614
+ if (user && user.image) {
2615
+ const image = document.createElementNS("http://www.w3.org/2000/svg","image");
2616
+ image.setAttribute("width", radius)
2617
+ image.setAttribute("height", radius)
2618
+ image.setAttribute("href", user.image)
2619
+ image.setAttribute("clip-path", "circle("+Math.floor(radius/2)+")")
2620
+ group.appendChild(image)
2621
+ } else if (user && user.anonymous) {
2622
+ const anonIconHead = document.createElementNS("http://www.w3.org/2000/svg","circle");
2623
+ anonIconHead.setAttribute("cx", radius/2)
2624
+ anonIconHead.setAttribute("cy", radius/2 - 2)
2625
+ anonIconHead.setAttribute("r", 2.4)
2626
+ anonIconHead.setAttribute("class","red-ui-multiplayer-annotation-anon-label");
2627
+ group.appendChild(anonIconHead)
2628
+ const anonIconBody = document.createElementNS("http://www.w3.org/2000/svg","path");
2629
+ anonIconBody.setAttribute("class","red-ui-multiplayer-annotation-anon-label");
2630
+ // anonIconBody.setAttribute("d",`M ${radius/2 - 4} ${radius/2 + 1} h 8 v4 h -8 z`);
2631
+ anonIconBody.setAttribute("d",`M ${radius/2} ${radius/2 + 5} h -2.5 c -2 1 -2 -5 0.5 -4.5 c 2 1 2 1 4 0 c 2.5 -0.5 2.5 5.5 0 4.5 z`);
2632
+ group.appendChild(anonIconBody)
2633
+ } else {
2634
+ const labelText = user.username ? user.username.substring(0,2) : user
2635
+ const label = document.createElementNS("http://www.w3.org/2000/svg","text");
2636
+ if (user.username) {
2637
+ label.setAttribute("class","red-ui-multiplayer-annotation-label");
2638
+ label.textContent = user.username.substring(0,2)
2639
+ } else {
2640
+ label.setAttribute("class","red-ui-multiplayer-annotation-label red-ui-multiplayer-user-count")
2641
+ label.textContent = user
2642
+ }
2643
+ label.setAttribute("text-anchor", "middle")
2644
+ label.setAttribute("x",radius/2);
2645
+ label.setAttribute("y",radius/2 + 3);
2646
+ group.appendChild(label)
2647
+ }
2648
+ const border = document.createElementNS("http://www.w3.org/2000/svg","circle");
2649
+ border.setAttribute("cx",radius/2);
2650
+ border.setAttribute("cy",radius/2);
2651
+ border.setAttribute("r",radius/2);
2652
+ border.setAttribute("class", "red-ui-multiplayer-annotation-border")
2653
+ group.appendChild(border)
2654
+
2655
+
2656
+
2657
+ return group
2658
+ }
2659
+
2660
+ RED.view.annotations.register("red-ui-multiplayer",{
2661
+ type: 'badge',
2662
+ align: 'left',
2663
+ class: "red-ui-multiplayer-annotation",
2664
+ show: "_multiplayer",
2665
+ refresh: "_multiplayer_refresh",
2666
+ element: function(node) {
2667
+ const containerGroup = document.createElementNS("http://www.w3.org/2000/svg","g");
2668
+ containerGroup.setAttribute("transform","translate(0,-4)")
2669
+ if (node._multiplayer) {
2670
+ let y = 0
2671
+ for (let i = Math.min(1, node._multiplayer.users.length - 1); i >= 0; i--) {
2672
+ const user = sessions[node._multiplayer.users[i]].user
2673
+ const group = createAnnotationUser(user)
2674
+ group.setAttribute("transform","translate("+y+",0)")
2675
+ y += 15
2676
+ containerGroup.appendChild(group)
2677
+ }
2678
+ if (node._multiplayer.users.length > 2) {
2679
+ const group = createAnnotationUser('+'+(node._multiplayer.users.length - 2))
2680
+ group.setAttribute("transform","translate("+y+",0)")
2681
+ y += 12
2682
+ containerGroup.appendChild(group)
2683
+ }
2684
+
2685
+ }
2686
+ return containerGroup;
2687
+ },
2688
+ tooltip: node => { return node._multiplayer.users.map(u => sessions[u].user.username).join('\n') }
2689
+ });
2690
+
2691
+
2692
+ // activeSessionId = RED.settings.getLocal('multiplayer:sessionId')
2693
+ // if (!activeSessionId) {
2694
+ activeSessionId = RED.nodes.id()
2695
+ // RED.settings.setLocal('multiplayer:sessionId', activeSessionId)
2696
+ // log('Session ID (new)', activeSessionId)
2697
+ // } else {
2698
+ log('Session ID', activeSessionId)
2699
+ // }
2700
+
2701
+ headerWidget = $('<li><ul id="red-ui-multiplayer-user-list"></ul></li>').prependTo('.red-ui-header-toolbar')
2702
+
2703
+ RED.comms.on('connect', () => {
2704
+ const location = getLocation()
2705
+ const connectInfo = {
2706
+ session: activeSessionId
2707
+ }
2708
+ if (location.workspace !== 0) {
2709
+ connectInfo.location = location
2710
+ }
2711
+ RED.comms.send('multiplayer/connect', connectInfo)
2712
+ })
2713
+ RED.comms.subscribe('multiplayer/#', (topic, msg) => {
2714
+ log('recv', topic, msg)
2715
+ if (topic === 'multiplayer/init') {
2716
+ // We have just reconnected, runtime has sent state to
2717
+ // initialise the world
2718
+ sessions = {}
2719
+ users = {}
2720
+ $('#red-ui-multiplayer-user-list').empty()
2721
+
2722
+ msg.sessions.forEach(session => {
2723
+ addUserSession(session)
2724
+ })
2725
+ } else if (topic === 'multiplayer/connection-added') {
2726
+ addUserSession(msg)
2727
+ } else if (topic === 'multiplayer/connection-removed') {
2728
+ removeUserSession(msg.session, msg.disconnected)
2729
+ } else if (topic === 'multiplayer/location') {
2730
+ const session = msg.session
2731
+ delete msg.session
2732
+ updateUserLocation(session, msg)
2733
+ }
2734
+ })
2735
+
2736
+ RED.events.on('workspace:change', (event) => {
2737
+ getWorkspaceTray(event.workspace)
2738
+ publishLocation()
2739
+ })
2740
+ RED.events.on('editor:open', () => {
2741
+ publishLocation()
2742
+ })
2743
+ RED.events.on('editor:close', () => {
2744
+ publishLocation()
2745
+ })
2746
+ RED.events.on('editor:change', () => {
2747
+ publishLocation()
2748
+ })
2749
+ RED.events.on('login', () => {
2750
+ publishLocation()
2751
+ })
2752
+ RED.events.on('flows:loaded', () => {
2753
+ attachWorkspaceTrays()
2754
+ })
2755
+ RED.events.on('workspace:close', (event) => {
2756
+ // A subflow tab has been closed. Need to mark its tray as detached
2757
+ if (workspaceTrays[event.workspace]) {
2758
+ workspaceTrays[event.workspace].attached = false
2759
+ }
2760
+ })
2761
+ RED.events.on('logout', () => {
2762
+ const disconnectInfo = {
2763
+ session: activeSessionId
2764
+ }
2765
+ RED.comms.send('multiplayer/disconnect', disconnectInfo)
2766
+ RED.settings.removeLocal('multiplayer:sessionId')
2767
+ })
2768
+ }
2769
+ }
2770
+
2771
+ function log() {
2772
+ if (RED.multiplayer.DEBUG) {
2773
+ console.log('[multiplayer]', ...arguments)
2774
+ }
2775
+ }
2776
+ })();
2178
2777
  ;/**
2179
2778
  * Copyright JS Foundation and other contributors, http://js.foundation
2180
2779
  *
@@ -3672,6 +4271,7 @@ RED.state = {
3672
4271
  ;RED.plugins = (function() {
3673
4272
  var plugins = {};
3674
4273
  var pluginsByType = {};
4274
+ var moduleList = {};
3675
4275
 
3676
4276
  function registerPlugin(id,definition) {
3677
4277
  plugins[id] = definition;
@@ -3709,10 +4309,44 @@ RED.state = {
3709
4309
  function getPluginsByType(type) {
3710
4310
  return pluginsByType[type] || [];
3711
4311
  }
4312
+
4313
+ function setPluginList(list) {
4314
+ for(let i=0;i<list.length;i++) {
4315
+ let p = list[i];
4316
+ addPlugin(p);
4317
+ }
4318
+ }
4319
+
4320
+ function addPlugin(p) {
4321
+
4322
+ moduleList[p.module] = moduleList[p.module] || {
4323
+ name:p.module,
4324
+ version:p.version,
4325
+ local:p.local,
4326
+ sets:{},
4327
+ plugin: true,
4328
+ id: p.id
4329
+ };
4330
+ if (p.pending_version) {
4331
+ moduleList[p.module].pending_version = p.pending_version;
4332
+ }
4333
+ moduleList[p.module].sets[p.name] = p;
4334
+
4335
+ RED.events.emit("registry:plugin-module-added",p.module);
4336
+ }
4337
+
4338
+ function getModule(module) {
4339
+ return moduleList[module];
4340
+ }
4341
+
3712
4342
  return {
3713
4343
  registerPlugin: registerPlugin,
3714
4344
  getPlugin: getPlugin,
3715
- getPluginsByType: getPluginsByType
4345
+ getPluginsByType: getPluginsByType,
4346
+
4347
+ setPluginList: setPluginList,
4348
+ addPlugin: addPlugin,
4349
+ getModule: getModule
3716
4350
  }
3717
4351
  })();
3718
4352
  ;/**
@@ -3866,6 +4500,8 @@ RED.nodes = (function() {
3866
4500
  },
3867
4501
  removeNodeSet: function(id) {
3868
4502
  var ns = nodeSets[id];
4503
+ if (!ns) { return {} }
4504
+
3869
4505
  for (var j=0;j<ns.types.length;j++) {
3870
4506
  delete typeToId[ns.types[j]];
3871
4507
  }
@@ -4289,12 +4925,16 @@ RED.nodes = (function() {
4289
4925
  * @param {String} z tab id
4290
4926
  */
4291
4927
  checkTabState: function (z) {
4292
- const ws = workspaces[z]
4928
+ const ws = workspaces[z] || subflows[z]
4293
4929
  if (ws) {
4294
4930
  const contentsChanged = tabDirtyMap[z].size > 0 || tabDeletedNodesMap[z].size > 0
4295
4931
  if (Boolean(ws.contentsChanged) !== contentsChanged) {
4296
4932
  ws.contentsChanged = contentsChanged
4297
- RED.events.emit("flows:change", ws);
4933
+ if (ws.type === 'tab') {
4934
+ RED.events.emit("flows:change", ws);
4935
+ } else {
4936
+ RED.events.emit("subflows:change", ws);
4937
+ }
4298
4938
  }
4299
4939
  }
4300
4940
  }
@@ -4767,7 +5407,22 @@ RED.nodes = (function() {
4767
5407
  RED.nodes.registerType("subflow:"+sf.id, {
4768
5408
  defaults:{
4769
5409
  name:{value:""},
4770
- env:{value:[]}
5410
+ env:{value:[], validate: function(value) {
5411
+ const errors = []
5412
+ if (value) {
5413
+ value.forEach(env => {
5414
+ const r = RED.utils.validateTypedProperty(env.value, env.type)
5415
+ if (r !== true) {
5416
+ errors.push(env.name+': '+r)
5417
+ }
5418
+ })
5419
+ }
5420
+ if (errors.length === 0) {
5421
+ return true
5422
+ } else {
5423
+ return errors
5424
+ }
5425
+ }}
4771
5426
  },
4772
5427
  icon: function() { return sf.icon||"subflow.svg" },
4773
5428
  category: sf.category || "subflows",
@@ -7716,7 +8371,14 @@ RED.history = (function() {
7716
8371
  }
7717
8372
  return RED.nodes.junction(id);
7718
8373
  }
7719
-
8374
+ function ensureUnlocked(id, flowsToLock) {
8375
+ const flow = id && (RED.nodes.workspace(id) || RED.nodes.subflow(id) || null);
8376
+ const isLocked = flow ? flow.locked : false;
8377
+ if (flow && isLocked) {
8378
+ flow.locked = false;
8379
+ flowsToLock.add(flow)
8380
+ }
8381
+ }
7720
8382
  function undoEvent(ev) {
7721
8383
  var i;
7722
8384
  var len;
@@ -7746,18 +8408,46 @@ RED.history = (function() {
7746
8408
  t: 'replace',
7747
8409
  config: RED.nodes.createCompleteNodeSet(),
7748
8410
  changed: {},
7749
- rev: RED.nodes.version()
8411
+ moved: {},
8412
+ complete: true,
8413
+ rev: RED.nodes.version(),
8414
+ dirty: RED.nodes.dirty()
7750
8415
  };
8416
+ var selectedTab = RED.workspaces.active();
8417
+ inverseEv.config.forEach(n => {
8418
+ const node = RED.nodes.node(n.id)
8419
+ if (node) {
8420
+ inverseEv.changed[n.id] = node.changed
8421
+ inverseEv.moved[n.id] = node.moved
8422
+ }
8423
+ })
7751
8424
  RED.nodes.clear();
7752
8425
  var imported = RED.nodes.import(ev.config);
8426
+ // Clear all change flags from the import
8427
+ RED.nodes.dirty(false);
8428
+
8429
+ const flowsToLock = new Set()
8430
+
7753
8431
  imported.nodes.forEach(function(n) {
7754
8432
  if (ev.changed[n.id]) {
8433
+ ensureUnlocked(n.z, flowsToLock)
7755
8434
  n.changed = true;
7756
- inverseEv.changed[n.id] = true;
8435
+ }
8436
+ if (ev.moved[n.id]) {
8437
+ ensureUnlocked(n.z, flowsToLock)
8438
+ n.moved = true;
7757
8439
  }
7758
8440
  })
8441
+ flowsToLock.forEach(flow => {
8442
+ flow.locked = true
8443
+ })
7759
8444
 
7760
8445
  RED.nodes.version(ev.rev);
8446
+ RED.view.redraw(true);
8447
+ RED.palette.refresh();
8448
+ RED.workspaces.refresh();
8449
+ RED.workspaces.show(selectedTab, true);
8450
+ RED.sidebar.config.refresh();
7761
8451
  } else {
7762
8452
  var importMap = {};
7763
8453
  ev.config.forEach(function(n) {
@@ -12309,7 +12999,7 @@ RED.popover = (function() {
12309
12999
  closePopup(true);
12310
13000
  });
12311
13001
  }
12312
- if (trigger === 'hover' && options.interactive) {
13002
+ if (/*trigger === 'hover' && */options.interactive) {
12313
13003
  div.on('mouseenter', function(e) {
12314
13004
  clearTimeout(timer);
12315
13005
  active = true;
@@ -12543,9 +13233,12 @@ RED.popover = (function() {
12543
13233
 
12544
13234
  return {
12545
13235
  create: createPopover,
12546
- tooltip: function(target,content, action) {
13236
+ tooltip: function(target,content, action, interactive) {
12547
13237
  var label = function() {
12548
13238
  var label = content;
13239
+ if (typeof content === 'function') {
13240
+ label = content()
13241
+ }
12549
13242
  if (action) {
12550
13243
  var shortcut = RED.keyboard.getShortcut(action);
12551
13244
  if (shortcut && shortcut.key) {
@@ -12561,6 +13254,7 @@ RED.popover = (function() {
12561
13254
  size: "small",
12562
13255
  direction: "bottom",
12563
13256
  content: label,
13257
+ interactive,
12564
13258
  delay: { show: 750, hide: 50 }
12565
13259
  });
12566
13260
  popover.setContent = function(newContent) {
@@ -13319,7 +14013,10 @@ RED.tabs = (function() {
13319
14013
 
13320
14014
  var thisTabA = thisTab.find("a");
13321
14015
  if (options.onclick) {
13322
- options.onclick(tabs[thisTabA.attr('href').slice(1)]);
14016
+ options.onclick(tabs[thisTabA.attr('href').slice(1)], evt);
14017
+ if (evt.isDefaultPrevented() && evt.isPropagationStopped()) {
14018
+ return false
14019
+ }
13323
14020
  }
13324
14021
  activateTab(thisTabA);
13325
14022
  if (fireSelectionChanged) {
@@ -13502,6 +14199,8 @@ RED.tabs = (function() {
13502
14199
  ul.find("li.red-ui-tab a")
13503
14200
  .on("mousedown", function(evt) { mousedownTab = evt.currentTarget })
13504
14201
  .on("mouseup",onTabClick)
14202
+ // prevent browser-default middle-click behaviour
14203
+ .on("auxclick", function(evt) { evt.preventDefault() })
13505
14204
  .on("click", function(evt) {evt.preventDefault(); })
13506
14205
  .on("dblclick", function(evt) {evt.stopPropagation(); evt.preventDefault(); })
13507
14206
 
@@ -13770,6 +14469,8 @@ RED.tabs = (function() {
13770
14469
  }
13771
14470
  link.on("mousedown", function(evt) { mousedownTab = evt.currentTarget })
13772
14471
  link.on("mouseup",onTabClick);
14472
+ // prevent browser-default middle-click behaviour
14473
+ link.on("auxclick", function(evt) { evt.preventDefault() })
13773
14474
  link.on("click", function(evt) { evt.preventDefault(); })
13774
14475
  link.on("dblclick", function(evt) { evt.stopPropagation(); evt.preventDefault(); })
13775
14476
 
@@ -16137,6 +16838,8 @@ RED.deploy = (function() {
16137
16838
 
16138
16839
  var currentDiff = null;
16139
16840
 
16841
+ var activeBackgroundDeployNotification;
16842
+
16140
16843
  function changeDeploymentType(type) {
16141
16844
  deploymentType = type;
16142
16845
  $("#red-ui-header-button-deploy-icon").attr("src",deploymentTypes[type].img);
@@ -16215,51 +16918,59 @@ RED.deploy = (function() {
16215
16918
  RED.actions.add("core:set-deploy-type-to-modified-nodes",function() { RED.menu.setSelected("deploymenu-item-node",true); });
16216
16919
  }
16217
16920
 
16218
-
16921
+ window.addEventListener('beforeunload', function (event) {
16922
+ if (RED.nodes.dirty()) {
16923
+ event.preventDefault();
16924
+ event.stopImmediatePropagation()
16925
+ event.returnValue = RED._("deploy.confirm.undeployedChanges");
16926
+ return
16927
+ }
16928
+ })
16219
16929
 
16220
16930
  RED.events.on('workspace:dirty',function(state) {
16221
16931
  if (state.dirty) {
16222
- window.onbeforeunload = function() {
16223
- return RED._("deploy.confirm.undeployedChanges");
16224
- }
16932
+ // window.onbeforeunload = function() {
16933
+ // return
16934
+ // }
16225
16935
  $("#red-ui-header-button-deploy").removeClass("disabled");
16226
16936
  } else {
16227
- window.onbeforeunload = null;
16937
+ // window.onbeforeunload = null;
16228
16938
  $("#red-ui-header-button-deploy").addClass("disabled");
16229
16939
  }
16230
16940
  });
16231
16941
 
16232
- var activeNotifyMessage;
16233
16942
  RED.comms.subscribe("notification/runtime-deploy",function(topic,msg) {
16234
- if (!activeNotifyMessage) {
16235
- var currentRev = RED.nodes.version();
16236
- if (currentRev === null || deployInflight || currentRev === msg.revision) {
16237
- return;
16238
- }
16239
- var message = $('<p>').text(RED._('deploy.confirm.backgroundUpdate'));
16240
- activeNotifyMessage = RED.notify(message,{
16241
- modal: true,
16242
- fixed: true,
16243
- buttons: [
16244
- {
16245
- text: RED._('deploy.confirm.button.ignore'),
16246
- click: function() {
16247
- activeNotifyMessage.close();
16248
- activeNotifyMessage = null;
16249
- }
16250
- },
16251
- {
16252
- text: RED._('deploy.confirm.button.review'),
16253
- class: "primary",
16254
- click: function() {
16255
- activeNotifyMessage.close();
16256
- var nns = RED.nodes.createCompleteNodeSet();
16257
- resolveConflict(nns,false);
16258
- activeNotifyMessage = null;
16259
- }
16943
+ var currentRev = RED.nodes.version();
16944
+ if (currentRev === null || deployInflight || currentRev === msg.revision) {
16945
+ return;
16946
+ }
16947
+ if (activeBackgroundDeployNotification?.hidden && !activeBackgroundDeployNotification?.closed) {
16948
+ activeBackgroundDeployNotification.showNotification()
16949
+ return
16950
+ }
16951
+ const message = $('<p>').text(RED._('deploy.confirm.backgroundUpdate'));
16952
+ const options = {
16953
+ id: 'background-update',
16954
+ type: 'compact',
16955
+ modal: false,
16956
+ fixed: true,
16957
+ timeout: 10000,
16958
+ buttons: [
16959
+ {
16960
+ text: RED._('deploy.confirm.button.review'),
16961
+ class: "primary",
16962
+ click: function() {
16963
+ activeBackgroundDeployNotification.hideNotification();
16964
+ var nns = RED.nodes.createCompleteNodeSet();
16965
+ resolveConflict(nns,false);
16260
16966
  }
16261
- ]
16262
- });
16967
+ }
16968
+ ]
16969
+ }
16970
+ if (!activeBackgroundDeployNotification || activeBackgroundDeployNotification.closed) {
16971
+ activeBackgroundDeployNotification = RED.notify(message, options)
16972
+ } else {
16973
+ activeBackgroundDeployNotification.update(message, options)
16263
16974
  }
16264
16975
  });
16265
16976
  }
@@ -16316,7 +17027,11 @@ RED.deploy = (function() {
16316
17027
  class: "primary disabled",
16317
17028
  click: function() {
16318
17029
  if (!$("#red-ui-deploy-dialog-confirm-deploy-review").hasClass('disabled')) {
16319
- RED.diff.showRemoteDiff();
17030
+ RED.diff.showRemoteDiff(null, {
17031
+ onmerge: function () {
17032
+ activeBackgroundDeployNotification.close()
17033
+ }
17034
+ });
16320
17035
  conflictNotification.close();
16321
17036
  }
16322
17037
  }
@@ -16329,6 +17044,7 @@ RED.deploy = (function() {
16329
17044
  if (!$("#red-ui-deploy-dialog-confirm-deploy-merge").hasClass('disabled')) {
16330
17045
  RED.diff.mergeDiff(currentDiff);
16331
17046
  conflictNotification.close();
17047
+ activeBackgroundDeployNotification.close()
16332
17048
  }
16333
17049
  }
16334
17050
  }
@@ -16341,6 +17057,7 @@ RED.deploy = (function() {
16341
17057
  click: function() {
16342
17058
  save(true,activeDeploy);
16343
17059
  conflictNotification.close();
17060
+ activeBackgroundDeployNotification.close()
16344
17061
  }
16345
17062
  })
16346
17063
  }
@@ -16351,21 +17068,17 @@ RED.deploy = (function() {
16351
17068
  buttons: buttons
16352
17069
  });
16353
17070
 
16354
- var now = Date.now();
16355
17071
  RED.diff.getRemoteDiff(function(diff) {
16356
- var ellapsed = Math.max(1000 - (Date.now()-now), 0);
16357
17072
  currentDiff = diff;
16358
- setTimeout(function() {
16359
- conflictCheck.hide();
16360
- var d = Object.keys(diff.conflicts);
16361
- if (d.length === 0) {
16362
- conflictAutoMerge.show();
16363
- $("#red-ui-deploy-dialog-confirm-deploy-merge").removeClass('disabled')
16364
- } else {
16365
- conflictManualMerge.show();
16366
- }
16367
- $("#red-ui-deploy-dialog-confirm-deploy-review").removeClass('disabled')
16368
- },ellapsed);
17073
+ conflictCheck.hide();
17074
+ var d = Object.keys(diff.conflicts);
17075
+ if (d.length === 0) {
17076
+ conflictAutoMerge.show();
17077
+ $("#red-ui-deploy-dialog-confirm-deploy-merge").removeClass('disabled')
17078
+ } else {
17079
+ conflictManualMerge.show();
17080
+ }
17081
+ $("#red-ui-deploy-dialog-confirm-deploy-review").removeClass('disabled')
16369
17082
  })
16370
17083
  }
16371
17084
  function cropList(list) {
@@ -16715,7 +17428,10 @@ RED.deploy = (function() {
16715
17428
  }
16716
17429
  });
16717
17430
  RED.nodes.eachSubflow(function (subflow) {
16718
- subflow.changed = false;
17431
+ if (subflow.changed) {
17432
+ subflow.changed = false;
17433
+ RED.events.emit("subflows:change", subflow);
17434
+ }
16719
17435
  });
16720
17436
  RED.nodes.eachWorkspace(function (ws) {
16721
17437
  if (ws.changed || ws.added) {
@@ -16823,7 +17539,6 @@ RED.diagnostics = (function () {
16823
17539
  };
16824
17540
  })();
16825
17541
  ;RED.diff = (function() {
16826
-
16827
17542
  var currentDiff = {};
16828
17543
  var diffVisible = false;
16829
17544
  var diffList;
@@ -16886,12 +17601,14 @@ RED.diagnostics = (function () {
16886
17601
  addedCount:0,
16887
17602
  deletedCount:0,
16888
17603
  changedCount:0,
17604
+ movedCount:0,
16889
17605
  unchangedCount: 0
16890
17606
  },
16891
17607
  remote: {
16892
17608
  addedCount:0,
16893
17609
  deletedCount:0,
16894
17610
  changedCount:0,
17611
+ movedCount:0,
16895
17612
  unchangedCount: 0
16896
17613
  },
16897
17614
  conflicts: 0
@@ -16962,7 +17679,7 @@ RED.diagnostics = (function () {
16962
17679
  $(this).parent().toggleClass('collapsed');
16963
17680
  });
16964
17681
 
16965
- createNodePropertiesTable(def,tab,localTabNode,remoteTabNode,conflicts).appendTo(div);
17682
+ createNodePropertiesTable(def,tab,localTabNode,remoteTabNode).appendTo(div);
16966
17683
  selectState = "";
16967
17684
  if (conflicts[tab.id]) {
16968
17685
  flowStats.conflicts++;
@@ -17032,19 +17749,26 @@ RED.diagnostics = (function () {
17032
17749
  var localStats = $('<span>',{class:"red-ui-diff-list-flow-stats"}).appendTo(localCell);
17033
17750
  $('<span class="red-ui-diff-status"></span>').text(RED._('diff.nodeCount',{count:localNodeCount})).appendTo(localStats);
17034
17751
 
17035
- if (flowStats.conflicts + flowStats.local.addedCount + flowStats.local.changedCount + flowStats.local.deletedCount > 0) {
17752
+ if (flowStats.conflicts + flowStats.local.addedCount + flowStats.local.changedCount + flowStats.local.movedCount + flowStats.local.deletedCount > 0) {
17036
17753
  $('<span class="red-ui-diff-status"> [ </span>').appendTo(localStats);
17037
17754
  if (flowStats.conflicts > 0) {
17038
17755
  $('<span class="red-ui-diff-status-conflict"><span class="red-ui-diff-status"><i class="fa fa-exclamation"></i> '+flowStats.conflicts+'</span></span>').appendTo(localStats);
17039
17756
  }
17040
17757
  if (flowStats.local.addedCount > 0) {
17041
- $('<span class="red-ui-diff-status-added"><span class="red-ui-diff-status"><i class="fa fa-plus-square"></i> '+flowStats.local.addedCount+'</span></span>').appendTo(localStats);
17758
+ const cell = $('<span class="red-ui-diff-status-added"><span class="red-ui-diff-status"><i class="fa fa-plus-square"></i> '+flowStats.local.addedCount+'</span></span>').appendTo(localStats);
17759
+ RED.popover.tooltip(cell, RED._('diff.type.added'))
17042
17760
  }
17043
17761
  if (flowStats.local.changedCount > 0) {
17044
- $('<span class="red-ui-diff-status-changed"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.local.changedCount+'</span></span>').appendTo(localStats);
17762
+ const cell = $('<span class="red-ui-diff-status-changed"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.local.changedCount+'</span></span>').appendTo(localStats);
17763
+ RED.popover.tooltip(cell, RED._('diff.type.changed'))
17764
+ }
17765
+ if (flowStats.local.movedCount > 0) {
17766
+ const cell = $('<span class="red-ui-diff-status-moved"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.local.movedCount+'</span></span>').appendTo(localStats);
17767
+ RED.popover.tooltip(cell, RED._('diff.type.moved'))
17045
17768
  }
17046
17769
  if (flowStats.local.deletedCount > 0) {
17047
- $('<span class="red-ui-diff-status-deleted"><span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> '+flowStats.local.deletedCount+'</span></span>').appendTo(localStats);
17770
+ const cell = $('<span class="red-ui-diff-status-deleted"><span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> '+flowStats.local.deletedCount+'</span></span>').appendTo(localStats);
17771
+ RED.popover.tooltip(cell, RED._('diff.type.deleted'))
17048
17772
  }
17049
17773
  $('<span class="red-ui-diff-status"> ] </span>').appendTo(localStats);
17050
17774
  }
@@ -17070,19 +17794,26 @@ RED.diagnostics = (function () {
17070
17794
  }
17071
17795
  var remoteStats = $('<span>',{class:"red-ui-diff-list-flow-stats"}).appendTo(remoteCell);
17072
17796
  $('<span class="red-ui-diff-status"></span>').text(RED._('diff.nodeCount',{count:remoteNodeCount})).appendTo(remoteStats);
17073
- if (flowStats.conflicts + flowStats.remote.addedCount + flowStats.remote.changedCount + flowStats.remote.deletedCount > 0) {
17797
+ if (flowStats.conflicts + flowStats.remote.addedCount + flowStats.remote.changedCount + flowStats.remote.movedCount + flowStats.remote.deletedCount > 0) {
17074
17798
  $('<span class="red-ui-diff-status"> [ </span>').appendTo(remoteStats);
17075
17799
  if (flowStats.conflicts > 0) {
17076
17800
  $('<span class="red-ui-diff-status-conflict"><span class="red-ui-diff-status"><i class="fa fa-exclamation"></i> '+flowStats.conflicts+'</span></span>').appendTo(remoteStats);
17077
17801
  }
17078
17802
  if (flowStats.remote.addedCount > 0) {
17079
- $('<span class="red-ui-diff-status-added"><span class="red-ui-diff-status"><i class="fa fa-plus-square"></i> '+flowStats.remote.addedCount+'</span></span>').appendTo(remoteStats);
17803
+ const cell = $('<span class="red-ui-diff-status-added"><span class="red-ui-diff-status"><i class="fa fa-plus-square"></i> '+flowStats.remote.addedCount+'</span></span>').appendTo(remoteStats);
17804
+ RED.popover.tooltip(cell, RED._('diff.type.added'))
17080
17805
  }
17081
17806
  if (flowStats.remote.changedCount > 0) {
17082
- $('<span class="red-ui-diff-status-changed"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.remote.changedCount+'</span></span>').appendTo(remoteStats);
17807
+ const cell = $('<span class="red-ui-diff-status-changed"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.remote.changedCount+'</span></span>').appendTo(remoteStats);
17808
+ RED.popover.tooltip(cell, RED._('diff.type.changed'))
17809
+ }
17810
+ if (flowStats.remote.movedCount > 0) {
17811
+ const cell = $('<span class="red-ui-diff-status-moved"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.remote.movedCount+'</span></span>').appendTo(remoteStats);
17812
+ RED.popover.tooltip(cell, RED._('diff.type.moved'))
17083
17813
  }
17084
17814
  if (flowStats.remote.deletedCount > 0) {
17085
- $('<span class="red-ui-diff-status-deleted"><span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> '+flowStats.remote.deletedCount+'</span></span>').appendTo(remoteStats);
17815
+ const cell = $('<span class="red-ui-diff-status-deleted"><span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> '+flowStats.remote.deletedCount+'</span></span>').appendTo(remoteStats);
17816
+ RED.popover.tooltip(cell, RED._('diff.type.deleted'))
17086
17817
  }
17087
17818
  $('<span class="red-ui-diff-status"> ] </span>').appendTo(remoteStats);
17088
17819
  }
@@ -17117,7 +17848,7 @@ RED.diagnostics = (function () {
17117
17848
  if (options.mode === "merge") {
17118
17849
  diffPanel.addClass("red-ui-diff-panel-merge");
17119
17850
  }
17120
- var diffList = createDiffTable(diffPanel, diff);
17851
+ var diffList = createDiffTable(diffPanel, diff, options);
17121
17852
 
17122
17853
  var localDiff = diff.localDiff;
17123
17854
  var remoteDiff = diff.remoteDiff;
@@ -17340,7 +18071,6 @@ RED.diagnostics = (function () {
17340
18071
 
17341
18072
  var hasChanges = false; // exists in original and local/remote but with changes
17342
18073
  var unChanged = true; // existing in original,local,remote unchanged
17343
- var localChanged = false;
17344
18074
 
17345
18075
  if (localDiff.added[node.id]) {
17346
18076
  stats.local.addedCount++;
@@ -17359,12 +18089,20 @@ RED.diagnostics = (function () {
17359
18089
  unChanged = false;
17360
18090
  }
17361
18091
  if (localDiff.changed[node.id]) {
17362
- stats.local.changedCount++;
18092
+ if (localDiff.positionChanged[node.id]) {
18093
+ stats.local.movedCount++
18094
+ } else {
18095
+ stats.local.changedCount++;
18096
+ }
17363
18097
  hasChanges = true;
17364
18098
  unChanged = false;
17365
18099
  }
17366
18100
  if (remoteDiff && remoteDiff.changed[node.id]) {
17367
- stats.remote.changedCount++;
18101
+ if (remoteDiff.positionChanged[node.id]) {
18102
+ stats.remote.movedCount++
18103
+ } else {
18104
+ stats.remote.changedCount++;
18105
+ }
17368
18106
  hasChanges = true;
17369
18107
  unChanged = false;
17370
18108
  }
@@ -17429,27 +18167,32 @@ RED.diagnostics = (function () {
17429
18167
  localNodeDiv.addClass("red-ui-diff-status-moved");
17430
18168
  var localMovedMessage = "";
17431
18169
  if (node.z === localN.z) {
17432
- localMovedMessage = RED._("diff.type.movedFrom",{id:(localDiff.currentConfig.all[node.id].z||'global')});
18170
+ const movedFromNodeTab = localDiff.currentConfig.all[localDiff.currentConfig.all[node.id].z]
18171
+ const movedFromLabel = `'${movedFromNodeTab ? (movedFromNodeTab.label || movedFromNodeTab.id) : 'global'}'`
18172
+ localMovedMessage = RED._("diff.type.movedFrom",{id: movedFromLabel});
17433
18173
  } else {
17434
- localMovedMessage = RED._("diff.type.movedTo",{id:(localN.z||'global')});
18174
+ const movedToNodeTab = localDiff.newConfig.all[localN.z]
18175
+ const movedToLabel = `'${movedToNodeTab ? (movedToNodeTab.label || movedToNodeTab.id) : 'global'}'`
18176
+ localMovedMessage = RED._("diff.type.movedTo",{id:movedToLabel});
17435
18177
  }
17436
18178
  $('<span class="red-ui-diff-status"><i class="fa fa-caret-square-o-right"></i> '+localMovedMessage+'</span>').appendTo(localNodeDiv);
17437
18179
  }
17438
- localChanged = true;
17439
18180
  } else if (localDiff.deleted[node.z]) {
17440
18181
  localNodeDiv.addClass("red-ui-diff-empty");
17441
- localChanged = true;
17442
18182
  } else if (localDiff.deleted[node.id]) {
17443
18183
  localNodeDiv.addClass("red-ui-diff-status-deleted");
17444
18184
  $('<span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> <span data-i18n="diff.type.deleted"></span></span>').appendTo(localNodeDiv);
17445
- localChanged = true;
17446
18185
  } else if (localDiff.changed[node.id]) {
17447
18186
  if (localDiff.newConfig.all[node.id].z !== node.z) {
17448
18187
  localNodeDiv.addClass("red-ui-diff-empty");
17449
18188
  } else {
17450
- localNodeDiv.addClass("red-ui-diff-status-changed");
17451
- $('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(localNodeDiv);
17452
- localChanged = true;
18189
+ if (localDiff.positionChanged[node.id]) {
18190
+ localNodeDiv.addClass("red-ui-diff-status-moved");
18191
+ $('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.moved"></span></span>').appendTo(localNodeDiv);
18192
+ } else {
18193
+ localNodeDiv.addClass("red-ui-diff-status-changed");
18194
+ $('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(localNodeDiv);
18195
+ }
17453
18196
  }
17454
18197
  } else {
17455
18198
  if (localDiff.newConfig.all[node.id].z !== node.z) {
@@ -17470,9 +18213,13 @@ RED.diagnostics = (function () {
17470
18213
  remoteNodeDiv.addClass("red-ui-diff-status-moved");
17471
18214
  var remoteMovedMessage = "";
17472
18215
  if (node.z === remoteN.z) {
17473
- remoteMovedMessage = RED._("diff.type.movedFrom",{id:(remoteDiff.currentConfig.all[node.id].z||'global')});
18216
+ const movedFromNodeTab = remoteDiff.currentConfig.all[remoteDiff.currentConfig.all[node.id].z]
18217
+ const movedFromLabel = `'${movedFromNodeTab ? (movedFromNodeTab.label || movedFromNodeTab.id) : 'global'}'`
18218
+ remoteMovedMessage = RED._("diff.type.movedFrom",{id:movedFromLabel});
17474
18219
  } else {
17475
- remoteMovedMessage = RED._("diff.type.movedTo",{id:(remoteN.z||'global')});
18220
+ const movedToNodeTab = remoteDiff.newConfig.all[remoteN.z]
18221
+ const movedToLabel = `'${movedToNodeTab ? (movedToNodeTab.label || movedToNodeTab.id) : 'global'}'`
18222
+ remoteMovedMessage = RED._("diff.type.movedTo",{id:movedToLabel});
17476
18223
  }
17477
18224
  $('<span class="red-ui-diff-status"><i class="fa fa-caret-square-o-right"></i> '+remoteMovedMessage+'</span>').appendTo(remoteNodeDiv);
17478
18225
  }
@@ -17485,8 +18232,13 @@ RED.diagnostics = (function () {
17485
18232
  if (remoteDiff.newConfig.all[node.id].z !== node.z) {
17486
18233
  remoteNodeDiv.addClass("red-ui-diff-empty");
17487
18234
  } else {
17488
- remoteNodeDiv.addClass("red-ui-diff-status-changed");
17489
- $('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(remoteNodeDiv);
18235
+ if (remoteDiff.positionChanged[node.id]) {
18236
+ remoteNodeDiv.addClass("red-ui-diff-status-moved");
18237
+ $('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.moved"></span></span>').appendTo(remoteNodeDiv);
18238
+ } else {
18239
+ remoteNodeDiv.addClass("red-ui-diff-status-changed");
18240
+ $('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(remoteNodeDiv);
18241
+ }
17490
18242
  }
17491
18243
  } else {
17492
18244
  if (remoteDiff.newConfig.all[node.id].z !== node.z) {
@@ -17612,7 +18364,7 @@ RED.diagnostics = (function () {
17612
18364
  $("<td>",{class:"red-ui-diff-list-cell-label"}).text("position").appendTo(row);
17613
18365
  localCell = $("<td>",{class:"red-ui-diff-list-cell red-ui-diff-list-node-local"}).appendTo(row);
17614
18366
  if (localNode) {
17615
- localCell.addClass("red-ui-diff-status-"+(localChanged?"changed":"unchanged"));
18367
+ localCell.addClass("red-ui-diff-status-"+(localChanged?"moved":"unchanged"));
17616
18368
  $('<span class="red-ui-diff-status">'+(localChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(localCell);
17617
18369
  element = $('<span class="red-ui-diff-list-element"></span>').appendTo(localCell);
17618
18370
  var localPosition = {x:localNode.x,y:localNode.y};
@@ -17637,7 +18389,7 @@ RED.diagnostics = (function () {
17637
18389
 
17638
18390
  if (remoteNode !== undefined) {
17639
18391
  remoteCell = $("<td>",{class:"red-ui-diff-list-cell red-ui-diff-list-node-remote"}).appendTo(row);
17640
- remoteCell.addClass("red-ui-diff-status-"+(remoteChanged?"changed":"unchanged"));
18392
+ remoteCell.addClass("red-ui-diff-status-"+(remoteChanged?"moved":"unchanged"));
17641
18393
  if (remoteNode) {
17642
18394
  $('<span class="red-ui-diff-status">'+(remoteChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(remoteCell);
17643
18395
  element = $('<span class="red-ui-diff-list-element"></span>').appendTo(remoteCell);
@@ -17923,11 +18675,11 @@ RED.diagnostics = (function () {
17923
18675
  // var diff = generateDiff(originalFlow,nns);
17924
18676
  // showDiff(diff);
17925
18677
  // }
17926
- function showRemoteDiff(diff) {
17927
- if (diff === undefined) {
17928
- getRemoteDiff(showRemoteDiff);
18678
+ function showRemoteDiff(diff, options = {}) {
18679
+ if (!diff) {
18680
+ getRemoteDiff((remoteDiff) => showRemoteDiff(remoteDiff, options));
17929
18681
  } else {
17930
- showDiff(diff,{mode:'merge'});
18682
+ showDiff(diff,{...options, mode:'merge'});
17931
18683
  }
17932
18684
  }
17933
18685
  function parseNodes(nodeList) {
@@ -17968,23 +18720,53 @@ RED.diagnostics = (function () {
17968
18720
  }
17969
18721
  }
17970
18722
  function generateDiff(currentNodes,newNodes) {
17971
- var currentConfig = parseNodes(currentNodes);
17972
- var newConfig = parseNodes(newNodes);
17973
- var added = {};
17974
- var deleted = {};
17975
- var changed = {};
17976
- var moved = {};
18723
+ const currentConfig = parseNodes(currentNodes);
18724
+ const newConfig = parseNodes(newNodes);
18725
+ const added = {};
18726
+ const deleted = {};
18727
+ const changed = {};
18728
+ const positionChanged = {};
18729
+ const moved = {};
17977
18730
 
17978
18731
  Object.keys(currentConfig.all).forEach(function(id) {
17979
- var node = RED.nodes.workspace(id)||RED.nodes.subflow(id)||RED.nodes.node(id);
18732
+ const node = RED.nodes.workspace(id)||RED.nodes.subflow(id)||RED.nodes.node(id);
17980
18733
  if (!newConfig.all.hasOwnProperty(id)) {
17981
18734
  deleted[id] = true;
17982
- } else if (JSON.stringify(currentConfig.all[id]) !== JSON.stringify(newConfig.all[id])) {
18735
+ return
18736
+ }
18737
+ const currentConfigJSON = JSON.stringify(currentConfig.all[id])
18738
+ const newConfigJSON = JSON.stringify(newConfig.all[id])
18739
+
18740
+ if (currentConfigJSON !== newConfigJSON) {
17983
18741
  changed[id] = true;
17984
-
17985
18742
  if (currentConfig.all[id].z !== newConfig.all[id].z) {
17986
18743
  moved[id] = true;
18744
+ } else if (
18745
+ currentConfig.all[id].x !== newConfig.all[id].x ||
18746
+ currentConfig.all[id].y !== newConfig.all[id].y ||
18747
+ currentConfig.all[id].w !== newConfig.all[id].w ||
18748
+ currentConfig.all[id].h !== newConfig.all[id].h
18749
+ ) {
18750
+ // This node's position on its parent has changed. We want to
18751
+ // check if this is the *only* change for this given node
18752
+ const currentNodeClone = JSON.parse(currentConfigJSON)
18753
+ const newNodeClone = JSON.parse(newConfigJSON)
18754
+
18755
+ delete currentNodeClone.x
18756
+ delete currentNodeClone.y
18757
+ delete currentNodeClone.w
18758
+ delete currentNodeClone.h
18759
+ delete newNodeClone.x
18760
+ delete newNodeClone.y
18761
+ delete newNodeClone.w
18762
+ delete newNodeClone.h
18763
+
18764
+ if (JSON.stringify(currentNodeClone) === JSON.stringify(newNodeClone)) {
18765
+ // Only the position has changed - everything else is the same
18766
+ positionChanged[id] = true
18767
+ }
17987
18768
  }
18769
+
17988
18770
  }
17989
18771
  });
17990
18772
  Object.keys(newConfig.all).forEach(function(id) {
@@ -17993,13 +18775,14 @@ RED.diagnostics = (function () {
17993
18775
  }
17994
18776
  });
17995
18777
 
17996
- var diff = {
17997
- currentConfig: currentConfig,
17998
- newConfig: newConfig,
17999
- added: added,
18000
- deleted: deleted,
18001
- changed: changed,
18002
- moved: moved
18778
+ const diff = {
18779
+ currentConfig,
18780
+ newConfig,
18781
+ added,
18782
+ deleted,
18783
+ changed,
18784
+ positionChanged,
18785
+ moved
18003
18786
  };
18004
18787
  return diff;
18005
18788
  }
@@ -18064,12 +18847,14 @@ RED.diagnostics = (function () {
18064
18847
  return diff;
18065
18848
  }
18066
18849
 
18067
- function showDiff(diff,options) {
18850
+ function showDiff(diff, options) {
18068
18851
  if (diffVisible) {
18069
18852
  return;
18070
18853
  }
18071
18854
  options = options || {};
18072
18855
  var mode = options.mode || 'merge';
18856
+
18857
+ options.hidePositionChanges = true
18073
18858
 
18074
18859
  var localDiff = diff.localDiff;
18075
18860
  var remoteDiff = diff.remoteDiff;
@@ -18139,6 +18924,9 @@ RED.diagnostics = (function () {
18139
18924
  if (!$("#red-ui-diff-view-diff-merge").hasClass('disabled')) {
18140
18925
  refreshConflictHeader(diff);
18141
18926
  mergeDiff(diff);
18927
+ if (options.onmerge) {
18928
+ options.onmerge()
18929
+ }
18142
18930
  RED.tray.close();
18143
18931
  }
18144
18932
  }
@@ -18169,6 +18957,7 @@ RED.diagnostics = (function () {
18169
18957
  var newConfig = [];
18170
18958
  var node;
18171
18959
  var nodeChangedStates = {};
18960
+ var nodeMovedStates = {};
18172
18961
  var localChangedStates = {};
18173
18962
  for (id in localDiff.newConfig.all) {
18174
18963
  if (localDiff.newConfig.all.hasOwnProperty(id)) {
@@ -18176,12 +18965,14 @@ RED.diagnostics = (function () {
18176
18965
  if (resolutions[id] === 'local') {
18177
18966
  if (node) {
18178
18967
  nodeChangedStates[id] = node.changed;
18968
+ nodeMovedStates[id] = node.moved;
18179
18969
  }
18180
18970
  newConfig.push(localDiff.newConfig.all[id]);
18181
18971
  } else if (resolutions[id] === 'remote') {
18182
18972
  if (!remoteDiff.deleted[id] && remoteDiff.newConfig.all.hasOwnProperty(id)) {
18183
18973
  if (node) {
18184
18974
  nodeChangedStates[id] = node.changed;
18975
+ nodeMovedStates[id] = node.moved;
18185
18976
  }
18186
18977
  localChangedStates[id] = 1;
18187
18978
  newConfig.push(remoteDiff.newConfig.all[id]);
@@ -18205,8 +18996,9 @@ RED.diagnostics = (function () {
18205
18996
  }
18206
18997
  return {
18207
18998
  config: newConfig,
18208
- nodeChangedStates: nodeChangedStates,
18209
- localChangedStates: localChangedStates
18999
+ nodeChangedStates,
19000
+ nodeMovedStates,
19001
+ localChangedStates
18210
19002
  }
18211
19003
  }
18212
19004
 
@@ -18217,6 +19009,7 @@ RED.diagnostics = (function () {
18217
19009
 
18218
19010
  var newConfig = appliedDiff.config;
18219
19011
  var nodeChangedStates = appliedDiff.nodeChangedStates;
19012
+ var nodeMovedStates = appliedDiff.nodeMovedStates;
18220
19013
  var localChangedStates = appliedDiff.localChangedStates;
18221
19014
 
18222
19015
  var isDirty = RED.nodes.dirty();
@@ -18225,33 +19018,56 @@ RED.diagnostics = (function () {
18225
19018
  t:"replace",
18226
19019
  config: RED.nodes.createCompleteNodeSet(),
18227
19020
  changed: nodeChangedStates,
19021
+ moved: nodeMovedStates,
19022
+ complete: true,
18228
19023
  dirty: isDirty,
18229
19024
  rev: RED.nodes.version()
18230
19025
  }
18231
19026
 
18232
19027
  RED.history.push(historyEvent);
18233
19028
 
18234
- var originalFlow = RED.nodes.originalFlow();
18235
- // originalFlow is what the editor things it loaded
18236
- // - add any newly added nodes from remote diff as they are now part of the record
18237
- for (var id in diff.remoteDiff.added) {
18238
- if (diff.remoteDiff.added.hasOwnProperty(id)) {
18239
- if (diff.remoteDiff.newConfig.all.hasOwnProperty(id)) {
18240
- originalFlow.push(JSON.parse(JSON.stringify(diff.remoteDiff.newConfig.all[id])));
18241
- }
18242
- }
18243
- }
19029
+ // var originalFlow = RED.nodes.originalFlow();
19030
+ // // originalFlow is what the editor thinks it loaded
19031
+ // // - add any newly added nodes from remote diff as they are now part of the record
19032
+ // for (var id in diff.remoteDiff.added) {
19033
+ // if (diff.remoteDiff.added.hasOwnProperty(id)) {
19034
+ // if (diff.remoteDiff.newConfig.all.hasOwnProperty(id)) {
19035
+ // originalFlow.push(JSON.parse(JSON.stringify(diff.remoteDiff.newConfig.all[id])));
19036
+ // }
19037
+ // }
19038
+ // }
18244
19039
 
18245
19040
  RED.nodes.clear();
18246
19041
  var imported = RED.nodes.import(newConfig);
18247
19042
 
18248
- // Restore the original flow so subsequent merge resolutions can properly
18249
- // identify new-vs-old
18250
- RED.nodes.originalFlow(originalFlow);
19043
+ // // Restore the original flow so subsequent merge resolutions can properly
19044
+ // // identify new-vs-old
19045
+ // RED.nodes.originalFlow(originalFlow);
19046
+
19047
+ // Clear all change flags from the import
19048
+ RED.nodes.dirty(false);
19049
+
19050
+ const flowsToLock = new Set()
19051
+ function ensureUnlocked(id) {
19052
+ const flow = id && (RED.nodes.workspace(id) || RED.nodes.subflow(id) || null);
19053
+ const isLocked = flow ? flow.locked : false;
19054
+ if (flow && isLocked) {
19055
+ flow.locked = false;
19056
+ flowsToLock.add(flow)
19057
+ }
19058
+ }
18251
19059
  imported.nodes.forEach(function(n) {
18252
- if (nodeChangedStates[n.id] || localChangedStates[n.id]) {
19060
+ if (nodeChangedStates[n.id]) {
19061
+ ensureUnlocked(n.z)
18253
19062
  n.changed = true;
18254
19063
  }
19064
+ if (nodeMovedStates[n.id]) {
19065
+ ensureUnlocked(n.z)
19066
+ n.moved = true;
19067
+ }
19068
+ })
19069
+ flowsToLock.forEach(flow => {
19070
+ flow.locked = true
18255
19071
  })
18256
19072
 
18257
19073
  RED.nodes.version(diff.remoteDiff.rev);
@@ -19920,10 +20736,6 @@ RED.keyboard = (function() {
19920
20736
  }
19921
20737
 
19922
20738
  function init(done) {
19923
- if (!RED.user.hasPermission("settings.write")) {
19924
- RED.notify(RED._("user.errors.settings"),"error");
19925
- return;
19926
- }
19927
20739
  RED.userSettings.add({
19928
20740
  id:'envvar',
19929
20741
  title: RED._("env-var.environment"),
@@ -20315,11 +21127,17 @@ RED.workspaces = (function() {
20315
21127
  RED.sidebar.config.refresh();
20316
21128
  RED.view.focus();
20317
21129
  },
20318
- onclick: function(tab) {
20319
- if (tab.id !== activeWorkspace) {
20320
- addToViewStack(activeWorkspace);
21130
+ onclick: function(tab, evt) {
21131
+ if(evt.which === 2) {
21132
+ evt.preventDefault();
21133
+ evt.stopPropagation();
21134
+ RED.actions.invoke("core:hide-flow", tab)
21135
+ } else {
21136
+ if (tab.id !== activeWorkspace) {
21137
+ addToViewStack(activeWorkspace);
21138
+ }
21139
+ RED.view.focus();
20321
21140
  }
20322
- RED.view.focus();
20323
21141
  },
20324
21142
  ondblclick: function(tab) {
20325
21143
  if (tab.type != "subflow") {
@@ -20357,6 +21175,7 @@ RED.workspaces = (function() {
20357
21175
  if (tab.type === "tab") {
20358
21176
  workspaceTabCount--;
20359
21177
  } else {
21178
+ RED.events.emit("workspace:close",{workspace: tab.id})
20360
21179
  hideStack.push(tab.id);
20361
21180
  }
20362
21181
  RED.menu.setDisabled("menu-item-workspace-delete",activeWorkspace === 0 || workspaceTabCount <= 1);
@@ -20447,6 +21266,11 @@ RED.workspaces = (function() {
20447
21266
  createWorkspaceTabs();
20448
21267
  RED.events.on("sidebar:resize",workspace_tabs.resize);
20449
21268
 
21269
+ RED.events.on("workspace:clear", () => {
21270
+ // Reset the index used to generate new flow names
21271
+ workspaceIndex = 0
21272
+ })
21273
+
20450
21274
  RED.actions.add("core:show-next-tab",function() {
20451
21275
  var oldActive = activeWorkspace;
20452
21276
  workspace_tabs.nextTab();
@@ -20613,6 +21437,9 @@ RED.workspaces = (function() {
20613
21437
  RED.events.on("flows:change", (ws) => {
20614
21438
  $("#red-ui-tab-"+(ws.id.replace(".","-"))).toggleClass('red-ui-workspace-changed',!!(ws.contentsChanged || ws.changed || ws.added));
20615
21439
  })
21440
+ RED.events.on("subflows:change", (ws) => {
21441
+ $("#red-ui-tab-"+(ws.id.replace(".","-"))).toggleClass('red-ui-workspace-changed',!!(ws.contentsChanged || ws.changed || ws.added));
21442
+ })
20616
21443
 
20617
21444
  hideWorkspace();
20618
21445
  }
@@ -21564,120 +22391,128 @@ RED.view = (function() {
21564
22391
  }
21565
22392
  d3.event = event;
21566
22393
  var selected_tool = $(ui.draggable[0]).attr("data-palette-type");
21567
- var result = createNode(selected_tool);
21568
- if (!result) {
21569
- return;
21570
- }
21571
- var historyEvent = result.historyEvent;
21572
- var nn = RED.nodes.add(result.node);
22394
+ try {
22395
+ var result = createNode(selected_tool);
22396
+ if (!result) {
22397
+ return;
22398
+ }
22399
+ var historyEvent = result.historyEvent;
22400
+ var nn = RED.nodes.add(result.node);
21573
22401
 
21574
- var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label");
21575
- if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) {
21576
- nn.l = showLabel;
21577
- }
22402
+ var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label");
22403
+ if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) {
22404
+ nn.l = showLabel;
22405
+ }
21578
22406
 
21579
- var helperOffset = d3.touches(ui.helper.get(0))[0]||d3.mouse(ui.helper.get(0));
21580
- var helperWidth = ui.helper.width();
21581
- var helperHeight = ui.helper.height();
21582
- var mousePos = d3.touches(this)[0]||d3.mouse(this);
22407
+ var helperOffset = d3.touches(ui.helper.get(0))[0]||d3.mouse(ui.helper.get(0));
22408
+ var helperWidth = ui.helper.width();
22409
+ var helperHeight = ui.helper.height();
22410
+ var mousePos = d3.touches(this)[0]||d3.mouse(this);
21583
22411
 
21584
- try {
21585
- var isLink = (nn.type === "link in" || nn.type === "link out")
21586
- var hideLabel = nn.hasOwnProperty('l')?!nn.l : isLink;
21587
-
21588
- var label = RED.utils.getNodeLabel(nn, nn.type);
21589
- var labelParts = getLabelParts(label, "red-ui-flow-node-label");
21590
- if (hideLabel) {
21591
- nn.w = node_height;
21592
- nn.h = Math.max(node_height,(nn.outputs || 0) * 15);
21593
- } else {
21594
- nn.w = Math.max(node_width,20*(Math.ceil((labelParts.width+50+(nn._def.inputs>0?7:0))/20)) );
21595
- nn.h = Math.max(6+24*labelParts.lines.length,(nn.outputs || 0) * 15, 30);
22412
+ try {
22413
+ var isLink = (nn.type === "link in" || nn.type === "link out")
22414
+ var hideLabel = nn.hasOwnProperty('l')?!nn.l : isLink;
22415
+
22416
+ var label = RED.utils.getNodeLabel(nn, nn.type);
22417
+ var labelParts = getLabelParts(label, "red-ui-flow-node-label");
22418
+ if (hideLabel) {
22419
+ nn.w = node_height;
22420
+ nn.h = Math.max(node_height,(nn.outputs || 0) * 15);
22421
+ } else {
22422
+ nn.w = Math.max(node_width,20*(Math.ceil((labelParts.width+50+(nn._def.inputs>0?7:0))/20)) );
22423
+ nn.h = Math.max(6+24*labelParts.lines.length,(nn.outputs || 0) * 15, 30);
22424
+ }
22425
+ } catch(err) {
21596
22426
  }
21597
- } catch(err) {
21598
- }
21599
22427
 
21600
- mousePos[1] += this.scrollTop + ((helperHeight/2)-helperOffset[1]);
21601
- mousePos[0] += this.scrollLeft + ((helperWidth/2)-helperOffset[0]);
21602
- mousePos[1] /= scaleFactor;
21603
- mousePos[0] /= scaleFactor;
22428
+ mousePos[1] += this.scrollTop + ((helperHeight/2)-helperOffset[1]);
22429
+ mousePos[0] += this.scrollLeft + ((helperWidth/2)-helperOffset[0]);
22430
+ mousePos[1] /= scaleFactor;
22431
+ mousePos[0] /= scaleFactor;
21604
22432
 
21605
- nn.x = mousePos[0];
21606
- nn.y = mousePos[1];
22433
+ nn.x = mousePos[0];
22434
+ nn.y = mousePos[1];
21607
22435
 
21608
- var minX = nn.w/2 -5;
21609
- if (nn.x < minX) {
21610
- nn.x = minX;
21611
- }
21612
- var minY = nn.h/2 -5;
21613
- if (nn.y < minY) {
21614
- nn.y = minY;
21615
- }
21616
- var maxX = space_width -nn.w/2 +5;
21617
- if (nn.x > maxX) {
21618
- nn.x = maxX;
21619
- }
21620
- var maxY = space_height -nn.h +5;
21621
- if (nn.y > maxY) {
21622
- nn.y = maxY;
21623
- }
21624
-
21625
- if (snapGrid) {
21626
- var gridOffset = RED.view.tools.calculateGridSnapOffsets(nn);
21627
- nn.x -= gridOffset.x;
21628
- nn.y -= gridOffset.y;
21629
- }
22436
+ var minX = nn.w/2 -5;
22437
+ if (nn.x < minX) {
22438
+ nn.x = minX;
22439
+ }
22440
+ var minY = nn.h/2 -5;
22441
+ if (nn.y < minY) {
22442
+ nn.y = minY;
22443
+ }
22444
+ var maxX = space_width -nn.w/2 +5;
22445
+ if (nn.x > maxX) {
22446
+ nn.x = maxX;
22447
+ }
22448
+ var maxY = space_height -nn.h +5;
22449
+ if (nn.y > maxY) {
22450
+ nn.y = maxY;
22451
+ }
21630
22452
 
21631
- var linkToSplice = $(ui.helper).data("splice");
21632
- if (linkToSplice) {
21633
- spliceLink(linkToSplice, nn, historyEvent)
21634
- }
22453
+ if (snapGrid) {
22454
+ var gridOffset = RED.view.tools.calculateGridSnapOffsets(nn);
22455
+ nn.x -= gridOffset.x;
22456
+ nn.y -= gridOffset.y;
22457
+ }
21635
22458
 
21636
- var group = $(ui.helper).data("group");
21637
- if (group) {
21638
- var oldX = group.x;
21639
- var oldY = group.y;
21640
- RED.group.addToGroup(group, nn);
21641
- var moveEvent = null;
21642
- if ((group.x !== oldX) ||
21643
- (group.y !== oldY)) {
21644
- moveEvent = {
21645
- t: "move",
21646
- nodes: [{n: group,
21647
- ox: oldX, oy: oldY,
21648
- dx: group.x -oldX,
21649
- dy: group.y -oldY}],
21650
- dirty: true
21651
- };
22459
+ var linkToSplice = $(ui.helper).data("splice");
22460
+ if (linkToSplice) {
22461
+ spliceLink(linkToSplice, nn, historyEvent)
21652
22462
  }
21653
- historyEvent = {
21654
- t: 'multi',
21655
- events: [historyEvent],
21656
22463
 
21657
- };
21658
- if (moveEvent) {
21659
- historyEvent.events.push(moveEvent)
22464
+ var group = $(ui.helper).data("group");
22465
+ if (group) {
22466
+ var oldX = group.x;
22467
+ var oldY = group.y;
22468
+ RED.group.addToGroup(group, nn);
22469
+ var moveEvent = null;
22470
+ if ((group.x !== oldX) ||
22471
+ (group.y !== oldY)) {
22472
+ moveEvent = {
22473
+ t: "move",
22474
+ nodes: [{n: group,
22475
+ ox: oldX, oy: oldY,
22476
+ dx: group.x -oldX,
22477
+ dy: group.y -oldY}],
22478
+ dirty: true
22479
+ };
22480
+ }
22481
+ historyEvent = {
22482
+ t: 'multi',
22483
+ events: [historyEvent],
22484
+
22485
+ };
22486
+ if (moveEvent) {
22487
+ historyEvent.events.push(moveEvent)
22488
+ }
22489
+ historyEvent.events.push({
22490
+ t: "addToGroup",
22491
+ group: group,
22492
+ nodes: nn
22493
+ })
21660
22494
  }
21661
- historyEvent.events.push({
21662
- t: "addToGroup",
21663
- group: group,
21664
- nodes: nn
21665
- })
21666
- }
21667
22495
 
21668
- RED.history.push(historyEvent);
21669
- RED.editor.validateNode(nn);
21670
- RED.nodes.dirty(true);
21671
- // auto select dropped node - so info shows (if visible)
21672
- clearSelection();
21673
- nn.selected = true;
21674
- movingSet.add(nn);
21675
- updateActiveNodes();
21676
- updateSelection();
21677
- redraw();
22496
+ RED.history.push(historyEvent);
22497
+ RED.editor.validateNode(nn);
22498
+ RED.nodes.dirty(true);
22499
+ // auto select dropped node - so info shows (if visible)
22500
+ clearSelection();
22501
+ nn.selected = true;
22502
+ movingSet.add(nn);
22503
+ updateActiveNodes();
22504
+ updateSelection();
22505
+ redraw();
21678
22506
 
21679
- if (nn._def.autoedit) {
21680
- RED.editor.edit(nn);
22507
+ if (nn._def.autoedit) {
22508
+ RED.editor.edit(nn);
22509
+ }
22510
+ } catch (error) {
22511
+ if (error.code != "NODE_RED") {
22512
+ RED.notify(RED._("notification.error",{message:error.toString()}),"error");
22513
+ } else {
22514
+ RED.notify(RED._("notification.error",{message:error.message}),"error");
22515
+ }
21681
22516
  }
21682
22517
  }
21683
22518
  });
@@ -26981,14 +27816,19 @@ RED.view = (function() {
26981
27816
  function createNode(type, x, y, z) {
26982
27817
  const wasDirty = RED.nodes.dirty()
26983
27818
  var m = /^subflow:(.+)$/.exec(type);
26984
- var activeSubflow = z ? RED.nodes.subflow(z) : null;
27819
+ var activeSubflow = (z || RED.workspaces.active()) ? RED.nodes.subflow(z || RED.workspaces.active()) : null;
27820
+
26985
27821
  if (activeSubflow && m) {
26986
27822
  var subflowId = m[1];
27823
+ let err
26987
27824
  if (subflowId === activeSubflow.id) {
26988
- throw new Error(RED._("notification.error", { message: RED._("notification.errors.cannotAddSubflowToItself") }))
27825
+ err = new Error(RED._("notification.errors.cannotAddSubflowToItself"))
27826
+ } else if (RED.nodes.subflowContains(m[1], activeSubflow.id)) {
27827
+ err = new Error(RED._("notification.errors.cannotAddCircularReference"))
26989
27828
  }
26990
- if (RED.nodes.subflowContains(m[1], activeSubflow.id)) {
26991
- throw new Error(RED._("notification.error", { message: RED._("notification.errors.cannotAddCircularReference") }))
27829
+ if (err) {
27830
+ err.code = 'NODE_RED'
27831
+ throw err
26992
27832
  }
26993
27833
  }
26994
27834
 
@@ -27377,14 +28217,27 @@ RED.view = (function() {
27377
28217
  addAnnotation(evt.node.__pendingAnnotation__,evt);
27378
28218
  delete evt.node.__pendingAnnotation__;
27379
28219
  }
27380
- var badgeDX = 0;
27381
- var controlDX = 0;
27382
- for (var i=0,l=evt.el.__annotations__.length;i<l;i++) {
27383
- var annotation = evt.el.__annotations__[i];
28220
+ let badgeRDX = 0;
28221
+ let badgeLDX = 0;
28222
+
28223
+ for (let i=0,l=evt.el.__annotations__.length;i<l;i++) {
28224
+ const annotation = evt.el.__annotations__[i];
27384
28225
  if (annotations.hasOwnProperty(annotation.id)) {
27385
- var opts = annotations[annotation.id];
27386
- var showAnnotation = true;
27387
- var isBadge = opts.type === 'badge';
28226
+ const opts = annotations[annotation.id];
28227
+ let showAnnotation = true;
28228
+ const isBadge = opts.type === 'badge';
28229
+ if (opts.refresh !== undefined) {
28230
+ let refreshAnnotation = false
28231
+ if (typeof opts.refresh === "string") {
28232
+ refreshAnnotation = !!evt.node[opts.refresh]
28233
+ delete evt.node[opts.refresh]
28234
+ } else if (typeof opts.refresh === "function") {
28235
+ refreshAnnotation = opts.refresh(evnt.node)
28236
+ }
28237
+ if (refreshAnnotation) {
28238
+ refreshAnnotationElement(annotation.id, annotation.node, annotation.element)
28239
+ }
28240
+ }
27388
28241
  if (opts.show !== undefined) {
27389
28242
  if (typeof opts.show === "string") {
27390
28243
  showAnnotation = !!evt.node[opts.show]
@@ -27397,17 +28250,24 @@ RED.view = (function() {
27397
28250
  }
27398
28251
  if (isBadge) {
27399
28252
  if (showAnnotation) {
27400
- var rect = annotation.element.getBoundingClientRect();
27401
- badgeDX += rect.width;
27402
- annotation.element.setAttribute("transform", "translate("+(evt.node.w-3-badgeDX)+", -8)");
27403
- badgeDX += 4;
27404
- }
27405
- } else {
27406
- if (showAnnotation) {
27407
- var rect = annotation.element.getBoundingClientRect();
27408
- annotation.element.setAttribute("transform", "translate("+(3+controlDX)+", -12)");
27409
- controlDX += rect.width + 4;
28253
+ const rect = annotation.element.getBoundingClientRect();
28254
+ let annotationX
28255
+ if (!opts.align || opts.align === 'right') {
28256
+ annotationX = evt.node.w - 3 - badgeRDX - rect.width
28257
+ badgeRDX += rect.width + 4;
28258
+
28259
+ } else if (opts.align === 'left') {
28260
+ annotationX = 3 + badgeLDX
28261
+ badgeLDX += rect.width + 4;
28262
+ }
28263
+ annotation.element.setAttribute("transform", "translate("+annotationX+", -8)");
27410
28264
  }
28265
+ // } else {
28266
+ // if (showAnnotation) {
28267
+ // var rect = annotation.element.getBoundingClientRect();
28268
+ // annotation.element.setAttribute("transform", "translate("+(3+controlDX)+", -12)");
28269
+ // controlDX += rect.width + 4;
28270
+ // }
27411
28271
  }
27412
28272
  } else {
27413
28273
  annotation.element.parentNode.removeChild(annotation.element);
@@ -27463,15 +28323,25 @@ RED.view = (function() {
27463
28323
  annotationGroup.setAttribute("class",opts.class || "");
27464
28324
  evt.el.__annotations__.push({
27465
28325
  id:id,
28326
+ node: evt.node,
27466
28327
  element: annotationGroup
27467
28328
  });
27468
- var annotation = opts.element(evt.node);
28329
+ refreshAnnotationElement(id, evt.node, annotationGroup)
28330
+ evt.el.appendChild(annotationGroup);
28331
+ }
28332
+
28333
+ function refreshAnnotationElement(id, node, annotationGroup) {
28334
+ const opts = annotations[id];
28335
+ const annotation = opts.element(node);
27469
28336
  if (opts.tooltip) {
27470
- annotation.addEventListener("mouseenter", getAnnotationMouseEnter(annotation,evt.node,opts.tooltip));
28337
+ annotation.addEventListener("mouseenter", getAnnotationMouseEnter(annotation, node, opts.tooltip));
27471
28338
  annotation.addEventListener("mouseleave", annotationMouseLeave);
27472
28339
  }
28340
+ if (annotationGroup.hasChildNodes()) {
28341
+ annotationGroup.removeChild(annotationGroup.firstChild)
28342
+ }
27473
28343
  annotationGroup.appendChild(annotation);
27474
- evt.el.appendChild(annotationGroup);
28344
+
27475
28345
  }
27476
28346
 
27477
28347
 
@@ -29427,6 +30297,10 @@ RED.palette = (function() {
29427
30297
  var categoryContainers = {};
29428
30298
  var sidebarControls;
29429
30299
 
30300
+ let paletteState = { filter: "", collapsed: [] };
30301
+
30302
+ let filterRefreshTimeout
30303
+
29430
30304
  function createCategory(originalCategory,rootCategory,category,ns) {
29431
30305
  if ($("#red-ui-palette-base-category-"+rootCategory).length === 0) {
29432
30306
  createCategoryContainer(originalCategory,rootCategory, ns+":palette.label."+rootCategory);
@@ -29452,20 +30326,57 @@ RED.palette = (function() {
29452
30326
  catDiv.data('label',label);
29453
30327
  categoryContainers[category] = {
29454
30328
  container: catDiv,
29455
- close: function() {
30329
+ hide: function (instant) {
30330
+ if (instant) {
30331
+ catDiv.hide()
30332
+ } else {
30333
+ catDiv.slideUp()
30334
+ }
30335
+ },
30336
+ show: function () {
30337
+ catDiv.show()
30338
+ },
30339
+ isOpen: function () {
30340
+ return !!catDiv.hasClass("red-ui-palette-open")
30341
+ },
30342
+ getNodeCount: function (visibleOnly) {
30343
+ const nodes = catDiv.find(".red-ui-palette-node")
30344
+ if (visibleOnly) {
30345
+ return nodes.filter(function() { return $(this).css('display') !== 'none'}).length
30346
+ } else {
30347
+ return nodes.length
30348
+ }
30349
+ },
30350
+ close: function(instant, skipSaveState) {
29456
30351
  catDiv.removeClass("red-ui-palette-open");
29457
30352
  catDiv.addClass("red-ui-palette-closed");
29458
- $("#red-ui-palette-base-category-"+category).slideUp();
30353
+ if (instant) {
30354
+ $("#red-ui-palette-base-category-"+category).hide();
30355
+ } else {
30356
+ $("#red-ui-palette-base-category-"+category).slideUp();
30357
+ }
29459
30358
  $("#red-ui-palette-header-"+category+" i").removeClass("expanded");
30359
+ if (!skipSaveState) {
30360
+ if (!paletteState.collapsed.includes(category)) {
30361
+ paletteState.collapsed.push(category);
30362
+ savePaletteState();
30363
+ }
30364
+ }
29460
30365
  },
29461
- open: function() {
30366
+ open: function(skipSaveState) {
29462
30367
  catDiv.addClass("red-ui-palette-open");
29463
30368
  catDiv.removeClass("red-ui-palette-closed");
29464
30369
  $("#red-ui-palette-base-category-"+category).slideDown();
29465
30370
  $("#red-ui-palette-header-"+category+" i").addClass("expanded");
30371
+ if (!skipSaveState) {
30372
+ if (paletteState.collapsed.includes(category)) {
30373
+ paletteState.collapsed.splice(paletteState.collapsed.indexOf(category), 1);
30374
+ savePaletteState();
30375
+ }
30376
+ }
29466
30377
  },
29467
30378
  toggle: function() {
29468
- if (catDiv.hasClass("red-ui-palette-open")) {
30379
+ if (categoryContainers[category].isOpen()) {
29469
30380
  categoryContainers[category].close();
29470
30381
  } else {
29471
30382
  categoryContainers[category].open();
@@ -29807,8 +30718,16 @@ RED.palette = (function() {
29807
30718
 
29808
30719
  var categoryNode = $("#red-ui-palette-container-"+rootCategory);
29809
30720
  if (categoryNode.find(".red-ui-palette-node").length === 1) {
29810
- categoryContainers[rootCategory].open();
30721
+ if (!paletteState?.collapsed?.includes(rootCategory)) {
30722
+ categoryContainers[rootCategory].open();
30723
+ } else {
30724
+ categoryContainers[rootCategory].close(true);
30725
+ }
29811
30726
  }
30727
+ clearTimeout(filterRefreshTimeout)
30728
+ filterRefreshTimeout = setTimeout(() => {
30729
+ refreshFilter()
30730
+ }, 200)
29812
30731
 
29813
30732
  }
29814
30733
  }
@@ -29908,7 +30827,8 @@ RED.palette = (function() {
29908
30827
  paletteNode.css("backgroundColor", sf.color);
29909
30828
  }
29910
30829
 
29911
- function filterChange(val) {
30830
+ function refreshFilter() {
30831
+ const val = $("#red-ui-palette-search input").val()
29912
30832
  var re = new RegExp(val.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'),'i');
29913
30833
  $("#red-ui-palette-container .red-ui-palette-node").each(function(i,el) {
29914
30834
  var currentLabel = $(el).attr("data-palette-label");
@@ -29920,16 +30840,26 @@ RED.palette = (function() {
29920
30840
  }
29921
30841
  });
29922
30842
 
29923
- for (var category in categoryContainers) {
30843
+ for (let category in categoryContainers) {
29924
30844
  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();
30845
+ const categorySection = categoryContainers[category]
30846
+ if (categorySection.getNodeCount(true) === 0) {
30847
+ categorySection.hide()
29930
30848
  } else {
29931
- categoryContainers[category].open();
29932
- categoryContainers[category].container.show();
30849
+ categorySection.show()
30850
+ if (val) {
30851
+ // There is a filter being applied and it has matched
30852
+ // something in this category - show the contents
30853
+ categorySection.open(true)
30854
+ } else {
30855
+ // No filter. Only show the category if it isn't in lastState
30856
+ if (!paletteState.collapsed.includes(category)) {
30857
+ categorySection.open(true)
30858
+ } else if (categorySection.isOpen()) {
30859
+ // This section should be collapsed but isn't - so make it so
30860
+ categorySection.close(true, true)
30861
+ }
30862
+ }
29933
30863
  }
29934
30864
  }
29935
30865
  }
@@ -29945,6 +30875,9 @@ RED.palette = (function() {
29945
30875
 
29946
30876
  $("#red-ui-palette > .red-ui-palette-spinner").show();
29947
30877
 
30878
+ RED.events.on('logout', function () {
30879
+ RED.settings.removeLocal('palette-state')
30880
+ })
29948
30881
 
29949
30882
  RED.events.on('registry:node-type-added', function(nodeType) {
29950
30883
  var def = RED.nodes.getType(nodeType);
@@ -29988,14 +30921,14 @@ RED.palette = (function() {
29988
30921
 
29989
30922
  RED.events.on("subflows:change",refreshSubflow);
29990
30923
 
29991
-
29992
-
29993
30924
  $("#red-ui-palette-search input").searchBox({
29994
30925
  delay: 100,
29995
30926
  change: function() {
29996
- filterChange($(this).val());
30927
+ refreshFilter();
30928
+ paletteState.filter = $(this).val();
30929
+ savePaletteState();
29997
30930
  }
29998
- })
30931
+ });
29999
30932
 
30000
30933
  sidebarControls = $('<div class="red-ui-sidebar-control-left"><i class="fa fa-chevron-left"></i></div>').appendTo($("#red-ui-palette"));
30001
30934
  RED.popover.tooltip(sidebarControls,RED._("keyboard.togglePalette"),"core:toggle-palette");
@@ -30061,7 +30994,23 @@ RED.palette = (function() {
30061
30994
  togglePalette(state);
30062
30995
  }
30063
30996
  });
30997
+
30998
+ try {
30999
+ paletteState = JSON.parse(RED.settings.getLocal("palette-state") || '{"filter":"", "collapsed": []}');
31000
+ if (paletteState.filter) {
31001
+ // Apply the category filter
31002
+ $("#red-ui-palette-search input").searchBox("value", paletteState.filter);
31003
+ }
31004
+ } catch (error) {
31005
+ console.error("Unexpected error loading palette state from localStorage: ", error);
31006
+ }
31007
+ setTimeout(() => {
31008
+ // Lazily tidy up any categories that haven't been reloaded
31009
+ paletteState.collapsed = paletteState.collapsed.filter(category => !!categoryContainers[category])
31010
+ savePaletteState()
31011
+ }, 10000)
30064
31012
  }
31013
+
30065
31014
  function togglePalette(state) {
30066
31015
  if (!state) {
30067
31016
  $("#red-ui-main-container").addClass("red-ui-palette-closed");
@@ -30081,6 +31030,15 @@ RED.palette = (function() {
30081
31030
  })
30082
31031
  return categories;
30083
31032
  }
31033
+
31034
+ function savePaletteState() {
31035
+ try {
31036
+ RED.settings.setLocal("palette-state", JSON.stringify(paletteState));
31037
+ } catch (error) {
31038
+ console.error("Unexpected error saving palette state to localStorage: ", error);
31039
+ }
31040
+ }
31041
+
30084
31042
  return {
30085
31043
  init: init,
30086
31044
  add:addNodeType,
@@ -31570,8 +32528,10 @@ RED.sidebar.help = (function() {
31570
32528
 
31571
32529
  function refreshSubflow(sf) {
31572
32530
  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()}));
32531
+ if (item) {
32532
+ item.subflowLabel = sf._def.label().toLowerCase();
32533
+ item.treeList.replaceElement(getNodeLabel({_def:sf._def,type:sf._def.label()}));
32534
+ }
31575
32535
  }
31576
32536
 
31577
32537
  function hideTOC() {
@@ -32823,7 +33783,7 @@ RED.palette.editor = (function() {
32823
33783
  }).done(function(data,textStatus,xhr) {
32824
33784
  callback();
32825
33785
  }).fail(function(xhr,textStatus,err) {
32826
- callback(xhr);
33786
+ callback(xhr,textStatus,err);
32827
33787
  });
32828
33788
  }
32829
33789
  function removeNodeModule(id,callback) {
@@ -32938,86 +33898,106 @@ RED.palette.editor = (function() {
32938
33898
  var moduleInfo = nodeEntries[module].info;
32939
33899
  var nodeEntry = nodeEntries[module].elements;
32940
33900
  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
- }
33901
+ if (moduleInfo.plugin) {
33902
+ nodeEntry.enableButton.hide();
33903
+ nodeEntry.removeButton.show();
33904
+
33905
+ let pluginCount = 0;
33906
+ for (let setName in moduleInfo.sets) {
33907
+ if (moduleInfo.sets.hasOwnProperty(setName)) {
33908
+ let set = moduleInfo.sets[setName];
33909
+ if (set.plugins) {
33910
+ pluginCount += set.plugins.length;
32977
33911
  }
32978
33912
  }
32979
- nodeEntries[module].setUseCount[setName] = inUseCount;
32980
- nodeEntries[module].totalUseCount += inUseCount;
33913
+ }
33914
+
33915
+ nodeEntry.setCount.text(RED._('palette.editor.pluginCount',{count:pluginCount,label:pluginCount}));
32981
33916
 
32982
- if (inUseCount > 0) {
32983
- setElements.enableButton.text(RED._('palette.editor.inuse'));
32984
- setElements.enableButton.addClass('disabled');
32985
- } else {
32986
- setElements.enableButton.removeClass('disabled');
33917
+ } else {
33918
+ var activeTypeCount = 0;
33919
+ var typeCount = 0;
33920
+ var errorCount = 0;
33921
+ nodeEntry.errorList.empty();
33922
+ nodeEntries[module].totalUseCount = 0;
33923
+ nodeEntries[module].setUseCount = {};
33924
+
33925
+ for (var setName in moduleInfo.sets) {
33926
+ if (moduleInfo.sets.hasOwnProperty(setName)) {
33927
+ var inUseCount = 0;
33928
+ const set = moduleInfo.sets[setName];
33929
+ const setElements = nodeEntry.sets[setName]
33930
+
33931
+ if (set.err) {
33932
+ errorCount++;
33933
+ var errMessage = set.err;
33934
+ if (set.err.message) {
33935
+ errMessage = set.err.message;
33936
+ } else if (set.err.code) {
33937
+ errMessage = set.err.code;
33938
+ }
33939
+ $("<li>").text(errMessage).appendTo(nodeEntry.errorList);
33940
+ }
32987
33941
  if (set.enabled) {
32988
- setElements.enableButton.text(RED._('palette.editor.disable'));
32989
- } else {
32990
- setElements.enableButton.text(RED._('palette.editor.enable'));
33942
+ activeTypeCount += set.types.length;
33943
+ }
33944
+ typeCount += set.types.length;
33945
+ for (var i=0;i<moduleInfo.sets[setName].types.length;i++) {
33946
+ var t = moduleInfo.sets[setName].types[i];
33947
+ inUseCount += (typesInUse[t]||0);
33948
+ if (setElements && set.enabled) {
33949
+ var def = RED.nodes.getType(t);
33950
+ if (def && def.color) {
33951
+ setElements.swatches[t].css({background:RED.utils.getNodeColor(t,def)});
33952
+ setElements.swatches[t].css({border: "1px solid "+getContrastingBorder(setElements.swatches[t].css('backgroundColor'))})
33953
+ }
33954
+ }
33955
+ }
33956
+ nodeEntries[module].setUseCount[setName] = inUseCount;
33957
+ nodeEntries[module].totalUseCount += inUseCount;
33958
+
33959
+ if (setElements) {
33960
+ if (inUseCount > 0) {
33961
+ setElements.enableButton.text(RED._('palette.editor.inuse'));
33962
+ setElements.enableButton.addClass('disabled');
33963
+ } else {
33964
+ setElements.enableButton.removeClass('disabled');
33965
+ if (set.enabled) {
33966
+ setElements.enableButton.text(RED._('palette.editor.disable'));
33967
+ } else {
33968
+ setElements.enableButton.text(RED._('palette.editor.enable'));
33969
+ }
33970
+ }
33971
+ setElements.setRow.toggleClass("red-ui-palette-module-set-disabled",!set.enabled);
32991
33972
  }
32992
33973
  }
32993
- setElements.setRow.toggleClass("red-ui-palette-module-set-disabled",!set.enabled);
32994
33974
  }
32995
- }
32996
33975
 
32997
- if (errorCount === 0) {
32998
- nodeEntry.errorRow.hide()
32999
- } else {
33000
- nodeEntry.errorRow.show();
33001
- }
33976
+ if (errorCount === 0) {
33977
+ nodeEntry.errorRow.hide()
33978
+ } else {
33979
+ nodeEntry.errorRow.show();
33980
+ }
33002
33981
 
33003
- var nodeCount = (activeTypeCount === typeCount)?typeCount:activeTypeCount+" / "+typeCount;
33004
- nodeEntry.setCount.text(RED._('palette.editor.nodeCount',{count:typeCount,label:nodeCount}));
33982
+ var nodeCount = (activeTypeCount === typeCount)?typeCount:activeTypeCount+" / "+typeCount;
33983
+ nodeEntry.setCount.text(RED._('palette.editor.nodeCount',{count:typeCount,label:nodeCount}));
33005
33984
 
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'));
33985
+ if (nodeEntries[module].totalUseCount > 0) {
33986
+ nodeEntry.enableButton.text(RED._('palette.editor.inuse'));
33987
+ nodeEntry.enableButton.addClass('disabled');
33988
+ nodeEntry.removeButton.hide();
33017
33989
  } else {
33018
- nodeEntry.enableButton.text(RED._('palette.editor.disableall'));
33990
+ nodeEntry.enableButton.removeClass('disabled');
33991
+ if (moduleInfo.local) {
33992
+ nodeEntry.removeButton.css('display', 'inline-block');
33993
+ }
33994
+ if (activeTypeCount === 0) {
33995
+ nodeEntry.enableButton.text(RED._('palette.editor.enableall'));
33996
+ } else {
33997
+ nodeEntry.enableButton.text(RED._('palette.editor.disableall'));
33998
+ }
33999
+ nodeEntry.container.toggleClass("disabled",(activeTypeCount === 0));
33019
34000
  }
33020
- nodeEntry.container.toggleClass("disabled",(activeTypeCount === 0));
33021
34001
  }
33022
34002
  }
33023
34003
  if (moduleInfo.pending_version) {
@@ -33368,6 +34348,33 @@ RED.palette.editor = (function() {
33368
34348
  }
33369
34349
  }
33370
34350
  })
34351
+
34352
+ RED.events.on("registry:plugin-module-added", function(module) {
34353
+
34354
+ if (!nodeEntries.hasOwnProperty(module)) {
34355
+ nodeEntries[module] = {info:RED.plugins.getModule(module)};
34356
+ var index = [module];
34357
+ for (var s in nodeEntries[module].info.sets) {
34358
+ if (nodeEntries[module].info.sets.hasOwnProperty(s)) {
34359
+ index.push(s);
34360
+ index = index.concat(nodeEntries[module].info.sets[s].types)
34361
+ }
34362
+ }
34363
+ nodeEntries[module].index = index.join(",").toLowerCase();
34364
+ nodeList.editableList('addItem', nodeEntries[module]);
34365
+ } else {
34366
+ _refreshNodeModule(module);
34367
+ }
34368
+
34369
+ for (var i=0;i<filteredList.length;i++) {
34370
+ if (filteredList[i].info.id === module) {
34371
+ var installButton = filteredList[i].elements.installButton;
34372
+ installButton.addClass('disabled');
34373
+ installButton.text(RED._('palette.editor.installed'));
34374
+ break;
34375
+ }
34376
+ }
34377
+ });
33371
34378
  }
33372
34379
 
33373
34380
  var settingsPane;
@@ -33494,6 +34501,7 @@ RED.palette.editor = (function() {
33494
34501
  errorRow: errorRow,
33495
34502
  errorList: errorList,
33496
34503
  setCount: setCount,
34504
+ setButton: setButton,
33497
34505
  container: container,
33498
34506
  shade: shade,
33499
34507
  versionSpan: versionSpan,
@@ -33504,49 +34512,88 @@ RED.palette.editor = (function() {
33504
34512
  if (container.hasClass('expanded')) {
33505
34513
  container.removeClass('expanded');
33506
34514
  contentRow.slideUp();
34515
+ setTimeout(() => {
34516
+ contentRow.empty()
34517
+ }, 200)
34518
+ object.elements.sets = {}
33507
34519
  } else {
33508
34520
  container.addClass('expanded');
34521
+ populateSetList()
33509
34522
  contentRow.slideDown();
33510
34523
  }
33511
34524
  })
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}));
34525
+ const populateSetList = function () {
34526
+ var setList = Object.keys(entry.sets)
34527
+ setList.sort(function(A,B) {
34528
+ return A.toLowerCase().localeCompare(B.toLowerCase());
34529
+ });
34530
+ setList.forEach(function(setName) {
34531
+ var set = entry.sets[setName];
34532
+ var setRow = $('<div>',{class:"red-ui-palette-module-set"}).appendTo(contentRow);
34533
+ var buttonGroup = $('<div>',{class:"red-ui-palette-module-set-button-group"}).appendTo(setRow);
34534
+ var typeSwatches = {};
34535
+ let enableButton;
34536
+ if (set.types) {
34537
+ set.types.forEach(function(t) {
34538
+ var typeDiv = $('<div>',{class:"red-ui-palette-module-type"}).appendTo(setRow);
34539
+ typeSwatches[t] = $('<span>',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv);
34540
+ if (set.enabled) {
34541
+ var def = RED.nodes.getType(t);
34542
+ if (def && def.color) {
34543
+ typeSwatches[t].css({background:RED.utils.getNodeColor(t,def)});
34544
+ typeSwatches[t].css({border: "1px solid "+getContrastingBorder(typeSwatches[t].css('backgroundColor'))})
33538
34545
  }
33539
34546
  }
33540
- });
34547
+ $('<span>',{class:"red-ui-palette-module-type-node"}).text(t).appendTo(typeDiv);
34548
+ })
34549
+ enableButton = $('<a href="#" class="red-ui-button red-ui-button-small"></a>').appendTo(buttonGroup);
34550
+ enableButton.on("click", function(evt) {
34551
+ evt.preventDefault();
34552
+ if (object.setUseCount[setName] === 0) {
34553
+ var currentSet = RED.nodes.registry.getNodeSet(set.id);
34554
+ shade.show();
34555
+ var newState = !currentSet.enabled
34556
+ changeNodeState(set.id,newState,shade,function(xhr){
34557
+ if (xhr) {
34558
+ if (xhr.responseJSON) {
34559
+ RED.notify(RED._('palette.editor.errors.'+(newState?'enable':'disable')+'Failed',{module: id,message:xhr.responseJSON.message}));
34560
+ }
34561
+ }
34562
+ });
34563
+ }
34564
+ })
34565
+
34566
+ if (object.setUseCount[setName] > 0) {
34567
+ enableButton.text(RED._('palette.editor.inuse'));
34568
+ enableButton.addClass('disabled');
34569
+ } else {
34570
+ enableButton.removeClass('disabled');
34571
+ if (set.enabled) {
34572
+ enableButton.text(RED._('palette.editor.disable'));
34573
+ } else {
34574
+ enableButton.text(RED._('palette.editor.enable'));
34575
+ }
34576
+ }
34577
+ setRow.toggleClass("red-ui-palette-module-set-disabled",!set.enabled);
34578
+
34579
+
34580
+ }
34581
+ if (set.plugins) {
34582
+ set.plugins.forEach(function(p) {
34583
+ var typeDiv = $('<div>',{class:"red-ui-palette-module-type"}).appendTo(setRow);
34584
+ // typeSwatches[p.id] = $('<span>',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv);
34585
+ $('<span><i class="fa fa-puzzle-piece" aria-hidden="true"></i> </span>',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv);
34586
+ $('<span>',{class:"red-ui-palette-module-type-node"}).text(p.id).appendTo(typeDiv);
34587
+ })
33541
34588
  }
33542
- })
33543
34589
 
33544
- object.elements.sets[set.name] = {
33545
- setRow: setRow,
33546
- enableButton: enableButton,
33547
- swatches: typeSwatches
33548
- };
33549
- });
34590
+ object.elements.sets[set.name] = {
34591
+ setRow: setRow,
34592
+ enableButton: enableButton,
34593
+ swatches: typeSwatches
34594
+ };
34595
+ });
34596
+ }
33550
34597
  enableButton.on("click", function(evt) {
33551
34598
  evt.preventDefault();
33552
34599
  if (object.totalUseCount === 0) {
@@ -33916,7 +34963,55 @@ RED.palette.editor = (function() {
33916
34963
  }
33917
34964
  }
33918
34965
  ]
33919
- }); }
34966
+ });
34967
+ }
34968
+ } else {
34969
+ // dedicated list management for plugins
34970
+ if (entry.plugin) {
34971
+
34972
+ let e = nodeEntries[entry.name];
34973
+ if (e) {
34974
+ nodeList.editableList('removeItem', e);
34975
+ delete nodeEntries[entry.name];
34976
+ }
34977
+
34978
+ // We assume that a plugin that implements onremove
34979
+ // cleans the editor accordingly of its left-overs.
34980
+ let found_onremove = true;
34981
+
34982
+ let keys = Object.keys(entry.sets);
34983
+ keys.forEach((key) => {
34984
+ let set = entry.sets[key];
34985
+ for (let i=0; i<set.plugins?.length; i++) {
34986
+ let plgn = RED.plugins.getPlugin(set.plugins[i].id);
34987
+ if (plgn && plgn.onremove && typeof plgn.onremove === 'function') {
34988
+ plgn.onremove();
34989
+ } else {
34990
+ if (plgn && plgn.onadd && typeof plgn.onadd === 'function') {
34991
+ // if there's no 'onadd', there shouldn't be any left-overs
34992
+ found_onremove = false;
34993
+ }
34994
+ }
34995
+ }
34996
+ });
34997
+
34998
+ if (!found_onremove) {
34999
+ let removeNotify = RED.notify(RED._("palette.editor.confirm.removePlugin.body",{module:entry.name}),{
35000
+ modal: true,
35001
+ fixed: true,
35002
+ type: 'warning',
35003
+ buttons: [
35004
+ {
35005
+ text: RED._("palette.editor.confirm.button.understood"),
35006
+ class:"primary",
35007
+ click: function(e) {
35008
+ removeNotify.close();
35009
+ }
35010
+ }
35011
+ ]
35012
+ });
35013
+ }
35014
+ }
33920
35015
  }
33921
35016
  })
33922
35017
  notification.close();
@@ -33960,9 +35055,28 @@ RED.palette.editor = (function() {
33960
35055
  RED.actions.invoke("core:show-event-log");
33961
35056
  });
33962
35057
  RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install")+" : "+entry.id+" "+entry.version);
33963
- installNodeModule(entry.id,entry.version,entry.pkg_url,function(xhr) {
35058
+ installNodeModule(entry.id,entry.version,entry.pkg_url,function(xhr, textStatus,err) {
33964
35059
  spinner.remove();
33965
- if (xhr) {
35060
+ if (err && xhr.status === 504) {
35061
+ var notification = RED.notify(RED._("palette.editor.errors.installTimeout"), {
35062
+ modal: true,
35063
+ fixed: true,
35064
+ buttons: [
35065
+ {
35066
+ text: RED._("common.label.close"),
35067
+ click: function() {
35068
+ notification.close();
35069
+ }
35070
+ },{
35071
+ text: RED._("eventLog.view"),
35072
+ click: function() {
35073
+ notification.close();
35074
+ RED.actions.invoke("core:show-event-log");
35075
+ }
35076
+ }
35077
+ ]
35078
+ })
35079
+ } else if (xhr) {
33966
35080
  if (xhr.responseJSON) {
33967
35081
  var notification = RED.notify(RED._('palette.editor.errors.installFailed',{module: entry.id,message:xhr.responseJSON.message}),{
33968
35082
  type: 'error',
@@ -34346,8 +35460,9 @@ RED.editor = (function() {
34346
35460
  nodeValue = node[property]
34347
35461
  }
34348
35462
 
34349
- const buttonId = `${prefix}-lookup-${property}`
34350
- const selectId = prefix + '-' + property
35463
+ const addBtnId = `${prefix}-btn-${property}-add`;
35464
+ const editBtnId = `${prefix}-btn-${property}-edit`;
35465
+ const selectId = prefix + '-' + property;
34351
35466
  const input = $(`#${selectId}`);
34352
35467
  if (input.length === 0) {
34353
35468
  return;
@@ -34370,40 +35485,68 @@ RED.editor = (function() {
34370
35485
  select.css({
34371
35486
  'flex-grow': 1
34372
35487
  });
35488
+
34373
35489
  updateConfigNodeSelect(property, type, nodeValue, prefix, filter);
34374
- const disableButton = function(disabled) {
34375
- btn.prop( "disabled", !!disabled)
34376
- btn.toggleClass("disabled", !!disabled)
34377
- }
35490
+
34378
35491
  // create the edit button
34379
- const btn = $('<a id="' + buttonId + '" class="red-ui-button"><i class="fa fa-pencil"></i></a>')
35492
+ const editButton = $('<a id="' + editBtnId + '" class="red-ui-button"><i class="fa fa-pencil"></i></a>')
34380
35493
  .css({ "margin-left": "10px" })
34381
35494
  .appendTo(outerWrap);
34382
35495
 
35496
+ RED.popover.tooltip(editButton, RED._('editor.editConfig', { type }));
35497
+
35498
+ // create the add button
35499
+ const addButton = $('<a id="' + addBtnId + '" class="red-ui-button"><i class="fa fa-plus"></i></a>')
35500
+ .css({ "margin-left": "10px" })
35501
+ .appendTo(outerWrap);
35502
+ RED.popover.tooltip(addButton, RED._('editor.addNewConfig', { type }));
35503
+
35504
+ const disableButton = function(button, disabled) {
35505
+ $(button).prop("disabled", !!disabled)
35506
+ $(button).toggleClass("disabled", !!disabled)
35507
+ };
35508
+
34383
35509
  // add the click handler
34384
- btn.on("click", function (e) {
35510
+ addButton.on("click", function (e) {
35511
+ if (addButton.prop("disabled")) { return }
35512
+ showEditConfigNodeDialog(property, type, "_ADD_", prefix, node);
35513
+ e.preventDefault();
35514
+ });
35515
+ editButton.on("click", function (e) {
34385
35516
  const selectedOpt = select.find(":selected")
34386
35517
  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 }
35518
+ if (editButton.prop("disabled")) { return }
34388
35519
  showEditConfigNodeDialog(property, type, selectedOpt.val(), prefix, node);
34389
35520
  e.preventDefault();
34390
35521
  });
34391
35522
 
34392
35523
  // dont permit the user to click the button if the selected option is an env var
34393
35524
  select.on("change", function () {
34394
- const selectedOpt = select.find(":selected")
35525
+ const selectedOpt = select.find(":selected");
35526
+ const optionsLength = select.find("option").length;
34395
35527
  if (selectedOpt?.data('env')) {
34396
- disableButton(true)
35528
+ disableButton(addButton, true);
35529
+ disableButton(editButton, true);
35530
+ // disable the edit button if no options available
35531
+ } else if (optionsLength === 1 && selectedOpt.val() === "_ADD_") {
35532
+ disableButton(addButton, false);
35533
+ disableButton(editButton, true);
35534
+ } else if (selectedOpt.val() === "") {
35535
+ disableButton(addButton, false);
35536
+ disableButton(editButton, true);
34397
35537
  } else {
34398
- disableButton(false)
35538
+ disableButton(addButton, false);
35539
+ disableButton(editButton, false);
34399
35540
  }
34400
35541
  });
35542
+
34401
35543
  var label = "";
34402
35544
  var configNode = RED.nodes.node(nodeValue);
34403
35545
 
34404
35546
  if (configNode) {
34405
35547
  label = RED.utils.getNodeLabel(configNode, configNode.id);
34406
35548
  }
35549
+
34407
35550
  input.val(label);
34408
35551
  }
34409
35552
 
@@ -34897,7 +36040,12 @@ RED.editor = (function() {
34897
36040
  }
34898
36041
  }
34899
36042
 
34900
- select.append('<option value="_ADD_"'+(value===""?" selected":"")+'>'+RED._("editor.addNewType", {type:label})+'</option>');
36043
+ if (!configNodes.length) {
36044
+ select.append('<option value="_ADD_" selected>' + RED._("editor.addNewType", { type: label }) + '</option>');
36045
+ } else {
36046
+ select.append('<option value="">' + RED._("editor.inputs.none") + '</option>');
36047
+ }
36048
+
34901
36049
  window.setTimeout(function() { select.trigger("change");},50);
34902
36050
  }
34903
36051
  }
@@ -35692,8 +36840,8 @@ RED.editor = (function() {
35692
36840
  }
35693
36841
 
35694
36842
  if (!isSameObj(old_env, new_env)) {
35695
- editing_node.env = new_env;
35696
36843
  editState.changes.env = editing_node.env;
36844
+ editing_node.env = new_env;
35697
36845
  editState.changed = true;
35698
36846
  }
35699
36847
 
@@ -40936,7 +42084,7 @@ RED.editor.codeEditor.monaco = (function() {
40936
42084
  _monaco.languages.json.jsonDefaults.setDiagnosticsOptions(diagnosticOptions);
40937
42085
  if(modeConfiguration) { _monaco.languages.json.jsonDefaults.setModeConfiguration(modeConfiguration); }
40938
42086
  } catch (error) {
40939
- console.warn("monaco - Error setting up json options", err)
42087
+ console.warn("monaco - Error setting up json options", error)
40940
42088
  }
40941
42089
  }
40942
42090
 
@@ -40948,7 +42096,7 @@ RED.editor.codeEditor.monaco = (function() {
40948
42096
  if(htmlDefaults) { _monaco.languages.html.htmlDefaults.setOptions(htmlDefaults); }
40949
42097
  if(handlebarDefaults) { _monaco.languages.html.handlebarDefaults.setOptions(handlebarDefaults); }
40950
42098
  } catch (error) {
40951
- console.warn("monaco - Error setting up html options", err)
42099
+ console.warn("monaco - Error setting up html options", error)
40952
42100
  }
40953
42101
  }
40954
42102
 
@@ -40968,7 +42116,7 @@ RED.editor.codeEditor.monaco = (function() {
40968
42116
  if(lessDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(lessDefaults_modeConfiguration); }
40969
42117
  if(scssDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(scssDefaults_modeConfiguration); }
40970
42118
  } catch (error) {
40971
- console.warn("monaco - Error setting up CSS/SCSS/LESS options", err)
42119
+ console.warn("monaco - Error setting up CSS/SCSS/LESS options", error)
40972
42120
  }
40973
42121
  }
40974
42122
 
@@ -41007,7 +42155,7 @@ RED.editor.codeEditor.monaco = (function() {
41007
42155
  createMonacoCompletionItem("set (flow context)", 'flow.set("${1:name}", ${1:value});','Set a value in flow context',range),
41008
42156
  createMonacoCompletionItem("get (global context)", 'global.get("${1:name}");','Get a value from global context',range),
41009
42157
  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),
42158
+ 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
42159
  createMonacoCompletionItem("cloneMessage (RED.util)", 'RED.util.cloneMessage(${1:msg});',
41012
42160
  ["```typescript",
41013
42161
  "RED.util.cloneMessage<T extends registry.NodeMessage>(msg: T): T",
@@ -42262,6 +43410,7 @@ RED.eventLog = (function() {
42262
43410
  setTimeout(function() {
42263
43411
  oldTray.tray.detach();
42264
43412
  showTray(options);
43413
+ RED.events.emit('editor:change')
42265
43414
  },250)
42266
43415
  } else {
42267
43416
  if (stack.length > 0) {
@@ -42331,6 +43480,7 @@ RED.eventLog = (function() {
42331
43480
  RED.view.focus();
42332
43481
  } else {
42333
43482
  stack[stack.length-1].tray.css("z-index", "auto");
43483
+ RED.events.emit('editor:change')
42334
43484
  }
42335
43485
  },250)
42336
43486
  }
@@ -44784,12 +45934,12 @@ RED.notifications = (function() {
44784
45934
  if (newType) {
44785
45935
  n.className = "red-ui-notification red-ui-notification-"+newType;
44786
45936
  }
44787
-
45937
+ newTimeout = newOptions.hasOwnProperty('timeout')?newOptions.timeout:timeout
44788
45938
  if (!fixed || newOptions.fixed === false) {
44789
- newTimeout = (newOptions.hasOwnProperty('timeout')?newOptions.timeout:timeout)||5000;
45939
+ newTimeout = newTimeout || 5000
44790
45940
  }
44791
45941
  if (newOptions.buttons) {
44792
- var buttonSet = $('<div style="margin-top: 20px;" class="ui-dialog-buttonset"></div>').appendTo(nn)
45942
+ var buttonSet = $('<div class="ui-dialog-buttonset"></div>').appendTo(nn)
44793
45943
  newOptions.buttons.forEach(function(buttonDef) {
44794
45944
  var b = $('<button>').text(buttonDef.text).on("click", buttonDef.click).appendTo(buttonSet);
44795
45945
  if (buttonDef.id) {
@@ -44835,6 +45985,15 @@ RED.notifications = (function() {
44835
45985
  };
44836
45986
  })());
44837
45987
  n.timeoutid = window.setTimeout(n.close,timeout||5000);
45988
+ } else if (timeout) {
45989
+ $(n).on("click.red-ui-notification-close", (function() {
45990
+ var nn = n;
45991
+ return function() {
45992
+ nn.hideNotification();
45993
+ window.clearTimeout(nn.timeoutid);
45994
+ };
45995
+ })());
45996
+ n.timeoutid = window.setTimeout(n.hideNotification,timeout||5000);
44838
45997
  }
44839
45998
  currentNotifications.push(n);
44840
45999
  if (options.id) {
@@ -45684,10 +46843,16 @@ RED.search = (function() {
45684
46843
  onselect: 'core:split-wire-with-link-nodes',
45685
46844
  disabled: !canEdit || !hasLinks
45686
46845
  },
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') }
46846
+ null
45690
46847
  )
46848
+ if (RED.settings.theme("menu.menu-item-import-library", true)) {
46849
+ insertOptions.push(
46850
+ { onselect: 'core:show-import-dialog', label: RED._('common.label.import')},
46851
+ { onselect: 'core:show-examples-import-dialog', label: RED._('menu.label.importExample') }
46852
+ )
46853
+ }
46854
+
46855
+
45691
46856
  if (hasSelection && canEdit) {
45692
46857
  const nodeOptions = []
45693
46858
  if (!hasMultipleSelection && !isGroup) {
@@ -45760,8 +46925,14 @@ RED.search = (function() {
45760
46925
  { onselect: 'core:paste-from-internal-clipboard', label: RED._("keyboard.pasteNode"), disabled: !canEdit || !RED.view.clipboard() },
45761
46926
  { onselect: 'core:delete-selection', label: RED._('keyboard.deleteSelected'), disabled: !canEdit || !canDelete },
45762
46927
  { 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") },
46928
+ )
46929
+ if (RED.settings.theme("menu.menu-item-export-library", true)) {
46930
+ menuItems.push(
46931
+ { onselect: 'core:show-export-dialog', label: RED._("menu.label.export") }
46932
+ )
46933
+ }
46934
+ menuItems.push(
46935
+ { onselect: 'core:select-all-nodes', label: RED._("keyboard.selectAll") }
45765
46936
  )
45766
46937
  }
45767
46938
 
@@ -47772,14 +48943,20 @@ RED.subflow = (function() {
47772
48943
  var nodePropValue = nodeProp;
47773
48944
  if (prop.ui && prop.ui.type === "cred") {
47774
48945
  nodePropType = "cred";
48946
+ } else if (prop.ui && prop.ui.type === "conf-types") {
48947
+ nodePropType = prop.value.type
47775
48948
  } else {
47776
48949
  switch(typeof nodeProp) {
47777
48950
  case "string": nodePropType = "str"; break;
47778
48951
  case "number": nodePropType = "num"; break;
47779
48952
  case "boolean": nodePropType = "bool"; nodePropValue = nodeProp?"true":"false"; break;
47780
48953
  default:
47781
- nodePropType = nodeProp.type;
47782
- nodePropValue = nodeProp.value;
48954
+ if (nodeProp) {
48955
+ nodePropType = nodeProp.type;
48956
+ nodePropValue = nodeProp.value;
48957
+ } else {
48958
+ nodePropType = 'str'
48959
+ }
47783
48960
  }
47784
48961
  }
47785
48962
  var item = {
@@ -47849,7 +49026,7 @@ RED.subflow = (function() {
47849
49026
  break;
47850
49027
  case "conf-types":
47851
49028
  item.value = input.val()
47852
- item.type = data.parent.value;
49029
+ item.type = "conf-type"
47853
49030
  }
47854
49031
  if (ui.type === "cred" || item.type !== data.parent.type || item.value !== data.parent.value) {
47855
49032
  env.push(item);
@@ -51702,7 +52879,7 @@ RED.projects.settings = (function() {
51702
52879
  var notInstalledCount = 0;
51703
52880
 
51704
52881
  for (var m in modulesInUse) {
51705
- if (modulesInUse.hasOwnProperty(m)) {
52882
+ if (modulesInUse.hasOwnProperty(m) && !activeProject.dependencies.hasOwnProperty(m)) {
51706
52883
  depsList.editableList('addItem',{
51707
52884
  id: modulesInUse[m].module,
51708
52885
  version: modulesInUse[m].version,
@@ -51722,8 +52899,8 @@ RED.projects.settings = (function() {
51722
52899
 
51723
52900
  if (activeProject.dependencies) {
51724
52901
  for (var m in activeProject.dependencies) {
51725
- if (activeProject.dependencies.hasOwnProperty(m) && !modulesInUse.hasOwnProperty(m)) {
51726
- var installed = !!RED.nodes.registry.getModule(m);
52902
+ if (activeProject.dependencies.hasOwnProperty(m)) {
52903
+ var installed = !!RED.nodes.registry.getModule(m) && activeProject.dependencies[m] === modulesInUse[m].version;
51727
52904
  depsList.editableList('addItem',{
51728
52905
  id: m,
51729
52906
  version: activeProject.dependencies[m], //RED.nodes.registry.getModule(module).version,