@node-red/editor-client 3.0.0-beta.3 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/public/red/red.js CHANGED
@@ -388,6 +388,10 @@ var RED = (function() {
388
388
  // handled below
389
389
  return;
390
390
  }
391
+ if (notificationId === "flows-run-state") {
392
+ // handled in editor-client/src/js/runtime.js
393
+ return;
394
+ }
391
395
  if (notificationId === "project-update") {
392
396
  loader.start(RED._("event.loadingProject"), 0);
393
397
  RED.nodes.clear();
@@ -423,7 +427,6 @@ var RED = (function() {
423
427
  id: notificationId
424
428
  }
425
429
  if (notificationId === "runtime-state") {
426
- RED.events.emit("runtime-state",msg);
427
430
  if (msg.error === "safe-mode") {
428
431
  options.buttons = [
429
432
  {
@@ -564,9 +567,9 @@ var RED = (function() {
564
567
  } else if (persistentNotifications.hasOwnProperty(notificationId)) {
565
568
  persistentNotifications[notificationId].close();
566
569
  delete persistentNotifications[notificationId];
567
- if (notificationId === 'runtime-state') {
568
- RED.events.emit("runtime-state",msg);
569
- }
570
+ }
571
+ if (notificationId === 'runtime-state') {
572
+ RED.events.emit("runtime-state",msg);
570
573
  }
571
574
  });
572
575
  RED.comms.subscribe("status/#",function(topic,msg) {
@@ -838,6 +841,7 @@ var RED = (function() {
838
841
  RED.keyboard.init(buildMainMenu);
839
842
 
840
843
  RED.nodes.init();
844
+ RED.runtime.init()
841
845
  RED.comms.connect();
842
846
 
843
847
  $("#red-ui-main-container").show();
@@ -853,7 +857,7 @@ var RED = (function() {
853
857
  $('<div id="red-ui-header-shade" class="hide"></div>').appendTo(header);
854
858
  $('<div id="red-ui-main-container" class="red-ui-sidebar-closed hide">'+
855
859
  '<div id="red-ui-workspace"></div>'+
856
- '<div id="red-ui-editor-stack"></div>'+
860
+ '<div id="red-ui-editor-stack" tabindex="-1"></div>'+
857
861
  '<div id="red-ui-palette"></div>'+
858
862
  '<div id="red-ui-sidebar"></div>'+
859
863
  '<div id="red-ui-sidebar-separator"></div>'+
@@ -2080,6 +2084,42 @@ RED.comms = (function() {
2080
2084
  unsubscribe:unsubscribe
2081
2085
  }
2082
2086
  })();
2087
+ ;RED.runtime = (function() {
2088
+ let state = ""
2089
+ let settings = { ui: false, enabled: false };
2090
+ const STOPPED = "stop"
2091
+ const STARTED = "start"
2092
+ const SAFE = "safe"
2093
+
2094
+ return {
2095
+ init: function() {
2096
+ // refresh the current runtime status from server
2097
+ settings = Object.assign({}, settings, RED.settings.runtimeState);
2098
+ RED.events.on("runtime-state", function(msg) {
2099
+ if (msg.state) {
2100
+ const currentState = state
2101
+ state = msg.state
2102
+ $(".red-ui-flow-node-button").toggleClass("red-ui-flow-node-button-stopped", state !== STARTED)
2103
+ if(settings.enabled === true && settings.ui === true) {
2104
+ RED.menu.setVisible("deploymenu-item-runtime-stop", state === STARTED)
2105
+ RED.menu.setVisible("deploymenu-item-runtime-start", state !== STARTED)
2106
+ }
2107
+ // Do not notify the user about this event if:
2108
+ // - This is the very first event we've received after loading the editor (currentState = '')
2109
+ // - The state matches what we already thought was the case (state === currentState)
2110
+ // - The event was triggered by a deploy (msg.deploy === true)
2111
+ // - The event is a safe mode event - that gets notified separately
2112
+ if (currentState !== '' && state !== currentState && !msg.deploy && state !== SAFE) {
2113
+ RED.notify(RED._("notification.state.flows"+(state === STOPPED?'Stopped':'Started'), msg), "success")
2114
+ }
2115
+ }
2116
+ });
2117
+ },
2118
+ get started() {
2119
+ return state === STARTED
2120
+ }
2121
+ }
2122
+ })()
2083
2123
  ;/**
2084
2124
  * Copyright JS Foundation and other contributors, http://js.foundation
2085
2125
  *
@@ -3636,7 +3676,7 @@ RED.state = {
3636
3676
  * limitations under the License.
3637
3677
  **/
3638
3678
 
3639
- /**
3679
+ /**
3640
3680
  * An Interface to nodes and utility functions for creating/adding/deleting nodes and links
3641
3681
  * @namespace RED.nodes
3642
3682
  */
@@ -5276,21 +5316,20 @@ RED.nodes = (function() {
5276
5316
  * Options:
5277
5317
  * - generateIds - whether to replace all node ids
5278
5318
  * - addFlow - whether to import nodes to a new tab
5279
- * - importToCurrent
5319
+ * - reimport - if node has a .z property, dont overwrite it
5320
+ * Only applicible when `generateIds` is false
5280
5321
  * - importMap - how to resolve any conflicts.
5281
5322
  * - id:import - import as-is
5282
5323
  * - id:copy - import with new id
5283
5324
  * - id:replace - import over the top of existing
5284
5325
  */
5285
5326
  function importNodes(newNodesObj,options) { // createNewIds,createMissingWorkspace) {
5286
- options = options || {
5287
- generateIds: false,
5288
- addFlow: false,
5289
- }
5290
- options.importMap = options.importMap || {};
5291
-
5292
- var createNewIds = options.generateIds;
5293
- var createMissingWorkspace = options.addFlow;
5327
+ const defOpts = { generateIds: false, addFlow: false, reimport: false, importMap: {} }
5328
+ options = Object.assign({}, defOpts, options)
5329
+ options.importMap = options.importMap || {}
5330
+ const createNewIds = options.generateIds;
5331
+ const reimport = (!createNewIds && !!options.reimport)
5332
+ const createMissingWorkspace = options.addFlow;
5294
5333
  var i;
5295
5334
  var n;
5296
5335
  var newNodes;
@@ -5591,7 +5630,8 @@ RED.nodes = (function() {
5591
5630
  }
5592
5631
  }
5593
5632
  } else {
5594
- if (n.z && !workspace_map[n.z] && !subflow_map[n.z]) {
5633
+ const keepNodesCurrentZ = reimport && n.z && RED.workspaces.contains(n.z)
5634
+ if (!keepNodesCurrentZ && n.z && !workspace_map[n.z] && !subflow_map[n.z]) {
5595
5635
  n.z = activeWorkspace;
5596
5636
  }
5597
5637
  }
@@ -5692,7 +5732,8 @@ RED.nodes = (function() {
5692
5732
  node.id = getID();
5693
5733
  } else {
5694
5734
  node.id = n.id;
5695
- if (node.z == null || (!workspace_map[node.z] && !subflow_map[node.z])) {
5735
+ const keepNodesCurrentZ = reimport && node.z && RED.workspaces.contains(node.z)
5736
+ if (!keepNodesCurrentZ && (node.z == null || (!workspace_map[node.z] && !subflow_map[node.z]))) {
5696
5737
  if (createMissingWorkspace) {
5697
5738
  if (missingWorkspace === null) {
5698
5739
  missingWorkspace = RED.workspaces.add(null,true);
@@ -6391,7 +6432,7 @@ RED.nodes = (function() {
6391
6432
  // Force the redraw to be synchronous so the view updates
6392
6433
  // *now* and removes the unknown node
6393
6434
  RED.view.redraw(true, true);
6394
- var result = importNodes(reimportList,{generateIds:false});
6435
+ var result = importNodes(reimportList,{generateIds:false, reimport: true});
6395
6436
  var newNodeMap = {};
6396
6437
  result.nodes.forEach(function(n) {
6397
6438
  newNodeMap[n.id] = n;
@@ -10080,6 +10121,7 @@ RED.utils = (function() {
10080
10121
  * - multi : boolean - if true, .selected will return an array of results
10081
10122
  * otherwise, returns the first selected item
10082
10123
  * - sortable: boolean/string - TODO: see editableList
10124
+ * - selectable: boolean - default true - whether individual items can be selected
10083
10125
  * - rootSortable: boolean - if 'sortable' is set, then setting this to
10084
10126
  * false, prevents items being sorted to the
10085
10127
  * top level of the tree
@@ -10177,6 +10219,7 @@ RED.utils = (function() {
10177
10219
  switch(evt.keyCode) {
10178
10220
  case 32: // SPACE
10179
10221
  case 13: // ENTER
10222
+ if (!that.options.selectable) { return }
10180
10223
  if (evt.altKey || evt.ctrlKey || evt.metaKey || evt.shiftKey) {
10181
10224
  return
10182
10225
  }
@@ -11281,6 +11324,8 @@ RED.menu = (function() {
11281
11324
  if (opt.options) {
11282
11325
  item.addClass("red-ui-menu-dropdown-submenu"+(opt.direction!=='right'?" pull-left":""));
11283
11326
  var submenu = $('<ul id="'+opt.id+'-submenu" class="red-ui-menu-dropdown"></ul>').appendTo(item);
11327
+ var hasIcons = false
11328
+ var hasSubmenus = false
11284
11329
 
11285
11330
  for (var i=0;i<opt.options.length;i++) {
11286
11331
 
@@ -11292,6 +11337,8 @@ RED.menu = (function() {
11292
11337
  opt.options[i].onpostselect = opt.onpostselect
11293
11338
  }
11294
11339
  opt.options[i].direction = opt.direction
11340
+ hasIcons = hasIcons || (opt.options[i].icon);
11341
+ hasSubmenus = hasSubmenus || (opt.options[i].options);
11295
11342
  }
11296
11343
 
11297
11344
  var li = createMenuItem(opt.options[i]);
@@ -11299,10 +11346,21 @@ RED.menu = (function() {
11299
11346
  li.appendTo(submenu);
11300
11347
  }
11301
11348
  }
11349
+ if (!hasIcons) {
11350
+ submenu.addClass("red-ui-menu-dropdown-noicons")
11351
+ }
11352
+ if (hasSubmenus) {
11353
+ submenu.addClass("red-ui-menu-dropdown-submenus")
11354
+ }
11355
+
11356
+
11302
11357
  }
11303
11358
  if (opt.disabled) {
11304
11359
  item.addClass("disabled");
11305
11360
  }
11361
+ if (opt.visible === false) {
11362
+ item.addClass("hide");
11363
+ }
11306
11364
  }
11307
11365
 
11308
11366
 
@@ -11339,6 +11397,8 @@ RED.menu = (function() {
11339
11397
  }
11340
11398
 
11341
11399
  var lastAddedSeparator = false;
11400
+ var hasSubmenus = false;
11401
+ var hasIcons = false;
11342
11402
  for (var i=0;i<options.options.length;i++) {
11343
11403
  var opt = options.options[i];
11344
11404
  if (opt) {
@@ -11351,6 +11411,8 @@ RED.menu = (function() {
11351
11411
  opt.direction = options.direction || 'left'
11352
11412
  }
11353
11413
  if (opt !== null || !lastAddedSeparator) {
11414
+ hasIcons = hasIcons || (opt && opt.icon);
11415
+ hasSubmenus = hasSubmenus || (opt && opt.options);
11354
11416
  var li = createMenuItem(opt);
11355
11417
  if (li) {
11356
11418
  li.appendTo(topMenu);
@@ -11358,7 +11420,12 @@ RED.menu = (function() {
11358
11420
  }
11359
11421
  }
11360
11422
  }
11361
-
11423
+ if (!hasIcons) {
11424
+ topMenu.addClass("red-ui-menu-dropdown-noicons")
11425
+ }
11426
+ if (hasSubmenus) {
11427
+ topMenu.addClass("red-ui-menu-dropdown-submenus")
11428
+ }
11362
11429
  return topMenu;
11363
11430
  }
11364
11431
 
@@ -11430,6 +11497,14 @@ RED.menu = (function() {
11430
11497
  }
11431
11498
  }
11432
11499
 
11500
+ function setVisible(id,state) {
11501
+ if (!state) {
11502
+ $("#"+id).parent().addClass("hide");
11503
+ } else {
11504
+ $("#"+id).parent().removeClass("hide");
11505
+ }
11506
+ }
11507
+
11433
11508
  function addItem(id,opt) {
11434
11509
  var item = createMenuItem(opt);
11435
11510
  if (opt !== null && opt.group) {
@@ -11486,6 +11561,7 @@ RED.menu = (function() {
11486
11561
  isSelected: isSelected,
11487
11562
  toggleSelected: toggleSelected,
11488
11563
  setDisabled: setDisabled,
11564
+ setVisible: setVisible,
11489
11565
  addItem: addItem,
11490
11566
  removeItem: removeItem,
11491
11567
  setAction: setAction,
@@ -13765,10 +13841,10 @@ RED.stack = (function() {
13765
13841
  optEl.append(generateSpans(srcMatch));
13766
13842
  optEl.appendTo(element);
13767
13843
  }
13768
- matches.push({
13769
- value: optVal,
13770
- label: element,
13771
- i: (valMatch.found ? valMatch.index : srcMatch.index)
13844
+ matches.push({
13845
+ value: optVal,
13846
+ label: element,
13847
+ i: (valMatch.found ? valMatch.index : srcMatch.index)
13772
13848
  });
13773
13849
  }
13774
13850
  })
@@ -14176,7 +14252,7 @@ RED.stack = (function() {
14176
14252
  this.options.types = this.options.types||Object.keys(allOptions);
14177
14253
  }
14178
14254
 
14179
- this.selectTrigger = $('<button class="red-ui-typedInput-type-select" tabindex="0"></button>').prependTo(this.uiSelect);
14255
+ this.selectTrigger = $('<button type="button" class="red-ui-typedInput-type-select" tabindex="0"></button>').prependTo(this.uiSelect);
14180
14256
  $('<i class="red-ui-typedInput-icon fa fa-caret-down"></i>').toggle(this.options.types.length > 1).appendTo(this.selectTrigger);
14181
14257
 
14182
14258
  this.selectLabel = $('<span class="red-ui-typedInput-type-label"></span>').appendTo(this.selectTrigger);
@@ -14245,7 +14321,7 @@ RED.stack = (function() {
14245
14321
  })
14246
14322
 
14247
14323
  // explicitly set optionSelectTrigger display to inline-block otherwise jQ sets it to 'inline'
14248
- this.optionSelectTrigger = $('<button tabindex="0" class="red-ui-typedInput-option-trigger" style="display:inline-block"><span class="red-ui-typedInput-option-caret"><i class="red-ui-typedInput-icon fa fa-caret-down"></i></span></button>').appendTo(this.uiSelect);
14324
+ this.optionSelectTrigger = $('<button type="button" tabindex="0" class="red-ui-typedInput-option-trigger" style="display:inline-block"><span class="red-ui-typedInput-option-caret"><i class="red-ui-typedInput-icon fa fa-caret-down"></i></span></button>').appendTo(this.uiSelect);
14249
14325
  this.optionSelectLabel = $('<span class="red-ui-typedInput-option-label"></span>').prependTo(this.optionSelectTrigger);
14250
14326
  // RED.popover.tooltip(this.optionSelectLabel,function() {
14251
14327
  // return that.optionValue;
@@ -14266,7 +14342,7 @@ RED.stack = (function() {
14266
14342
  that.uiSelect.addClass('red-ui-typedInput-focus');
14267
14343
  });
14268
14344
 
14269
- this.optionExpandButton = $('<button tabindex="0" class="red-ui-typedInput-option-expand" style="display:inline-block"></button>').appendTo(this.uiSelect);
14345
+ this.optionExpandButton = $('<button type="button" tabindex="0" class="red-ui-typedInput-option-expand" style="display:inline-block"></button>').appendTo(this.uiSelect);
14270
14346
  this.optionExpandButtonIcon = $('<i class="red-ui-typedInput-icon fa fa-ellipsis-h"></i>').appendTo(this.optionExpandButton);
14271
14347
 
14272
14348
  this.type(this.typeField.val() || this.options.default||this.typeList[0].value);
@@ -15362,16 +15438,18 @@ RED.deploy = (function() {
15362
15438
  '</a>'+
15363
15439
  '<a id="red-ui-header-button-deploy-options" class="red-ui-deploy-button" href="#"><i class="fa fa-caret-down"></i></a>'+
15364
15440
  '</span></li>').prependTo(".red-ui-header-toolbar");
15365
- RED.menu.init({id:"red-ui-header-button-deploy-options",
15366
- options: [
15367
- {id:"deploymenu-item-full",toggle:"deploy-type",icon:"red/images/deploy-full.svg",label:RED._("deploy.full"),sublabel:RED._("deploy.fullDesc"),selected: true, onselect:function(s) { if(s){changeDeploymentType("full")}}},
15368
- {id:"deploymenu-item-flow",toggle:"deploy-type",icon:"red/images/deploy-flows.svg",label:RED._("deploy.modifiedFlows"),sublabel:RED._("deploy.modifiedFlowsDesc"), onselect:function(s) {if(s){changeDeploymentType("flows")}}},
15369
- {id:"deploymenu-item-node",toggle:"deploy-type",icon:"red/images/deploy-nodes.svg",label:RED._("deploy.modifiedNodes"),sublabel:RED._("deploy.modifiedNodesDesc"),onselect:function(s) { if(s){changeDeploymentType("nodes")}}},
15370
- null,
15371
- {id:"deploymenu-item-reload", icon:"red/images/deploy-reload.svg",label:RED._("deploy.restartFlows"),sublabel:RED._("deploy.restartFlowsDesc"),onselect:"core:restart-flows"},
15372
-
15373
- ]
15374
- });
15441
+ const mainMenuItems = [
15442
+ {id:"deploymenu-item-full",toggle:"deploy-type",icon:"red/images/deploy-full.svg",label:RED._("deploy.full"),sublabel:RED._("deploy.fullDesc"),selected: true, onselect:function(s) { if(s){changeDeploymentType("full")}}},
15443
+ {id:"deploymenu-item-flow",toggle:"deploy-type",icon:"red/images/deploy-flows.svg",label:RED._("deploy.modifiedFlows"),sublabel:RED._("deploy.modifiedFlowsDesc"), onselect:function(s) {if(s){changeDeploymentType("flows")}}},
15444
+ {id:"deploymenu-item-node",toggle:"deploy-type",icon:"red/images/deploy-nodes.svg",label:RED._("deploy.modifiedNodes"),sublabel:RED._("deploy.modifiedNodesDesc"),onselect:function(s) { if(s){changeDeploymentType("nodes")}}},
15445
+ null
15446
+ ]
15447
+ if (RED.settings.runtimeState && RED.settings.runtimeState.ui === true) {
15448
+ mainMenuItems.push({id:"deploymenu-item-runtime-start", icon:"red/images/start.svg",label:RED._("deploy.startFlows"),sublabel:RED._("deploy.startFlowsDesc"),onselect:"core:start-flows", visible:false})
15449
+ mainMenuItems.push({id:"deploymenu-item-runtime-stop", icon:"red/images/stop.svg",label:RED._("deploy.stopFlows"),sublabel:RED._("deploy.stopFlowsDesc"),onselect:"core:stop-flows", visible:false})
15450
+ }
15451
+ mainMenuItems.push({id:"deploymenu-item-reload", icon:"red/images/deploy-reload.svg",label:RED._("deploy.restartFlows"),sublabel:RED._("deploy.restartFlowsDesc"),onselect:"core:restart-flows"})
15452
+ RED.menu.init({id:"red-ui-header-button-deploy-options", options: mainMenuItems });
15375
15453
  } else if (type == "simple") {
15376
15454
  var label = options.label || RED._("deploy.deploy");
15377
15455
  var icon = 'red/images/deploy-full-o.svg';
@@ -15399,6 +15477,10 @@ RED.deploy = (function() {
15399
15477
 
15400
15478
  RED.actions.add("core:deploy-flows",save);
15401
15479
  if (type === "default") {
15480
+ if (RED.settings.runtimeState && RED.settings.runtimeState.ui === true) {
15481
+ RED.actions.add("core:stop-flows",function() { stopStartFlows("stop") });
15482
+ RED.actions.add("core:start-flows",function() { stopStartFlows("start") });
15483
+ }
15402
15484
  RED.actions.add("core:restart-flows",restart);
15403
15485
  RED.actions.add("core:set-deploy-type-to-full",function() { RED.menu.setSelected("deploymenu-item-full",true);});
15404
15486
  RED.actions.add("core:set-deploy-type-to-modified-flows",function() { RED.menu.setSelected("deploymenu-item-flow",true); });
@@ -15569,18 +15651,73 @@ RED.deploy = (function() {
15569
15651
  function sanitize(html) {
15570
15652
  return html.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")
15571
15653
  }
15572
- function restart() {
15573
- var startTime = Date.now();
15574
- $(".red-ui-deploy-button-content").css('opacity',0);
15575
- $(".red-ui-deploy-button-spinner").show();
15576
- var deployWasEnabled = !$("#red-ui-header-button-deploy").hasClass("disabled");
15577
- $("#red-ui-header-button-deploy").addClass("disabled");
15578
- deployInflight = true;
15654
+
15655
+ function shadeShow() {
15579
15656
  $("#red-ui-header-shade").show();
15580
15657
  $("#red-ui-editor-shade").show();
15581
15658
  $("#red-ui-palette-shade").show();
15582
15659
  $("#red-ui-sidebar-shade").show();
15583
-
15660
+ }
15661
+ function shadeHide() {
15662
+ $("#red-ui-header-shade").hide();
15663
+ $("#red-ui-editor-shade").hide();
15664
+ $("#red-ui-palette-shade").hide();
15665
+ $("#red-ui-sidebar-shade").hide();
15666
+ }
15667
+ function deployButtonSetBusy(){
15668
+ $(".red-ui-deploy-button-content").css('opacity',0);
15669
+ $(".red-ui-deploy-button-spinner").show();
15670
+ $("#red-ui-header-button-deploy").addClass("disabled");
15671
+ }
15672
+ function deployButtonClearBusy(){
15673
+ $(".red-ui-deploy-button-content").css('opacity',1);
15674
+ $(".red-ui-deploy-button-spinner").hide();
15675
+ }
15676
+ function stopStartFlows(state) {
15677
+ const startTime = Date.now()
15678
+ const deployWasEnabled = !$("#red-ui-header-button-deploy").hasClass("disabled")
15679
+ deployInflight = true
15680
+ deployButtonSetBusy()
15681
+ shadeShow()
15682
+ $.ajax({
15683
+ url:"flows/state",
15684
+ type: "POST",
15685
+ data: {state: state}
15686
+ }).done(function(data,textStatus,xhr) {
15687
+ if (deployWasEnabled) {
15688
+ $("#red-ui-header-button-deploy").removeClass("disabled")
15689
+ }
15690
+ }).fail(function(xhr,textStatus,err) {
15691
+ if (deployWasEnabled) {
15692
+ $("#red-ui-header-button-deploy").removeClass("disabled")
15693
+ }
15694
+ if (xhr.status === 401) {
15695
+ RED.notify(RED._("notification.error", { message: RED._("user.notAuthorized") }), "error")
15696
+ } else if (xhr.responseText) {
15697
+ const errorDetail = { message: err ? (err + "") : "" }
15698
+ try {
15699
+ errorDetail.message = JSON.parse(xhr.responseText).message
15700
+ } finally {
15701
+ errorDetail.message = errorDetail.message || xhr.responseText
15702
+ }
15703
+ RED.notify(RED._("notification.error", errorDetail), "error")
15704
+ } else {
15705
+ RED.notify(RED._("notification.error", { message: RED._("deploy.errors.noResponse") }), "error")
15706
+ }
15707
+ }).always(function() {
15708
+ const delta = Math.max(0, 300 - (Date.now() - startTime))
15709
+ setTimeout(function () {
15710
+ deployButtonClearBusy()
15711
+ shadeHide()
15712
+ deployInflight = false
15713
+ }, delta);
15714
+ });
15715
+ }
15716
+ function restart() {
15717
+ var startTime = Date.now();
15718
+ var deployWasEnabled = !$("#red-ui-header-button-deploy").hasClass("disabled");
15719
+ deployInflight = true;
15720
+ deployButtonSetBusy();
15584
15721
  $.ajax({
15585
15722
  url:"flows",
15586
15723
  type: "POST",
@@ -15606,15 +15743,10 @@ RED.deploy = (function() {
15606
15743
  RED.notify(RED._("deploy.deployFailed",{message:RED._("deploy.errors.noResponse")}),"error");
15607
15744
  }
15608
15745
  }).always(function() {
15609
- deployInflight = false;
15610
15746
  var delta = Math.max(0,300-(Date.now()-startTime));
15611
15747
  setTimeout(function() {
15612
- $(".red-ui-deploy-button-content").css('opacity',1);
15613
- $(".red-ui-deploy-button-spinner").hide();
15614
- $("#red-ui-header-shade").hide();
15615
- $("#red-ui-editor-shade").hide();
15616
- $("#red-ui-palette-shade").hide();
15617
- $("#red-ui-sidebar-shade").hide();
15748
+ deployButtonClearBusy();
15749
+ deployInflight = false;
15618
15750
  },delta);
15619
15751
  });
15620
15752
  }
@@ -15749,21 +15881,14 @@ RED.deploy = (function() {
15749
15881
  const nns = RED.nodes.createCompleteNodeSet();
15750
15882
  const startTime = Date.now();
15751
15883
 
15752
- $(".red-ui-deploy-button-content").css('opacity', 0);
15753
- $(".red-ui-deploy-button-spinner").show();
15754
- $("#red-ui-header-button-deploy").addClass("disabled");
15755
-
15884
+ deployButtonSetBusy();
15756
15885
  const data = { flows: nns };
15757
-
15758
15886
  if (!force) {
15759
15887
  data.rev = RED.nodes.version();
15760
15888
  }
15761
15889
 
15762
15890
  deployInflight = true;
15763
- $("#red-ui-header-shade").show();
15764
- $("#red-ui-editor-shade").show();
15765
- $("#red-ui-palette-shade").show();
15766
- $("#red-ui-sidebar-shade").show();
15891
+ shadeShow();
15767
15892
  $.ajax({
15768
15893
  url: "flows",
15769
15894
  type: "POST",
@@ -15849,15 +15974,11 @@ RED.deploy = (function() {
15849
15974
  RED.notify(RED._("deploy.deployFailed", { message: RED._("deploy.errors.noResponse") }), "error");
15850
15975
  }
15851
15976
  }).always(function () {
15852
- deployInflight = false;
15853
15977
  const delta = Math.max(0, 300 - (Date.now() - startTime));
15854
15978
  setTimeout(function () {
15855
- $(".red-ui-deploy-button-content").css('opacity', 1);
15856
- $(".red-ui-deploy-button-spinner").hide();
15857
- $("#red-ui-header-shade").hide();
15858
- $("#red-ui-editor-shade").hide();
15859
- $("#red-ui-palette-shade").hide();
15860
- $("#red-ui-sidebar-shade").hide();
15979
+ deployInflight = false;
15980
+ deployButtonClearBusy()
15981
+ shadeHide()
15861
15982
  }, delta);
15862
15983
  });
15863
15984
  }
@@ -18434,13 +18555,18 @@ RED.keyboard = (function() {
18434
18555
  if (partialState) {
18435
18556
  partialState = null;
18436
18557
  return resolveKeyEvent(evt);
18437
- } else if (Object.keys(handler).length > 0) {
18438
- partialState = handler;
18439
- evt.preventDefault();
18440
- return null;
18441
- } else {
18442
- return null;
18443
18558
  }
18559
+ if (Object.keys(handler).length > 0) {
18560
+ // check if there's a potential combined handler initiated by this keyCode
18561
+ for (let h in handler) {
18562
+ if (matchHandlerToEvent(evt,handler[h]) > -1) {
18563
+ partialState = handler;
18564
+ evt.preventDefault();
18565
+ break;
18566
+ }
18567
+ }
18568
+ }
18569
+ return null;
18444
18570
  } else {
18445
18571
  var depth = Infinity;
18446
18572
  var matchedHandler;
@@ -19146,9 +19272,22 @@ RED.workspaces = (function() {
19146
19272
  onselect: "core:show-last-hidden-flow"
19147
19273
  }
19148
19274
  ]
19149
- if (hideStack.length > 0) {
19275
+ let hiddenFlows = new Set()
19276
+ for (let i = 0; i < hideStack.length; i++) {
19277
+ let ids = hideStack[i]
19278
+ if (!Array.isArray(ids)) {
19279
+ ids = [ids]
19280
+ }
19281
+ ids.forEach(id => {
19282
+ if (RED.nodes.workspace(id)) {
19283
+ hiddenFlows.add(id)
19284
+ }
19285
+ })
19286
+ }
19287
+ const flowCount = hiddenFlows.size;
19288
+ if (flowCount > 0) {
19150
19289
  menuItems.unshift({
19151
- label: RED._("workspace.hiddenFlows",{count: hideStack.length}),
19290
+ label: RED._("workspace.hiddenFlows",{count: flowCount}),
19152
19291
  onselect: "core:list-hidden-flows"
19153
19292
  })
19154
19293
  }
@@ -19646,6 +19785,7 @@ RED.view = (function() {
19646
19785
  let flashingNodeId;
19647
19786
 
19648
19787
  var clipboard = "";
19788
+ let clipboardSource
19649
19789
 
19650
19790
  // Note: these are the permitted status colour aliases. The actual RGB values
19651
19791
  // are set in the CSS - flow.scss/colors.scss
@@ -20179,8 +20319,10 @@ RED.view = (function() {
20179
20319
  });
20180
20320
 
20181
20321
  RED.actions.add("core:copy-selection-to-internal-clipboard",copySelection);
20182
- RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection();deleteSelection();});
20183
- RED.actions.add("core:paste-from-internal-clipboard",function(){importNodes(clipboard,{generateIds: true, generateDefaultNames: true});});
20322
+ RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection(true);deleteSelection();});
20323
+ RED.actions.add("core:paste-from-internal-clipboard",function(){
20324
+ importNodes(clipboard,{generateIds: clipboardSource === 'copy', generateDefaultNames: clipboardSource === 'copy'});
20325
+ });
20184
20326
 
20185
20327
  RED.actions.add("core:detach-selected-nodes", function() { detachSelectedNodes() })
20186
20328
 
@@ -20539,6 +20681,7 @@ RED.view = (function() {
20539
20681
  if (RED.view.DEBUG) {
20540
20682
  console.warn("canvasMouseDown", { mouse_mode, point: d3.mouse(this), event: d3.event });
20541
20683
  }
20684
+ RED.contextMenu.hide();
20542
20685
  if (mouse_mode === RED.state.SELECTING_NODE) {
20543
20686
  d3.event.stopPropagation();
20544
20687
  return;
@@ -20618,12 +20761,15 @@ RED.view = (function() {
20618
20761
  RED.view.redraw();
20619
20762
  }
20620
20763
 
20764
+ // `point` is the place in the workspace the mouse has clicked.
20765
+ // This takes into account scrolling and scaling of the workspace.
20621
20766
  var ox = point[0];
20622
20767
  var oy = point[1];
20623
20768
 
20769
+ // Need to map that to browser location to position the pop-up
20624
20770
  const offset = $("#red-ui-workspace-chart").offset()
20625
- var clientX = ox + offset.left
20626
- var clientY = oy + offset.top
20771
+ var clientX = (ox * scaleFactor) + offset.left - $("#red-ui-workspace-chart").scrollLeft()
20772
+ var clientY = (oy * scaleFactor) + offset.top - $("#red-ui-workspace-chart").scrollTop()
20627
20773
 
20628
20774
  if (RED.settings.get("editor").view['view-snap-grid']) {
20629
20775
  // eventLayer.append("circle").attr("cx",point[0]).attr("cy",point[1]).attr("r","2").attr('fill','red')
@@ -21330,6 +21476,9 @@ RED.view = (function() {
21330
21476
  }
21331
21477
  var i;
21332
21478
  var historyEvent;
21479
+ if (d3.event.button === 2) {
21480
+ return
21481
+ }
21333
21482
  if (mouse_mode === RED.state.PANNING) {
21334
21483
  resetMouseVars();
21335
21484
  return
@@ -21696,6 +21845,9 @@ RED.view = (function() {
21696
21845
  }
21697
21846
  }
21698
21847
  if (mouse_mode == RED.state.IMPORT_DRAGGING) {
21848
+ if (clipboardSource === 'cut') {
21849
+ clipboardSource = 'copy'
21850
+ }
21699
21851
  updateActiveNodes();
21700
21852
  RED.nodes.dirty(true);
21701
21853
  }
@@ -22250,7 +22402,7 @@ RED.view = (function() {
22250
22402
  }
22251
22403
  }
22252
22404
 
22253
- function copySelection() {
22405
+ function copySelection(isCut) {
22254
22406
  if (mouse_mode === RED.state.SELECTING_NODE) {
22255
22407
  return;
22256
22408
  }
@@ -22314,6 +22466,7 @@ RED.view = (function() {
22314
22466
  }
22315
22467
  }
22316
22468
  clipboard = JSON.stringify(nns);
22469
+ clipboardSource = isCut ? 'cut' : 'copy'
22317
22470
  RED.menu.setDisabled("menu-item-edit-paste", false);
22318
22471
  if (nodeCount > 0) {
22319
22472
  RED.notify(RED._("clipboard.nodeCopied",{count:nodeCount}),{id:"clipboard"});
@@ -22454,6 +22607,7 @@ RED.view = (function() {
22454
22607
 
22455
22608
  function portMouseDown(d,portType,portIndex, evt) {
22456
22609
  if (RED.view.DEBUG) { console.warn("portMouseDown", mouse_mode,d,portType,portIndex); }
22610
+ RED.contextMenu.hide();
22457
22611
  evt = evt || d3.event;
22458
22612
  if (evt === 1) {
22459
22613
  return;
@@ -22832,11 +22986,17 @@ RED.view = (function() {
22832
22986
 
22833
22987
  if (active && ((portType === PORT_TYPE_INPUT && ((d._def && d._def.inputLabels)||d.inputLabels)) || (portType === PORT_TYPE_OUTPUT && ((d._def && d._def.outputLabels)||d.outputLabels)))) {
22834
22988
  portLabelHoverTimeout = setTimeout(function() {
22989
+ const n = port && port.node()
22990
+ const nId = n && n.__data__ && n.__data__.id
22991
+ //check see if node has been deleted since timeout started
22992
+ if(!n || !n.parentNode || !RED.nodes.node(n.__data__.id)) {
22993
+ return; //node is gone!
22994
+ }
22835
22995
  var tooltip = getPortLabel(d,portType,portIndex);
22836
22996
  if (!tooltip) {
22837
22997
  return;
22838
22998
  }
22839
- var pos = getElementPosition(port.node());
22999
+ var pos = getElementPosition(n);
22840
23000
  portLabelHoverTimeout = null;
22841
23001
  portLabelHover = showTooltip(
22842
23002
  (pos[0]+(portType===PORT_TYPE_INPUT?-2:12)),
@@ -22962,6 +23122,7 @@ RED.view = (function() {
22962
23122
  function nodeMouseDown(d) {
22963
23123
  if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); }
22964
23124
  focusView();
23125
+ RED.contextMenu.hide();
22965
23126
  if (d3.event.button === 1) {
22966
23127
  return;
22967
23128
  }
@@ -23022,6 +23183,9 @@ RED.view = (function() {
23022
23183
  updateSelection();
23023
23184
  RED.nodes.dirty(true);
23024
23185
  redraw();
23186
+ if (clipboardSource === 'cut') {
23187
+ clipboardSource = 'copy'
23188
+ }
23025
23189
  resetMouseVars();
23026
23190
  d3.event.stopPropagation();
23027
23191
  return;
@@ -23269,6 +23433,10 @@ RED.view = (function() {
23269
23433
  if (d.hasOwnProperty('l')?!d.l : (d.type === "link in" || d.type === "link out")) {
23270
23434
  var parentNode = this.parentNode;
23271
23435
  portLabelHoverTimeout = setTimeout(function() {
23436
+ //check see if node has been deleted since timeout started
23437
+ if(!parentNode || !parentNode.parentNode || !RED.nodes.node(parentNode.id)) {
23438
+ return; //node is gone!
23439
+ }
23272
23440
  var tooltip;
23273
23441
  if (d._def.label) {
23274
23442
  tooltip = d._def.label;
@@ -23344,6 +23512,7 @@ RED.view = (function() {
23344
23512
  if (RED.view.DEBUG) {
23345
23513
  console.warn("linkMouseDown", { mouse_mode, point: d3.mouse(this), event: d3.event });
23346
23514
  }
23515
+ RED.contextMenu.hide();
23347
23516
  if (mouse_mode === RED.state.SELECTING_NODE) {
23348
23517
  d3.event.stopPropagation();
23349
23518
  return;
@@ -23403,6 +23572,9 @@ RED.view = (function() {
23403
23572
  }
23404
23573
 
23405
23574
  function groupMouseUp(g) {
23575
+ if (RED.view.DEBUG) {
23576
+ console.warn("groupMouseUp", { mouse_mode, event: d3.event });
23577
+ }
23406
23578
  if (dblClickPrimed && mousedown_group == g && clickElapsed > 0 && clickElapsed < dblClickInterval) {
23407
23579
  mouse_mode = RED.state.DEFAULT;
23408
23580
  RED.editor.editGroup(g);
@@ -23418,6 +23590,10 @@ RED.view = (function() {
23418
23590
  // return
23419
23591
  // }
23420
23592
 
23593
+ if (RED.view.DEBUG) {
23594
+ console.warn("groupMouseDown", { mouse_mode, point: mouse, event: d3.event });
23595
+ }
23596
+ RED.contextMenu.hide();
23421
23597
  focusView();
23422
23598
  if (d3.event.button === 1) {
23423
23599
  return;
@@ -23623,7 +23799,7 @@ RED.view = (function() {
23623
23799
  var mdn = mousedown_node;
23624
23800
  var options = [];
23625
23801
  options.push({name:"delete",disabled:(movingSet.length()===0 && selectedLinks.length() === 0),onselect:function() {deleteSelection();}});
23626
- options.push({name:"cut",disabled:(movingSet.length()===0),onselect:function() {copySelection();deleteSelection();}});
23802
+ options.push({name:"cut",disabled:(movingSet.length()===0),onselect:function() {copySelection(true);deleteSelection();}});
23627
23803
  options.push({name:"copy",disabled:(movingSet.length()===0),onselect:function() {copySelection();}});
23628
23804
  options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard, {generateIds: true, touchImport: true});}});
23629
23805
  options.push({name:"edit",disabled:(movingSet.length() != 1),onselect:function() { RED.editor.edit(mdn);}});
@@ -24097,12 +24273,10 @@ RED.view = (function() {
24097
24273
  icon_groupEl.setAttribute("y",0);
24098
24274
  icon_groupEl.style["pointer-events"] = "none";
24099
24275
  node[0][0].__iconGroup__ = icon_groupEl;
24100
- var icon_shade = document.createElementNS("http://www.w3.org/2000/svg","rect");
24276
+ var icon_shade = document.createElementNS("http://www.w3.org/2000/svg","path");
24101
24277
  icon_shade.setAttribute("x",0);
24102
24278
  icon_shade.setAttribute("y",0);
24103
24279
  icon_shade.setAttribute("class","red-ui-flow-node-icon-shade")
24104
- icon_shade.setAttribute("width",30);
24105
- icon_shade.setAttribute("height",Math.min(50,d.h-4));
24106
24280
  icon_groupEl.appendChild(icon_shade);
24107
24281
  node[0][0].__iconShade__ = icon_shade;
24108
24282
 
@@ -24395,9 +24569,20 @@ RED.view = (function() {
24395
24569
  }
24396
24570
 
24397
24571
  icon.attr("y",function(){return (d.h-d3.select(this).attr("height"))/2;});
24398
- this.__iconShade__.setAttribute("height", d.h );
24572
+
24573
+
24574
+ const iconShadeHeight = d.h
24575
+ const iconShadeWidth = 30
24576
+ this.__iconShade__.setAttribute("d", hideLabel ?
24577
+ `M5 0 h${iconShadeWidth-10} a 5 5 0 0 1 5 5 v${iconShadeHeight-10} a 5 5 0 0 1 -5 5 h-${iconShadeWidth-10} a 5 5 0 0 1 -5 -5 v-${iconShadeHeight-10} a 5 5 0 0 1 5 -5` : (
24578
+ "right" === d._def.align ?
24579
+ `M 0 0 h${iconShadeWidth-5} a 5 5 0 0 1 5 5 v${iconShadeHeight-10} a 5 5 0 0 1 -5 5 h-${iconShadeWidth-5} v-${iconShadeHeight}` :
24580
+ `M5 0 h${iconShadeWidth-5} v${iconShadeHeight} h-${iconShadeWidth-5} a 5 5 0 0 1 -5 -5 v-${iconShadeHeight-10} a 5 5 0 0 1 5 -5`
24581
+ )
24582
+ )
24583
+ this.__iconShadeBorder__.style.display = hideLabel?'none':''
24399
24584
  this.__iconShadeBorder__.setAttribute("d",
24400
- "M " + (((!d._def.align && d.inputs !== 0 && d.outputs === 0) || "right" === d._def.align) ? 0 : 30) + " 1 l 0 " + (d.h - 2)
24585
+ "M " + (((!d._def.align && d.inputs !== 0 && d.outputs === 0) || "right" === d._def.align) ? 0.5 : 29.5) + " "+(d.selected?1:0.5)+" l 0 " + (d.h - (d.selected?2:1))
24401
24586
  );
24402
24587
  faIcon.attr("y",(d.h+13)/2);
24403
24588
  }
@@ -24414,6 +24599,9 @@ RED.view = (function() {
24414
24599
  if (d._def.button) {
24415
24600
  var buttonEnabled = isButtonEnabled(d);
24416
24601
  this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-disabled", !buttonEnabled);
24602
+ if (RED.runtime && RED.runtime.started !== undefined) {
24603
+ this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-stopped", !RED.runtime.started);
24604
+ }
24417
24605
 
24418
24606
  var x = d._def.align == "right"?d.w-6:-25;
24419
24607
  if (d._def.button.toggle && !d[d._def.button.toggle]) {
@@ -25409,6 +25597,7 @@ RED.view = (function() {
25409
25597
  * @private
25410
25598
  */
25411
25599
  function createNode(type, x, y, z) {
25600
+ const wasDirty = RED.nodes.dirty()
25412
25601
  var m = /^subflow:(.+)$/.exec(type);
25413
25602
  var activeSubflow = z ? RED.nodes.subflow(z) : null;
25414
25603
  if (activeSubflow && m) {
@@ -25467,7 +25656,7 @@ RED.view = (function() {
25467
25656
  var historyEvent = {
25468
25657
  t: "add",
25469
25658
  nodes: [nn.id],
25470
- dirty: RED.nodes.dirty()
25659
+ dirty: wasDirty
25471
25660
  }
25472
25661
  if (activeSubflow) {
25473
25662
  var subflowRefresh = RED.subflow.refresh(true);
@@ -25769,7 +25958,13 @@ RED.view = (function() {
25769
25958
  showQuickAddDialog:showQuickAddDialog,
25770
25959
  calculateNodeDimensions: calculateNodeDimensions,
25771
25960
  getElementPosition:getElementPosition,
25772
- showTooltip:showTooltip
25961
+ showTooltip:showTooltip,
25962
+ dimensions: function() {
25963
+ return {
25964
+ width: space_width,
25965
+ height: space_height
25966
+ };
25967
+ }
25773
25968
  };
25774
25969
  })();
25775
25970
  ;RED.view.annotations = (function() {
@@ -26188,6 +26383,9 @@ RED.view.tools = (function() {
26188
26383
  $(document).one('keyup',endKeyboardMove);
26189
26384
  endMoveSet = true;
26190
26385
  }
26386
+ var dim = RED.view.dimensions();
26387
+ var space_width = dim.width;
26388
+ var space_height = dim.height;
26191
26389
  var minX = 0;
26192
26390
  var minY = 0;
26193
26391
  var node;
@@ -26203,6 +26401,12 @@ RED.view.tools = (function() {
26203
26401
  node.n.dirty = true;
26204
26402
  node.n.x += dx;
26205
26403
  node.n.y += dy;
26404
+ if ((node.n.x +node.n.w/2) >= space_width) {
26405
+ node.n.x = space_width -node.n.w/2;
26406
+ }
26407
+ if ((node.n.y +node.n.h/2) >= space_height) {
26408
+ node.n.y = space_height -node.n.h/2;
26409
+ }
26206
26410
  node.n.dirty = true;
26207
26411
  if (node.n.type === "group") {
26208
26412
  RED.group.markDirty(node.n);
@@ -26897,7 +27101,7 @@ RED.view.tools = (function() {
26897
27101
  * @param {Object || Object[]} wires The wire(s) to split and replace with link-out, link-in nodes.
26898
27102
  */
26899
27103
  function splitWiresWithLinkNodes(wires) {
26900
- let wiresToSplit = wires || RED.view.selection().links;
27104
+ let wiresToSplit = wires || (RED.view.selection().links && RED.view.selection().links.filter(e => !e.link));
26901
27105
  if (!wiresToSplit) {
26902
27106
  return
26903
27107
  }
@@ -27062,13 +27266,14 @@ RED.view.tools = (function() {
27062
27266
  * - it uses `<paletteLabel> <N>` - where N is the next available integer that
27063
27267
  * doesn't clash with any existing nodes of that type
27064
27268
  * @param {Object} node The node to set the name of - if not provided, uses current selection
27269
+ * @param {{ renameBlank: boolean, renameClash: boolean, generateHistory: boolean }} options Possible options are `renameBlank`, `renameClash` and `generateHistory`
27065
27270
  */
27066
27271
  function generateNodeNames(node, options) {
27067
- options = options || {
27272
+ options = Object.assign({
27068
27273
  renameBlank: true,
27069
27274
  renameClash: true,
27070
27275
  generateHistory: true
27071
- }
27276
+ }, options)
27072
27277
  let nodes = node;
27073
27278
  if (node) {
27074
27279
  if (!Array.isArray(node)) {
@@ -27134,7 +27339,7 @@ RED.view.tools = (function() {
27134
27339
  }
27135
27340
 
27136
27341
  function addJunctionsToWires(wires) {
27137
- let wiresToSplit = wires || RED.view.selection().links;
27342
+ let wiresToSplit = wires || (RED.view.selection().links && RED.view.selection().links.filter(e => !e.link));
27138
27343
  if (!wiresToSplit) {
27139
27344
  return
27140
27345
  }
@@ -27162,6 +27367,7 @@ RED.view.tools = (function() {
27162
27367
  linkGroups.sort(function(A,B) {
27163
27368
  return groupedLinks[B].length - groupedLinks[A].length
27164
27369
  })
27370
+ const wasDirty = RED.nodes.dirty()
27165
27371
  linkGroups.forEach(function(gid) {
27166
27372
  var links = groupedLinks[gid]
27167
27373
  var junction = {
@@ -27252,12 +27458,14 @@ RED.view.tools = (function() {
27252
27458
  })
27253
27459
  if (addedJunctions.length > 0) {
27254
27460
  RED.history.push({
27461
+ dirty: wasDirty,
27255
27462
  t: 'add',
27256
27463
  links: addedLinks,
27257
27464
  junctions: addedJunctions,
27258
27465
  removedLinks: Array.from(removedLinks)
27259
27466
  })
27260
27467
  RED.nodes.dirty(true)
27468
+ RED.view.select({nodes: addedJunctions });
27261
27469
  }
27262
27470
  RED.view.redraw(true);
27263
27471
  }
@@ -29620,10 +29828,8 @@ RED.sidebar.help = (function() {
29620
29828
  var helpSection;
29621
29829
  var panels;
29622
29830
  var panelRatio;
29623
- var helpTopics = [];
29624
29831
  var treeList;
29625
29832
  var tocPanel;
29626
- var helpIndex = {};
29627
29833
 
29628
29834
  function resizeStack() {
29629
29835
  var h = $(content).parent().height() - toolbar.outerHeight();
@@ -29697,7 +29903,10 @@ RED.sidebar.help = (function() {
29697
29903
  var pendingContentLoad;
29698
29904
  treeList.on('treelistselect', function(e,item) {
29699
29905
  pendingContentLoad = item;
29700
- if (item.nodeType) {
29906
+ if (item.tour) {
29907
+ RED.tourGuide.run(item.tour);
29908
+ }
29909
+ else if (item.nodeType) {
29701
29910
  showNodeTypeHelp(item.nodeType);
29702
29911
  } else if (item.content) {
29703
29912
  helpSection.empty();
@@ -29789,7 +29998,6 @@ RED.sidebar.help = (function() {
29789
29998
  }
29790
29999
 
29791
30000
  function refreshHelpIndex() {
29792
- helpTopics = [];
29793
30001
  var modules = RED.nodes.registry.getModuleList();
29794
30002
  var moduleNames = Object.keys(modules);
29795
30003
  moduleNames.sort();
@@ -29798,15 +30006,32 @@ RED.sidebar.help = (function() {
29798
30006
  label: RED._("sidebar.help.nodeHelp"),
29799
30007
  children: [],
29800
30008
  expanded: true
29801
- }
30009
+ };
30010
+ var tours = RED.tourGuide.list().map(function (item) {
30011
+ return {
30012
+ icon: "fa fa-play-circle-o",
30013
+ label: item.label,
30014
+ tour: item.path,
30015
+ };
30016
+ });
29802
30017
  var helpData = [
29803
30018
  {
29804
- id: 'changelog',
29805
- label: "Node-RED v"+RED.settings.version,
29806
- content: getChangelog
30019
+ label: "Node-RED",
30020
+ children: [
30021
+ {
30022
+ id: 'changelog',
30023
+ label: RED._("sidebar.help.changeLog"),
30024
+ content: getChangelog
30025
+ },
30026
+ {
30027
+ label: RED._("tourGuide.welcomeTours"),
30028
+ children: tours
30029
+ }
30030
+
30031
+ ]
29807
30032
  },
29808
30033
  nodeHelp
29809
- ]
30034
+ ];
29810
30035
  var subflows = RED.nodes.registry.getNodeTypes().filter(function(t) {return /subflow/.test(t)});
29811
30036
  if (subflows.length > 0) {
29812
30037
  nodeHelp.children.push({
@@ -33147,6 +33372,10 @@ RED.editor = (function() {
33147
33372
  if (editing_node) {
33148
33373
  RED.sidebar.info.refresh(editing_node);
33149
33374
  RED.sidebar.help.show(editing_node.type, false);
33375
+ //ensure focused element is NOT body (for keyboard scope to operate correctly)
33376
+ if (document.activeElement.tagName === 'BODY') {
33377
+ $('#red-ui-editor-stack').trigger('focus')
33378
+ }
33150
33379
  }
33151
33380
  }
33152
33381
  }
@@ -34152,8 +34381,7 @@ RED.editor = (function() {
34152
34381
  if (!node._def.defaults || !node._def.defaults.hasOwnProperty("icon")) {
34153
34382
  var icon = $("#red-ui-editor-node-icon").val()||"";
34154
34383
  if (!this.isDefaultIcon) {
34155
- if ((icon !== node.icon) &&
34156
- (icon !== "")) {
34384
+ if ((node.icon && icon !== node.icon) || (!node.icon && icon !== "")) {
34157
34385
  editState.changes.icon = node.icon;
34158
34386
  node.icon = icon;
34159
34387
  editState.changed = true;
@@ -35459,7 +35687,7 @@ RED.editor = (function() {
35459
35687
 
35460
35688
  const MONACO = "monaco";
35461
35689
  const ACE = "ace";
35462
- const defaultEditor = ACE;
35690
+ const defaultEditor = MONACO;
35463
35691
  const DEFAULT_SETTINGS = { lib: defaultEditor, options: {} };
35464
35692
  var selectedCodeEditor = null;
35465
35693
  var initialised = false;
@@ -35486,12 +35714,12 @@ RED.editor = (function() {
35486
35714
  }
35487
35715
 
35488
35716
  function create(options) {
35489
- //TODO: (quandry - for consideration)
35717
+ //TODO: (quandry - for consideration)
35490
35718
  // Below, I had to create a hidden element if options.id || options.element is not in the DOM
35491
- // I have seen 1 node calling `this.editor = RED.editor.createEditor()` with an
35719
+ // I have seen 1 node calling `this.editor = RED.editor.createEditor()` with an
35492
35720
  // invalid (non existing html element selector) (e.g. node-red-contrib-components does this)
35493
- // This causes monaco to throw an error when attempting to hook up its events to the dom & the rest of the 'oneditperapre'
35494
- // code is thus skipped.
35721
+ // This causes monaco to throw an error when attempting to hook up its events to the dom & the rest of the 'oneditperapre'
35722
+ // code is thus skipped.
35495
35723
  // In ACE mode, creating an ACE editor (with an invalid ID) allows the editor to be created (but obviously there is no UI)
35496
35724
  // Because one (or more) contrib nodes have left this bad code in place, how would we handle this?
35497
35725
  // For compatibility, I have decided to create a hidden element so that at least an editor is created & errors do not occur.
@@ -35517,7 +35745,7 @@ RED.editor = (function() {
35517
35745
  return this.editor.create(options);//fallback to ACE
35518
35746
  }
35519
35747
  }
35520
-
35748
+
35521
35749
  return {
35522
35750
  init: init,
35523
35751
  /**
@@ -35529,7 +35757,7 @@ RED.editor = (function() {
35529
35757
  },
35530
35758
  /**
35531
35759
  * Get user selected code editor
35532
- * @return {string} Returns
35760
+ * @return {string} Returns
35533
35761
  * @memberof RED.editor.codeEditor
35534
35762
  */
35535
35763
  get editor() {
@@ -35542,7 +35770,8 @@ RED.editor = (function() {
35542
35770
  */
35543
35771
  create: create
35544
35772
  }
35545
- })();;RED.editor.colorPicker = RED.colorPicker = (function() {
35773
+ })();
35774
+ ;RED.editor.colorPicker = RED.colorPicker = (function() {
35546
35775
 
35547
35776
  function create(options) {
35548
35777
  var color = options.value;
@@ -35794,8 +36023,12 @@ RED.editor = (function() {
35794
36023
  style: "width:100%",
35795
36024
  class: "node-input-env-value",
35796
36025
  type: "text",
35797
- }).attr("autocomplete","disable").appendTo(envRow)
35798
- valueField.typedInput({default:'str',types:isTemplateNode?DEFAULT_ENV_TYPE_LIST:DEFAULT_ENV_TYPE_LIST_INC_CRED});
36026
+ }).attr("autocomplete","disable").appendTo(envRow);
36027
+ var types = (opt.ui && opt.ui.opts && opt.ui.opts.types);
36028
+ if (!types) {
36029
+ types = isTemplateNode ? DEFAULT_ENV_TYPE_LIST : DEFAULT_ENV_TYPE_LIST_INC_CRED;
36030
+ }
36031
+ valueField.typedInput({default:'str',types:types});
35799
36032
  valueField.typedInput('type', opt.type);
35800
36033
  if (opt.type === "cred") {
35801
36034
  if (opt.value) {
@@ -35847,6 +36080,11 @@ RED.editor = (function() {
35847
36080
  }
35848
36081
  opt.ui.label = opt.ui.label || {};
35849
36082
  opt.ui.type = opt.ui.type || "input";
36083
+ if ((opt.ui.type === "cred") &&
36084
+ opt.ui.opts &&
36085
+ opt.ui.opts.types) {
36086
+ opt.ui.type = "input";
36087
+ }
35850
36088
 
35851
36089
  var uiRow = $('<div/>').appendTo(container).hide();
35852
36090
  // save current info for reverting on cancel
@@ -37476,6 +37714,7 @@ RED.editor = (function() {
37476
37714
  var container = $("#red-ui-editor-type-json-tab-ui-container").css({"height":"100%"});
37477
37715
  var filterDepth = Infinity;
37478
37716
  var list = $('<div class="red-ui-debug-msg-payload red-ui-editor-type-json-editor">').appendTo(container).treeList({
37717
+ selectable: false,
37479
37718
  rootSortable: false,
37480
37719
  sortable: ".red-ui-editor-type-json-editor-item-handle",
37481
37720
  }).on("treelistchangeparent", function(event, evt) {
@@ -38867,7 +39106,7 @@ RED.editor.codeEditor.monaco = (function() {
38867
39106
 
38868
39107
 
38869
39108
  if(!options.stateId && options.stateId !== false) {
38870
- options.stateId = RED.editor.generateViewStateId("monaco", options, (options.mode || options.title).split("/").pop());
39109
+ options.stateId = RED.editor.generateViewStateId("monaco", options, (options.mode || options.title || "").split("/").pop());
38871
39110
  }
38872
39111
  var el = options.element || $("#"+options.id)[0];
38873
39112
  var toolbarRow = $("<div>").appendTo(el);
@@ -42719,7 +42958,7 @@ RED.search = (function() {
42719
42958
  }
42720
42959
  if (flags.hasOwnProperty("unused")) {
42721
42960
  var isUnused = (node.node.type === 'subflow' && node.node.instances.length === 0) ||
42722
- (isConfigNode && node.node.users.length === 0)
42961
+ (isConfigNode && node.node.users.length === 0 && node.node._def.hasUsers !== false)
42723
42962
  if (flags.unused !== isUnused) {
42724
42963
  continue;
42725
42964
  }
@@ -43070,7 +43309,7 @@ RED.search = (function() {
43070
43309
  $(previousActiveElement).trigger("focus");
43071
43310
  }
43072
43311
  previousActiveElement = null;
43073
- }
43312
+ }
43074
43313
  if(!keepSearchToolbar) {
43075
43314
  clearActiveSearch();
43076
43315
  }
@@ -43162,7 +43401,7 @@ RED.search = (function() {
43162
43401
  $("#red-ui-sidebar-shade").on('mousedown',hide);
43163
43402
 
43164
43403
  $("#red-ui-view-searchtools-close").on("click", function close() {
43165
- clearActiveSearch();
43404
+ clearActiveSearch();
43166
43405
  updateSearchToolbar();
43167
43406
  });
43168
43407
  $("#red-ui-view-searchtools-close").trigger("click");
@@ -43196,7 +43435,7 @@ RED.search = (function() {
43196
43435
  };
43197
43436
 
43198
43437
  })();
43199
- ;RED.contextMenu = (function() {
43438
+ ;RED.contextMenu = (function () {
43200
43439
 
43201
43440
  let menu;
43202
43441
  function createMenu() {
@@ -43213,10 +43452,6 @@ RED.search = (function() {
43213
43452
  // ],
43214
43453
  // width: 200,
43215
43454
  // })
43216
-
43217
-
43218
-
43219
-
43220
43455
  }
43221
43456
 
43222
43457
  function disposeMenu() {
@@ -43232,42 +43467,80 @@ RED.search = (function() {
43232
43467
  }
43233
43468
 
43234
43469
  const selection = RED.view.selection()
43470
+ const noSelection = !selection || Object.keys(selection).length === 0
43235
43471
  const hasSelection = (selection.nodes && selection.nodes.length > 0);
43236
43472
  const hasMultipleSelection = hasSelection && selection.nodes.length > 1;
43237
- const hasLinks = selection.links && selection.links.length > 0;
43238
- const isSingleLink = !hasSelection && hasLinks && selection.links.length === 1
43239
- const isMultipleLinks = !hasSelection && hasLinks && selection.links.length > 1
43473
+ const virtulLinks = (selection.links && selection.links.filter(e => !!e.link)) || [];
43474
+ const wireLinks = (selection.links && selection.links.filter(e => !e.link)) || [];
43475
+ const hasLinks = wireLinks.length > 0;
43476
+ const isSingleLink = !hasSelection && hasLinks && wireLinks.length === 1
43477
+ const isMultipleLinks = !hasSelection && hasLinks && wireLinks.length > 1
43240
43478
  const canDelete = hasSelection || hasLinks
43241
43479
  const isGroup = hasSelection && selection.nodes.length === 1 && selection.nodes[0].type === 'group'
43242
43480
 
43243
43481
  const canRemoveFromGroup = hasSelection && !!selection.nodes[0].g
43482
+ const offset = $("#red-ui-workspace-chart").offset()
43244
43483
 
43484
+ // addX/addY must be the position in the workspace accounting for both scroll and scale
43485
+ // The +5 is because we display the contextMenu -5,-5 to actual click position
43486
+ let addX = (options.x + 5 - offset.left + $("#red-ui-workspace-chart").scrollLeft()) / RED.view.scale()
43487
+ let addY = (options.y + 5 - offset.top + $("#red-ui-workspace-chart").scrollTop()) / RED.view.scale()
43245
43488
 
43246
43489
  const menuItems = [
43247
- { onselect: 'core:show-action-list', onpostselect: function() {} },
43490
+ { onselect: 'core:show-action-list', onpostselect: function () { } },
43248
43491
  {
43249
- label: 'Insert',
43492
+ label: RED._("contextMenu.insert"),
43250
43493
  options: [
43251
43494
  {
43252
- label: 'Node',
43253
- onselect: function() {
43495
+ label: RED._("contextMenu.node"),
43496
+ onselect: function () {
43254
43497
  RED.view.showQuickAddDialog({
43255
- position: [ options.x - offset.left, options.y - offset.top ],
43498
+ position: [addX, addY],
43256
43499
  touchTrigger: true,
43257
- splice: isSingleLink?selection.links[0]:undefined,
43500
+ splice: isSingleLink ? selection.links[0] : undefined,
43258
43501
  // spliceMultiple: isMultipleLinks
43259
43502
  })
43503
+ },
43504
+ onpostselect: function() {
43505
+ // ensure quick add dialog search input has focus
43506
+ $('#red-ui-type-search-input').trigger('focus')
43260
43507
  }
43261
43508
  },
43262
- {
43263
- label: 'Junction',
43509
+ (hasLinks) ? { // has least 1 wire selected
43510
+ label: RED._("contextMenu.junction"),
43264
43511
  onselect: 'core:split-wires-with-junctions',
43265
- disabled: hasSelection || !hasLinks
43512
+ disabled: !hasLinks
43513
+ } : {
43514
+ label: RED._("contextMenu.junction"),
43515
+ onselect: function () {
43516
+ const nn = {
43517
+ _def: { defaults: {} },
43518
+ type: 'junction',
43519
+ z: RED.workspaces.active(),
43520
+ id: RED.nodes.id(),
43521
+ x: addX,
43522
+ y: addY,
43523
+ w: 0, h: 0,
43524
+ outputs: 1,
43525
+ inputs: 1,
43526
+ dirty: true
43527
+ }
43528
+ const historyEvent = {
43529
+ dirty: RED.nodes.dirty(),
43530
+ t: 'add',
43531
+ junctions: [nn]
43532
+ }
43533
+ RED.nodes.addJunction(nn);
43534
+ RED.history.push(historyEvent);
43535
+ RED.nodes.dirty(true);
43536
+ RED.view.select({nodes: [nn] });
43537
+ RED.view.redraw(true)
43538
+ }
43266
43539
  },
43267
43540
  {
43268
- label: 'Link Nodes',
43541
+ label: RED._("contextMenu.linkNodes"),
43269
43542
  onselect: 'core:split-wire-with-link-nodes',
43270
- disabled: hasSelection || !hasLinks
43543
+ disabled: !hasLinks
43271
43544
  }
43272
43545
  ]
43273
43546
 
@@ -43275,28 +43548,13 @@ RED.search = (function() {
43275
43548
 
43276
43549
  }
43277
43550
  ]
43278
- // menuItems.push(
43279
- // {
43280
- // label: (isSingleLink || isMultipleLinks)?'Insert into wire...':'Add node...',
43281
- // onselect: function() {
43282
- // RED.view.showQuickAddDialog({
43283
- // position: [ options.x - offset.left, options.y - offset.top ],
43284
- // touchTrigger: true,
43285
- // splice: isSingleLink?selection.links[0]:undefined,
43286
- // spliceMultiple: isMultipleLinks
43287
- // })
43288
- // }
43289
- // },
43290
- // )
43291
- // if (hasLinks && !hasSelection) {
43292
- // menuItems.push({ onselect: 'core:split-wires-with-junctions', label: 'Insert junction'})
43293
- // }
43551
+
43294
43552
  menuItems.push(
43295
43553
  null,
43296
43554
  { onselect: 'core:undo', disabled: RED.history.list().length === 0 },
43297
43555
  { onselect: 'core:redo', disabled: RED.history.listRedo().length === 0 },
43298
43556
  null,
43299
- { onselect: 'core:cut-selection-to-internal-clipboard', label: RED._("keyboard.cutNode"), disabled: !hasSelection},
43557
+ { onselect: 'core:cut-selection-to-internal-clipboard', label: RED._("keyboard.cutNode"), disabled: !hasSelection },
43300
43558
  { onselect: 'core:copy-selection-to-internal-clipboard', label: RED._("keyboard.copyNode"), disabled: !hasSelection },
43301
43559
  { onselect: 'core:paste-from-internal-clipboard', label: RED._("keyboard.pasteNode"), disabled: !RED.view.clipboard() },
43302
43560
  { onselect: 'core:delete-selection', disabled: !canDelete },
@@ -43308,27 +43566,34 @@ RED.search = (function() {
43308
43566
  menuItems.push(
43309
43567
  null,
43310
43568
  isGroup ?
43311
- { onselect: 'core:ungroup-selection', disabled: !isGroup }
43312
- : { onselect: 'core:group-selection', disabled: !hasSelection }
43569
+ { onselect: 'core:ungroup-selection', disabled: !isGroup }
43570
+ : { onselect: 'core:group-selection', disabled: !hasSelection }
43313
43571
  )
43314
43572
  if (canRemoveFromGroup) {
43315
43573
  menuItems.push({ onselect: 'core:remove-selection-from-group', label: RED._("menu.label.groupRemoveSelection") })
43316
43574
  }
43317
43575
 
43318
43576
  }
43319
- const offset = $("#red-ui-workspace-chart").offset()
43577
+
43578
+ var direction = "right";
43579
+ var MENU_WIDTH = 500; // can not use menu width here
43580
+ if ((options.x -$(document).scrollLeft()) >
43581
+ ($(window).width() -MENU_WIDTH)) {
43582
+ direction = "left";
43583
+ }
43584
+
43320
43585
  menu = RED.menu.init({
43321
- direction: 'right',
43586
+ direction: direction,
43322
43587
  onpreselect: function() {
43323
43588
  disposeMenu()
43324
43589
  },
43325
- onpostselect: function() {
43590
+ onpostselect: function () {
43326
43591
  RED.view.focus()
43327
43592
  },
43328
43593
  options: menuItems
43329
43594
  });
43330
43595
 
43331
- menu.attr("id","red-ui-workspace-context-menu");
43596
+ menu.attr("id", "red-ui-workspace-context-menu");
43332
43597
  menu.css({
43333
43598
  position: "absolute"
43334
43599
  })
@@ -43339,36 +43604,38 @@ RED.search = (function() {
43339
43604
  var top = options.y
43340
43605
  var left = options.x
43341
43606
 
43342
- if (top+menu.height()-$(document).scrollTop() > $(window).height()) {
43343
- top -= (top+menu.height())-$(window).height() + 22;
43607
+ if (top + menu.height() - $(document).scrollTop() > $(window).height()) {
43608
+ top -= (top + menu.height()) - $(window).height() + 22;
43344
43609
  }
43345
- if (left+menu.width()-$(document).scrollLeft() > $(window).width()) {
43346
- left -= (left+menu.width())-$(window).width() + 18;
43610
+ if (left + menu.width() - $(document).scrollLeft() > $(window).width()) {
43611
+ left -= (left + menu.width()) - $(window).width() + 18;
43347
43612
  }
43348
43613
  menu.css({
43349
- top: top+"px",
43350
- left: left+"px"
43614
+ top: top + "px",
43615
+ left: left + "px"
43351
43616
  })
43352
43617
  $(".red-ui-menu.red-ui-menu-dropdown").hide();
43353
- $(document).on("mousedown.red-ui-workspace-context-menu", function(evt) {
43618
+ $(document).on("mousedown.red-ui-workspace-context-menu", function (evt) {
43354
43619
  if (menu && menu[0].contains(evt.target)) {
43355
43620
  return
43356
43621
  }
43357
43622
  disposeMenu()
43358
43623
  });
43359
43624
  menu.show();
43360
-
43361
- // menu.show({
43362
- // target: $('#red-ui-main-container'),
43363
- // x: options.x,
43364
- // y: options.y
43365
- // })
43366
-
43625
+ // set focus to first item so that pressing escape key closes the menu
43626
+ $("#red-ui-workspace-context-menu :first(ul) > a").trigger("focus")
43367
43627
 
43368
43628
  }
43369
-
43629
+ // Allow escape key hook and other editor events to close context menu
43630
+ RED.keyboard.add("red-ui-workspace-context-menu", "escape", function () { RED.contextMenu.hide() })
43631
+ RED.events.on("editor:open", function () { RED.contextMenu.hide() });
43632
+ RED.events.on("search:open", function () { RED.contextMenu.hide() });
43633
+ RED.events.on("type-search:open", function () { RED.contextMenu.hide() });
43634
+ RED.events.on("actionList:open", function () { RED.contextMenu.hide() });
43635
+ RED.events.on("view:selection-changed", function () { RED.contextMenu.hide() });
43370
43636
  return {
43371
- show: show
43637
+ show: show,
43638
+ hide: disposeMenu
43372
43639
  }
43373
43640
  })()
43374
43641
  ;/**
@@ -43874,8 +44141,8 @@ RED.actionList = (function() {
43874
44141
  moveCallback = opts.move;
43875
44142
  RED.events.emit("type-search:open");
43876
44143
  //shade.show();
43877
- if ($("#red-ui-main-container").height() - opts.y - 150 < 0) {
43878
- opts.y = opts.y - 235;
44144
+ if ($("#red-ui-main-container").height() - opts.y - 195 < 0) {
44145
+ opts.y = opts.y - 275;
43879
44146
  }
43880
44147
  dialog.css({left:opts.x+"px",top:opts.y+"px"}).show();
43881
44148
  searchResultsDiv.slideDown(300);
@@ -44904,6 +45171,7 @@ RED.subflow = (function() {
44904
45171
  RED.nodes.dirty(true);
44905
45172
  RED.view.updateActive();
44906
45173
  RED.view.select(null);
45174
+ RED.view.focus();
44907
45175
  }
44908
45176
 
44909
45177
 
@@ -44958,6 +45226,7 @@ RED.subflow = (function() {
44958
45226
 
44959
45227
 
44960
45228
  function buildEnvUIRow(row, tenv, ui, node) {
45229
+ console.log(tenv, ui)
44961
45230
  ui.label = ui.label||{};
44962
45231
  if ((tenv.type === "cred" || (tenv.parent && tenv.parent.type === "cred")) && !ui.type) {
44963
45232
  ui.type = "cred";
@@ -45018,6 +45287,17 @@ RED.subflow = (function() {
45018
45287
  default: inputType
45019
45288
  })
45020
45289
  input.typedInput('value',val.value)
45290
+ if (inputType === 'cred') {
45291
+ if (node.credentials) {
45292
+ if (node.credentials[tenv.name]) {
45293
+ input.typedInput('value', node.credentials[tenv.name]);
45294
+ } else if (node.credentials['has_'+tenv.name]) {
45295
+ input.typedInput('value', "__PWRD__")
45296
+ } else {
45297
+ input.typedInput('value', "");
45298
+ }
45299
+ }
45300
+ }
45021
45301
  } else {
45022
45302
  input.val(val.value)
45023
45303
  }
@@ -45659,6 +45939,7 @@ RED.group = (function() {
45659
45939
  RED.history.push(historyEvent);
45660
45940
  RED.view.select({nodes:[group]});
45661
45941
  RED.nodes.dirty(true);
45942
+ RED.view.focus();
45662
45943
  }
45663
45944
  }
45664
45945
  }
@@ -45681,6 +45962,7 @@ RED.group = (function() {
45681
45962
  RED.history.push(historyEvent);
45682
45963
  RED.view.select({nodes:newSelection})
45683
45964
  RED.nodes.dirty(true);
45965
+ RED.view.focus();
45684
45966
  }
45685
45967
  }
45686
45968
 
@@ -45775,6 +46057,7 @@ RED.group = (function() {
45775
46057
  });
45776
46058
  RED.history.push(historyEvent);
45777
46059
  RED.nodes.dirty(true);
46060
+ RED.view.focus();
45778
46061
  }
45779
46062
  }
45780
46063
 
@@ -45802,6 +46085,7 @@ RED.group = (function() {
45802
46085
  }
45803
46086
  }
45804
46087
  RED.view.select({nodes:selection.nodes})
46088
+ RED.view.focus();
45805
46089
  }
45806
46090
  }
45807
46091
  function createGroup(nodes) {
@@ -52943,9 +53227,30 @@ RED.touch.radialMenu = (function() {
52943
53227
  })
52944
53228
  }
52945
53229
 
53230
+ function listTour() {
53231
+ return [
53232
+ {
53233
+ id: "3_0",
53234
+ label: "3.0",
53235
+ path: "./tours/welcome.js"
53236
+ },
53237
+ {
53238
+ id: "2_2",
53239
+ label: "2.2",
53240
+ path: "./tours/2.2/welcome.js"
53241
+ },
53242
+ {
53243
+ id: "2_1",
53244
+ label: "2.1",
53245
+ path: "./tours/2.1/welcome.js"
53246
+ }
53247
+ ];
53248
+ }
53249
+
52946
53250
  return {
52947
53251
  load: loadTour,
52948
53252
  run: run,
53253
+ list: listTour,
52949
53254
  reset: function() {
52950
53255
  RED.settings.set("editor.tours.welcome",'');
52951
53256
  }