@node-red/editor-client 4.0.2 → 4.0.4

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
@@ -2386,16 +2386,36 @@ RED.comms = (function() {
2386
2386
  break
2387
2387
  }
2388
2388
  }
2389
+ if (isInWorkspace) {
2390
+ const chart = $('#red-ui-workspace-chart')
2391
+ const chartOffset = chart.offset()
2392
+ const scaleFactor = RED.view.scale()
2393
+ location.cursor = {
2394
+ x: (lastPosition[0] - chartOffset.left + chart.scrollLeft()) / scaleFactor,
2395
+ y: (lastPosition[1] - chartOffset.top + chart.scrollTop()) / scaleFactor
2396
+ }
2397
+ }
2389
2398
  return location
2390
2399
  }
2400
+
2401
+ let publishLocationTimeout
2402
+ let lastPosition = [0,0]
2403
+ let isInWorkspace = false
2404
+
2391
2405
  function publishLocation () {
2392
- const location = getLocation()
2393
- if (location.workspace !== 0) {
2394
- log('send', 'multiplayer/location', location)
2395
- RED.comms.send('multiplayer/location', location)
2406
+ if (!publishLocationTimeout) {
2407
+ publishLocationTimeout = setTimeout(() => {
2408
+ const location = getLocation()
2409
+ if (location.workspace !== 0) {
2410
+ log('send', 'multiplayer/location', location)
2411
+ RED.comms.send('multiplayer/location', location)
2412
+ }
2413
+ publishLocationTimeout = null
2414
+ }, 100)
2396
2415
  }
2397
2416
  }
2398
2417
 
2418
+
2399
2419
  function revealUser(location, skipWorkspace) {
2400
2420
  if (location.node) {
2401
2421
  // Need to check if this is a known node, so we can fall back to revealing
@@ -2557,7 +2577,16 @@ RED.comms = (function() {
2557
2577
 
2558
2578
  function removeUserLocation (sessionId) {
2559
2579
  updateUserLocation(sessionId, {})
2580
+ removeUserCursor(sessionId)
2560
2581
  }
2582
+ function removeUserCursor (sessionId) {
2583
+ // return
2584
+ if (sessions[sessionId]?.cursor) {
2585
+ sessions[sessionId].cursor.parentNode.removeChild(sessions[sessionId].cursor)
2586
+ delete sessions[sessionId].cursor
2587
+ }
2588
+ }
2589
+
2561
2590
  function updateUserLocation (sessionId, location) {
2562
2591
  let viewTouched = false
2563
2592
  const oldLocation = sessions[sessionId].location
@@ -2577,6 +2606,28 @@ RED.comms = (function() {
2577
2606
  // console.log(`updateUserLocation sessionId:${sessionId} oldWS:${oldLocation?.workspace} newWS:${location.workspace}`)
2578
2607
  if (location.workspace) {
2579
2608
  getWorkspaceTray(location.workspace).addUser(sessionId)
2609
+ if (location.cursor && location.workspace === RED.workspaces.active()) {
2610
+ if (!sessions[sessionId].cursor) {
2611
+ const user = sessions[sessionId].user
2612
+ const cursorIcon = document.createElementNS("http://www.w3.org/2000/svg","g");
2613
+ cursorIcon.setAttribute("class", "red-ui-multiplayer-annotation")
2614
+ cursorIcon.appendChild(createAnnotationUser(user, true))
2615
+ $(cursorIcon).css({
2616
+ transform: `translate( ${location.cursor.x}px, ${location.cursor.y}px)`,
2617
+ transition: 'transform 0.1s linear'
2618
+ })
2619
+ $("#red-ui-workspace-chart svg").append(cursorIcon)
2620
+ sessions[sessionId].cursor = cursorIcon
2621
+ } else {
2622
+ const cursorIcon = sessions[sessionId].cursor
2623
+ $(cursorIcon).css({
2624
+ transform: `translate( ${location.cursor.x}px, ${location.cursor.y}px)`
2625
+ })
2626
+
2627
+ }
2628
+ } else if (sessions[sessionId].cursor) {
2629
+ removeUserCursor(sessionId)
2630
+ }
2580
2631
  }
2581
2632
  if (location.node) {
2582
2633
  addUserToNode(sessionId, location.node)
@@ -2595,67 +2646,69 @@ RED.comms = (function() {
2595
2646
  // }
2596
2647
  // }
2597
2648
 
2598
- return {
2599
- init: function () {
2600
2649
 
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
-
2650
+ function createAnnotationUser(user, pointer = false) {
2651
+ const radius = 20
2652
+ const halfRadius = radius/2
2653
+ const group = document.createElementNS("http://www.w3.org/2000/svg","g");
2654
+ const badge = document.createElementNS("http://www.w3.org/2000/svg","path");
2655
+ let shapePath
2656
+ if (!pointer) {
2657
+ shapePath = `M 0 ${halfRadius} a ${halfRadius} ${halfRadius} 0 1 1 ${radius} 0 a ${halfRadius} ${halfRadius} 0 1 1 -${radius} 0 z`
2658
+ } else {
2659
+ shapePath = `M 0 0 h ${halfRadius} a ${halfRadius} ${halfRadius} 0 1 1 -${halfRadius} ${halfRadius} z`
2660
+ }
2661
+ badge.setAttribute('d', shapePath)
2662
+ badge.setAttribute("class", "red-ui-multiplayer-annotation-background")
2663
+ group.appendChild(badge)
2664
+ if (user && user.profileColor !== undefined) {
2665
+ badge.setAttribute("class", "red-ui-multiplayer-annotation-background red-ui-user-profile-color-" + user.profileColor)
2666
+ }
2667
+ if (user && user.image) {
2668
+ const image = document.createElementNS("http://www.w3.org/2000/svg","image");
2669
+ image.setAttribute("width", radius)
2670
+ image.setAttribute("height", radius)
2671
+ image.setAttribute("href", user.image)
2672
+ image.setAttribute("clip-path", "circle("+Math.floor(radius/2)+")")
2673
+ group.appendChild(image)
2674
+ } else if (user && user.anonymous) {
2675
+ const anonIconHead = document.createElementNS("http://www.w3.org/2000/svg","circle");
2676
+ anonIconHead.setAttribute("cx", radius/2)
2677
+ anonIconHead.setAttribute("cy", radius/2 - 2)
2678
+ anonIconHead.setAttribute("r", 2.4)
2679
+ anonIconHead.setAttribute("class","red-ui-multiplayer-annotation-anon-label");
2680
+ group.appendChild(anonIconHead)
2681
+ const anonIconBody = document.createElementNS("http://www.w3.org/2000/svg","path");
2682
+ anonIconBody.setAttribute("class","red-ui-multiplayer-annotation-anon-label");
2683
+ // anonIconBody.setAttribute("d",`M ${radius/2 - 4} ${radius/2 + 1} h 8 v4 h -8 z`);
2684
+ 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`);
2685
+ group.appendChild(anonIconBody)
2686
+ } else {
2687
+ const labelText = user.username ? user.username.substring(0,2) : user
2688
+ const label = document.createElementNS("http://www.w3.org/2000/svg","text");
2689
+ if (user.username) {
2690
+ label.setAttribute("class","red-ui-multiplayer-annotation-label");
2691
+ label.textContent = user.username.substring(0,2)
2692
+ } else {
2693
+ label.setAttribute("class","red-ui-multiplayer-annotation-label red-ui-multiplayer-user-count")
2694
+ label.textContent = user
2695
+ }
2696
+ label.setAttribute("text-anchor", "middle")
2697
+ label.setAttribute("x",radius/2);
2698
+ label.setAttribute("y",radius/2 + 3);
2699
+ group.appendChild(label)
2700
+ }
2701
+ const border = document.createElementNS("http://www.w3.org/2000/svg","path");
2702
+ border.setAttribute('d', shapePath)
2703
+ border.setAttribute("class", "red-ui-multiplayer-annotation-border")
2704
+ group.appendChild(border)
2705
+ return group
2706
+ }
2655
2707
 
2708
+ return {
2709
+ init: function () {
2656
2710
 
2657
- return group
2658
- }
2711
+
2659
2712
 
2660
2713
  RED.view.annotations.register("red-ui-multiplayer",{
2661
2714
  type: 'badge',
@@ -2765,6 +2818,24 @@ RED.comms = (function() {
2765
2818
  RED.comms.send('multiplayer/disconnect', disconnectInfo)
2766
2819
  RED.settings.removeLocal('multiplayer:sessionId')
2767
2820
  })
2821
+
2822
+ const chart = $('#red-ui-workspace-chart')
2823
+ chart.on('mousemove', function (evt) {
2824
+ lastPosition[0] = evt.clientX
2825
+ lastPosition[1] = evt.clientY
2826
+ publishLocation()
2827
+ })
2828
+ chart.on('scroll', function (evt) {
2829
+ publishLocation()
2830
+ })
2831
+ chart.on('mouseenter', function () {
2832
+ isInWorkspace = true
2833
+ publishLocation()
2834
+ })
2835
+ chart.on('mouseleave', function () {
2836
+ isInWorkspace = false
2837
+ publishLocation()
2838
+ })
2768
2839
  }
2769
2840
  }
2770
2841
 
@@ -6757,11 +6828,28 @@ RED.nodes = (function() {
6757
6828
  } else {
6758
6829
  delete n.g
6759
6830
  }
6760
- // If importing into a subflow, ensure an outbound-link doesn't get added
6761
- if (activeSubflow && /^link /.test(n.type) && n.links) {
6831
+ // If importing a link node, ensure both ends of each link are either:
6832
+ // - not in a subflow
6833
+ // - both in the same subflow (not for link call node)
6834
+ if (/^link /.test(n.type) && n.links) {
6762
6835
  n.links = n.links.filter(function(id) {
6763
6836
  const otherNode = node_map[id] || RED.nodes.node(id);
6764
- return (otherNode && otherNode.z === activeWorkspace);
6837
+ if (!otherNode) {
6838
+ // Cannot find other end - remove the link
6839
+ return false
6840
+ }
6841
+ if (otherNode.z === n.z) {
6842
+ // Both ends in the same flow/subflow
6843
+ return true
6844
+ } else if (n.type === "link call" && !!getSubflow(otherNode.z)) {
6845
+ // Link call node can call out of a subflow as long as otherNode is
6846
+ // not in a subflow
6847
+ return false
6848
+ } else if (!!getSubflow(n.z) || !!getSubflow(otherNode.z)) {
6849
+ // One end is in a subflow - remove the link
6850
+ return false
6851
+ }
6852
+ return true
6765
6853
  });
6766
6854
  }
6767
6855
  for (var d3 in n._def.defaults) {
@@ -17435,7 +17523,9 @@ RED.deploy = (function() {
17435
17523
  RED.notify('<p>' + RED._("deploy.successfulDeploy") + '</p>', "success");
17436
17524
  }
17437
17525
  const flowsToLock = new Set()
17526
+ // Node's properties cannot be modified if its workspace is locked.
17438
17527
  function ensureUnlocked(id) {
17528
+ // TODO: `RED.nodes.subflow` is useless
17439
17529
  const flow = id && (RED.nodes.workspace(id) || RED.nodes.subflow(id) || null);
17440
17530
  const isLocked = flow ? flow.locked : false;
17441
17531
  if (flow && isLocked) {
@@ -17488,6 +17578,7 @@ RED.deploy = (function() {
17488
17578
  delete confNode.credentials;
17489
17579
  }
17490
17580
  });
17581
+ // Subflow cannot be locked
17491
17582
  RED.nodes.eachSubflow(function (subflow) {
17492
17583
  if (subflow.changed) {
17493
17584
  subflow.changed = false;
@@ -17496,12 +17587,18 @@ RED.deploy = (function() {
17496
17587
  });
17497
17588
  RED.nodes.eachWorkspace(function (ws) {
17498
17589
  if (ws.changed || ws.added) {
17499
- ensureUnlocked(ws.z)
17590
+ // Ensure the Workspace is unlocked to modify its properties.
17591
+ ensureUnlocked(ws.id);
17500
17592
  ws.changed = false;
17501
17593
  delete ws.added
17594
+ if (flowsToLock.has(ws)) {
17595
+ ws.locked = true;
17596
+ flowsToLock.delete(ws);
17597
+ }
17502
17598
  RED.events.emit("flows:change", ws)
17503
17599
  }
17504
17600
  });
17601
+ // Ensures all workspaces to be locked have been locked.
17505
17602
  flowsToLock.forEach(flow => {
17506
17603
  flow.locked = true
17507
17604
  })
@@ -18099,7 +18196,7 @@ RED.diagnostics = (function () {
18099
18196
  }
18100
18197
  })
18101
18198
  if (c === 0) {
18102
- result.text("none");
18199
+ result.text(RED._("diff.type.none"));
18103
18200
  } else {
18104
18201
  list.appendTo(result);
18105
18202
  }
@@ -18423,7 +18520,7 @@ RED.diagnostics = (function () {
18423
18520
  conflict = true;
18424
18521
  }
18425
18522
  row = $("<tr>").appendTo(nodePropertiesTableBody);
18426
- $("<td>",{class:"red-ui-diff-list-cell-label"}).text("position").appendTo(row);
18523
+ $("<td>",{class:"red-ui-diff-list-cell-label"}).text(RED._("diff.type.position")).appendTo(row);
18427
18524
  localCell = $("<td>",{class:"red-ui-diff-list-cell red-ui-diff-list-node-local"}).appendTo(row);
18428
18525
  if (localNode) {
18429
18526
  localCell.addClass("red-ui-diff-status-"+(localChanged?"moved":"unchanged"));
@@ -18501,7 +18598,7 @@ RED.diagnostics = (function () {
18501
18598
  conflict = true;
18502
18599
  }
18503
18600
  row = $("<tr>").appendTo(nodePropertiesTableBody);
18504
- $("<td>",{class:"red-ui-diff-list-cell-label"}).text("wires").appendTo(row);
18601
+ $("<td>",{class:"red-ui-diff-list-cell-label"}).text(RED._("diff.type.wires")).appendTo(row);
18505
18602
  localCell = $("<td>",{class:"red-ui-diff-list-cell red-ui-diff-list-node-local"}).appendTo(row);
18506
18603
  if (localNode) {
18507
18604
  if (!conflict) {
@@ -19631,15 +19728,14 @@ RED.diagnostics = (function () {
19631
19728
  if (!isSeparator) {
19632
19729
  var isOurs = /^..<<<<<<</.test(lineText);
19633
19730
  if (isOurs) {
19634
- $('<span>').text("<<<<<<< Local Changes").appendTo(line);
19731
+ $('<span>').text("<<<<<<< " + RED._("diff.localChanges")).appendTo(line);
19635
19732
  hunk.localChangeStart = actualLineNumber;
19636
19733
  } else {
19637
19734
  hunk.remoteChangeEnd = actualLineNumber;
19638
- $('<span>').text(">>>>>>> Remote Changes").appendTo(line);
19639
-
19735
+ $('<span>').text(">>>>>>> " + RED._("diff.remoteChanges")).appendTo(line);
19640
19736
  }
19641
19737
  diffRow.addClass("mergeHeader-"+(isOurs?"ours":"theirs"));
19642
- $('<button class="red-ui-button red-ui-button-small" style="float: right; margin-right: 20px;"><i class="fa fa-angle-double-'+(isOurs?"down":"up")+'"></i> use '+(isOurs?"local":"remote")+' changes</button>')
19738
+ $('<button class="red-ui-button red-ui-button-small" style="float: right; margin-right: 20px;"><i class="fa fa-angle-double-'+(isOurs?"down":"up")+'"></i> '+RED._(isOurs?"diff.useLocalChanges":"diff.useRemoteChanges")+'</button>')
19643
19739
  .appendTo(line)
19644
19740
  .on("click", function(evt) {
19645
19741
  evt.preventDefault();
@@ -19721,7 +19817,7 @@ RED.diagnostics = (function () {
19721
19817
  $("<h3>").text(commit.title).appendTo(content);
19722
19818
  $('<div class="commit-body"></div>').text(commit.comment).appendTo(content);
19723
19819
  var summary = $('<div class="commit-summary"></div>').appendTo(content);
19724
- $('<div style="float: right">').text("Commit "+commit.sha).appendTo(summary);
19820
+ $('<div style="float: right">').text(RED._('diff.commit')+" "+commit.sha).appendTo(summary);
19725
19821
  $('<div>').text((commit.authorName||commit.author)+" - "+options.date).appendTo(summary);
19726
19822
 
19727
19823
  if (commit.files) {
@@ -21013,25 +21109,29 @@ RED.workspaces = (function() {
21013
21109
  },
21014
21110
  null)
21015
21111
  }
21016
- menuItems.push(
21017
- {
21018
- id:"red-ui-tabs-menu-option-add-flow",
21019
- label: RED._("workspace.addFlow"),
21020
- onselect: "core:add-flow"
21021
- }
21022
- )
21023
- if (isMenuButton || !!tab) {
21112
+ if (RED.settings.theme("menu.menu-item-workspace-add", true)) {
21024
21113
  menuItems.push(
21025
21114
  {
21026
- id:"red-ui-tabs-menu-option-add-flow-right",
21027
- label: RED._("workspace.addFlowToRight"),
21028
- shortcut: RED.keyboard.getShortcut("core:add-flow-to-right"),
21029
- onselect: function() {
21030
- RED.actions.invoke("core:add-flow-to-right", tab)
21031
- }
21032
- },
21033
- null
21115
+ id:"red-ui-tabs-menu-option-add-flow",
21116
+ label: RED._("workspace.addFlow"),
21117
+ onselect: "core:add-flow"
21118
+ }
21034
21119
  )
21120
+ }
21121
+ if (isMenuButton || !!tab) {
21122
+ if (RED.settings.theme("menu.menu-item-workspace-add", true)) {
21123
+ menuItems.push(
21124
+ {
21125
+ id:"red-ui-tabs-menu-option-add-flow-right",
21126
+ label: RED._("workspace.addFlowToRight"),
21127
+ shortcut: RED.keyboard.getShortcut("core:add-flow-to-right"),
21128
+ onselect: function() {
21129
+ RED.actions.invoke("core:add-flow-to-right", tab)
21130
+ }
21131
+ },
21132
+ null
21133
+ )
21134
+ }
21035
21135
  if (activeWorkspace && activeWorkspace.type === 'tab') {
21036
21136
  menuItems.push(
21037
21137
  isFlowDisabled ? {
@@ -21085,7 +21185,9 @@ RED.workspaces = (function() {
21085
21185
  }
21086
21186
  )
21087
21187
  }
21088
- menuItems.push(null)
21188
+ if (menuItems.length > 0) {
21189
+ menuItems.push(null)
21190
+ }
21089
21191
  if (isMenuButton || !!tab) {
21090
21192
  menuItems.push(
21091
21193
  {
@@ -21129,19 +21231,24 @@ RED.workspaces = (function() {
21129
21231
  }
21130
21232
  )
21131
21233
  if (tab) {
21234
+ menuItems.push(null)
21235
+
21236
+ if (RED.settings.theme("menu.menu-item-workspace-delete", true)) {
21237
+ menuItems.push(
21238
+ {
21239
+ label: RED._("common.label.delete"),
21240
+ onselect: function() {
21241
+ if (tab.type === 'tab') {
21242
+ RED.workspaces.delete(tab)
21243
+ } else if (tab.type === 'subflow') {
21244
+ RED.subflow.delete(tab.id)
21245
+ }
21246
+ },
21247
+ disabled: isCurrentLocked || (workspaceTabCount === 1)
21248
+ }
21249
+ )
21250
+ }
21132
21251
  menuItems.push(
21133
- null,
21134
- {
21135
- label: RED._("common.label.delete"),
21136
- onselect: function() {
21137
- if (tab.type === 'tab') {
21138
- RED.workspaces.delete(tab)
21139
- } else if (tab.type === 'subflow') {
21140
- RED.subflow.delete(tab.id)
21141
- }
21142
- },
21143
- disabled: isCurrentLocked || (workspaceTabCount === 1)
21144
- },
21145
21252
  {
21146
21253
  label: RED._("menu.label.export"),
21147
21254
  shortcut: RED.keyboard.getShortcut("core:show-export-dialog"),
@@ -21298,7 +21405,7 @@ RED.workspaces = (function() {
21298
21405
  },
21299
21406
  minimumActiveTabWidth: 150,
21300
21407
  scrollable: true,
21301
- addButton: "core:add-flow",
21408
+ addButton: RED.settings.theme("menu.menu-item-workspace-add", true) ? "core:add-flow" : undefined,
21302
21409
  addButtonCaption: RED._("workspace.addFlow"),
21303
21410
  menu: function() { return getMenuItems(true) },
21304
21411
  contextmenu: function(tab) { return getMenuItems(false, tab) }
@@ -21355,19 +21462,24 @@ RED.workspaces = (function() {
21355
21462
  $(window).on("resize", function() {
21356
21463
  workspace_tabs.resize();
21357
21464
  });
21358
-
21359
- RED.actions.add("core:add-flow",function(opts) { addWorkspace(undefined,undefined,opts?opts.index:undefined)});
21360
- RED.actions.add("core:add-flow-to-right",function(workspace) {
21361
- let index
21362
- if (workspace) {
21363
- index = workspace_tabs.getTabIndex(workspace.id)+1
21364
- } else {
21365
- index = workspace_tabs.activeIndex()+1
21366
- }
21367
- addWorkspace(undefined,undefined,index)
21368
- });
21369
- RED.actions.add("core:edit-flow",editWorkspace);
21370
- RED.actions.add("core:remove-flow",removeWorkspace);
21465
+ if (RED.settings.theme("menu.menu-item-workspace-add", true)) {
21466
+ RED.actions.add("core:add-flow",function(opts) { addWorkspace(undefined,undefined,opts?opts.index:undefined)});
21467
+ RED.actions.add("core:add-flow-to-right",function(workspace) {
21468
+ let index
21469
+ if (workspace) {
21470
+ index = workspace_tabs.getTabIndex(workspace.id)+1
21471
+ } else {
21472
+ index = workspace_tabs.activeIndex()+1
21473
+ }
21474
+ addWorkspace(undefined,undefined,index)
21475
+ });
21476
+ }
21477
+ if (RED.settings.theme("menu.menu-item-workspace-edit", true)) {
21478
+ RED.actions.add("core:edit-flow",editWorkspace);
21479
+ }
21480
+ if (RED.settings.theme("menu.menu-item-workspace-delete", true)) {
21481
+ RED.actions.add("core:remove-flow",removeWorkspace);
21482
+ }
21371
21483
  RED.actions.add("core:enable-flow",enableWorkspace);
21372
21484
  RED.actions.add("core:disable-flow",disableWorkspace);
21373
21485
  RED.actions.add("core:lock-flow",lockWorkspace);
@@ -21734,6 +21846,17 @@ RED.workspaces = (function() {
21734
21846
  }
21735
21847
  },
21736
21848
  refresh: function() {
21849
+ var workspace = RED.nodes.workspace(RED.workspaces.active());
21850
+ if (workspace) {
21851
+ document.title = `${documentTitle} : ${workspace.label}`;
21852
+ } else {
21853
+ var subflow = RED.nodes.subflow(RED.workspaces.active());
21854
+ if (subflow) {
21855
+ document.title = `${documentTitle} : ${subflow.name}`;
21856
+ } else {
21857
+ document.title = documentTitle
21858
+ }
21859
+ }
21737
21860
  RED.nodes.eachWorkspace(function(ws) {
21738
21861
  workspace_tabs.renameTab(ws.id,ws.label);
21739
21862
  $("#red-ui-tab-"+(ws.id.replace(".","-"))).attr("flowname",ws.label)
@@ -23016,7 +23139,10 @@ RED.view = (function() {
23016
23139
  lasso = null;
23017
23140
  }
23018
23141
  if (d3.event.touches || d3.event.button === 0) {
23019
- if ((mouse_mode === 0 || mouse_mode === RED.state.QUICK_JOINING) && isControlPressed(d3.event) && !(d3.event.altKey || d3.event.shiftKey)) {
23142
+ if (
23143
+ (mouse_mode === 0 && isControlPressed(d3.event) && !(d3.event.altKey || d3.event.shiftKey)) ||
23144
+ mouse_mode === RED.state.QUICK_JOINING
23145
+ ) {
23020
23146
  // Trigger quick add dialog
23021
23147
  d3.event.stopPropagation();
23022
23148
  clearSelection();
@@ -23092,7 +23218,6 @@ RED.view = (function() {
23092
23218
  }
23093
23219
 
23094
23220
  var mainPos = $("#red-ui-main-container").position();
23095
-
23096
23221
  if (mouse_mode !== RED.state.QUICK_JOINING) {
23097
23222
  mouse_mode = RED.state.QUICK_JOINING;
23098
23223
  $(window).on('keyup',disableQuickJoinEventHandler);
@@ -24864,8 +24989,8 @@ RED.view = (function() {
24864
24989
  }
24865
24990
 
24866
24991
  function disableQuickJoinEventHandler(evt) {
24867
- // Check for ctrl (all browsers), "Meta" (Chrome/FF), keyCode 91 (Safari)
24868
- if (evt.keyCode === 17 || evt.key === "Meta" || evt.keyCode === 91) {
24992
+ // Check for ctrl (all browsers), "Meta" (Chrome/FF), keyCode 91 (Safari), or Escape
24993
+ if (evt.keyCode === 17 || evt.key === "Meta" || evt.keyCode === 91 || evt.keyCode === 27) {
24869
24994
  resetMouseVars();
24870
24995
  hideDragLines();
24871
24996
  redraw();
@@ -24996,27 +25121,59 @@ RED.view = (function() {
24996
25121
 
24997
25122
  for (i=0;i<drag_lines.length;i++) {
24998
25123
  if (portType != drag_lines[i].portType && mouseup_node !== drag_lines[i].node) {
24999
- var drag_line = drag_lines[i];
25000
- var src,dst,src_port;
25124
+ let drag_line = drag_lines[i];
25125
+ let src,dst,src_port;
25126
+ let oldDst;
25127
+ let oldSrc;
25001
25128
  if (drag_line.portType === PORT_TYPE_OUTPUT) {
25002
25129
  src = drag_line.node;
25003
25130
  src_port = drag_line.port;
25004
25131
  dst = mouseup_node;
25132
+ oldSrc = src;
25133
+ if (drag_line.link) {
25134
+ oldDst = drag_line.link.target;
25135
+ }
25005
25136
  } else if (drag_line.portType === PORT_TYPE_INPUT) {
25006
25137
  src = mouseup_node;
25007
25138
  dst = drag_line.node;
25008
25139
  src_port = portIndex || 0;
25140
+ oldSrc = dst;
25141
+ if (drag_line.link) {
25142
+ oldDst = drag_line.link.source
25143
+ }
25009
25144
  }
25010
25145
  var link = {source: src, sourcePort:src_port, target: dst};
25011
25146
  if (drag_line.virtualLink) {
25012
25147
  if (/^link (in|out)$/.test(src.type) && /^link (in|out)$/.test(dst.type) && src.type !== dst.type) {
25013
25148
  if (src.links.indexOf(dst.id) === -1 && dst.links.indexOf(src.id) === -1) {
25014
- var oldSrcLinks = $.extend(true,{},{v:src.links}).v
25015
- var oldDstLinks = $.extend(true,{},{v:dst.links}).v
25149
+ var oldSrcLinks = [...src.links]
25150
+ var oldDstLinks = [...dst.links]
25151
+
25016
25152
  src.links.push(dst.id);
25017
25153
  dst.links.push(src.id);
25154
+
25155
+ if (oldDst) {
25156
+ src.links = src.links.filter(id => id !== oldDst.id)
25157
+ dst.links = dst.links.filter(id => id !== oldDst.id)
25158
+ var oldOldDstLinks = [...oldDst.links]
25159
+ oldDst.links = oldDst.links.filter(id => id !== oldSrc.id)
25160
+ oldDst.dirty = true;
25161
+ modifiedNodes.push(oldDst);
25162
+ linkEditEvents.push({
25163
+ t:'edit',
25164
+ node: oldDst,
25165
+ dirty: RED.nodes.dirty(),
25166
+ changed: oldDst.changed,
25167
+ changes: {
25168
+ links:oldOldDstLinks
25169
+ }
25170
+ });
25171
+ oldDst.changed = true;
25172
+ }
25173
+
25018
25174
  src.dirty = true;
25019
25175
  dst.dirty = true;
25176
+
25020
25177
  modifiedNodes.push(src);
25021
25178
  modifiedNodes.push(dst);
25022
25179
 
@@ -25044,6 +25201,7 @@ RED.view = (function() {
25044
25201
  links:oldDstLinks
25045
25202
  }
25046
25203
  });
25204
+
25047
25205
  src.changed = true;
25048
25206
  dst.changed = true;
25049
25207
  }
@@ -31318,7 +31476,7 @@ RED.sidebar.info = (function() {
31318
31476
 
31319
31477
  propertiesPanelHeaderIcon.empty();
31320
31478
  RED.utils.createNodeIcon({type:"_selection_"}).appendTo(propertiesPanelHeaderIcon);
31321
- propertiesPanelHeaderLabel.text("Selection");
31479
+ propertiesPanelHeaderLabel.text(RED._("sidebar.info.selection"));
31322
31480
  propertiesPanelHeaderReveal.hide();
31323
31481
  propertiesPanelHeaderHelp.hide();
31324
31482
  propertiesPanelHeaderCopyLink.hide();
@@ -36089,7 +36247,7 @@ RED.editor = (function() {
36089
36247
  const labelText = RED.editor.envVarList.lookupLabel(labels, labels["en-US"] || tenv.name, locale)
36090
36248
  const config = {
36091
36249
  env: tenv,
36092
- id: '${' + parentEnv[0].name + '}',
36250
+ id: '${' + tenv.name + '}',
36093
36251
  type: type,
36094
36252
  label: labelText,
36095
36253
  __label__: `[env] ${labelText}`
@@ -45814,10 +45972,10 @@ RED.library = (function() {
45814
45972
  if (file && file.label && !file.children) {
45815
45973
  $.get("library/"+file.library+"/"+file.type+"/"+file.path, function(data) {
45816
45974
  //TODO: nls + sanitize
45817
- var propRow = $('<tr class="red-ui-help-info-row"><td>Type</td><td></td></tr>').appendTo(table);
45975
+ var propRow = $('<tr class="red-ui-help-info-row"><td>'+RED._("library.type")+'</td><td></td></tr>').appendTo(table);
45818
45976
  $(propRow.children()[1]).text(activeLibrary.type);
45819
45977
  if (file.props.hasOwnProperty('name')) {
45820
- propRow = $('<tr class="red-ui-help-info-row"><td>Name</td><td>'+file.props.name+'</td></tr>').appendTo(table);
45978
+ propRow = $('<tr class="red-ui-help-info-row"><td>'+RED._("library.name")+'</td><td>'+file.props.name+'</td></tr>').appendTo(table);
45821
45979
  $(propRow.children()[1]).text(file.props.name);
45822
45980
  }
45823
45981
  for (var p in file.props) {
@@ -46901,24 +47059,28 @@ RED.search = (function() {
46901
47059
  const canRemoveFromGroup = hasSelection && !!selection.nodes[0].g
46902
47060
  let hasGroup, isAllGroups = true, hasDisabledNode, hasEnabledNode, hasLabeledNode, hasUnlabeledNode;
46903
47061
  if (hasSelection) {
46904
- selection.nodes.forEach(n => {
47062
+ const nodes = selection.nodes.slice();
47063
+ while (nodes.length) {
47064
+ const n = nodes.shift();
46905
47065
  if (n.type === 'group') {
46906
47066
  hasGroup = true;
47067
+ nodes.push(...n.nodes);
46907
47068
  } else {
46908
47069
  isAllGroups = false;
46909
- }
46910
- if (n.d) {
46911
- hasDisabledNode = true;
46912
- } else {
46913
- hasEnabledNode = true;
47070
+ if (n.d) {
47071
+ hasDisabledNode = true;
47072
+ } else {
47073
+ hasEnabledNode = true;
47074
+ }
46914
47075
  }
46915
47076
  if (n.l === undefined || n.l) {
46916
47077
  hasLabeledNode = true;
46917
47078
  } else {
46918
47079
  hasUnlabeledNode = true;
46919
47080
  }
46920
- });
47081
+ }
46921
47082
  }
47083
+
46922
47084
  const offset = $("#red-ui-workspace-chart").offset()
46923
47085
 
46924
47086
  let addX = options.x - offset.left + $("#red-ui-workspace-chart").scrollLeft()
@@ -46930,10 +47092,11 @@ RED.search = (function() {
46930
47092
  addY = gridSize * Math.floor(addY / gridSize)
46931
47093
  }
46932
47094
 
46933
- menuItems.push(
46934
- { onselect: 'core:show-action-list', label: RED._("contextMenu.showActionList"), onpostselect: function () { } }
46935
- )
46936
-
47095
+ if (RED.settings.theme("menu.menu-item-action-list", true)) {
47096
+ menuItems.push(
47097
+ { onselect: 'core:show-action-list', label: RED._("contextMenu.showActionList"), onpostselect: function () { } }
47098
+ )
47099
+ }
46937
47100
  const insertOptions = []
46938
47101
  menuItems.push({ label: RED._("contextMenu.insert"), options: insertOptions })
46939
47102
  insertOptions.push(
@@ -47350,7 +47513,9 @@ RED.actionList = (function() {
47350
47513
  }
47351
47514
 
47352
47515
  function init() {
47353
- RED.actions.add("core:show-action-list",show);
47516
+ if (RED.settings.theme("menu.menu-item-action-list", true)) {
47517
+ RED.actions.add("core:show-action-list",show);
47518
+ }
47354
47519
 
47355
47520
  RED.events.on("editor:open",function() { disabled = true; });
47356
47521
  RED.events.on("editor:close",function() { disabled = false; });
@@ -47656,6 +47821,11 @@ RED.actionList = (function() {
47656
47821
  if ($("#red-ui-main-container").height() - opts.y - 195 < 0) {
47657
47822
  opts.y = opts.y - 275;
47658
47823
  }
47824
+ const dialogWidth = dialog.width() || 300 // default is 300 (defined in class .red-ui-search)
47825
+ const workspaceWidth = $('#red-ui-workspace').width()
47826
+ if (workspaceWidth > dialogWidth && workspaceWidth - opts.x - dialogWidth < 0) {
47827
+ opts.x = opts.x - (dialogWidth - RED.view.node_width)
47828
+ }
47659
47829
  dialog.css({left:opts.x+"px",top:opts.y+"px"}).show();
47660
47830
  searchResultsDiv.slideDown(300);
47661
47831
  setTimeout(function() {
@@ -47707,13 +47877,25 @@ RED.actionList = (function() {
47707
47877
  }
47708
47878
  }
47709
47879
  function applyFilter(filter,type,def) {
47710
- return !def || !filter ||
47711
- (
47712
- (!filter.spliceMultiple) &&
47713
- (!filter.type || type === filter.type) &&
47714
- (!filter.input || type === 'junction' || def.inputs > 0) &&
47715
- (!filter.output || type === 'junction' || def.outputs > 0)
47716
- )
47880
+ if (!filter) {
47881
+ // No filter; allow everything
47882
+ return true
47883
+ }
47884
+ if (type === 'junction') {
47885
+ // Only allow Junction is there's no specific type filter
47886
+ return !filter.type
47887
+ }
47888
+ if (filter.type) {
47889
+ // Handle explicit type filter
47890
+ return filter.type === type
47891
+ }
47892
+ if (!def) {
47893
+ // No node definition available - allow it
47894
+ return true
47895
+ }
47896
+ // Check if the filter is for input/outputs and apply
47897
+ return (!filter.input || def.inputs > 0) &&
47898
+ (!filter.output || def.outputs > 0)
47717
47899
  }
47718
47900
  function refreshTypeList(opts) {
47719
47901
  var i;
@@ -53044,7 +53226,7 @@ RED.projects.settings = (function() {
53044
53226
  if (activeProject.dependencies) {
53045
53227
  for (var m in activeProject.dependencies) {
53046
53228
  if (activeProject.dependencies.hasOwnProperty(m)) {
53047
- var installed = !!RED.nodes.registry.getModule(m) && activeProject.dependencies[m] === modulesInUse[m].version;
53229
+ var installed = !!RED.nodes.registry.getModule(m) && activeProject.dependencies[m] === modulesInUse[m]?.version;
53048
53230
  depsList.editableList('addItem',{
53049
53231
  id: m,
53050
53232
  version: activeProject.dependencies[m], //RED.nodes.registry.getModule(module).version,
@@ -53992,7 +54174,7 @@ RED.projects.settings = (function() {
53992
54174
  notification.close();
53993
54175
  }
53994
54176
  },{
53995
- text: 'Delete branch',
54177
+ text: RED._("sidebar.project.projectSettings.deleteBranch"),
53996
54178
  click: function() {
53997
54179
  notification.close();
53998
54180
  var url = "projects/"+activeProject.name+"/branches/"+entry.name;